Files
mtgodot-poc/src/port/common/Win32Crt.cpp
T
shenleiandClaude Opus 5.5 042bddf195 port(EterLib): Mutex/Thread/FileLoaderThread/GrpVertexBufferDynamic verbatim over Win32Crt kernel-object shims
Adds CreateSemaphore/ReleaseSemaphore/WaitForSingleObject/CloseHandle/
SetThreadPriority/_beginthreadex to Win32Crt; port.file_loader_thread covers
the shims, CThread and both the un-Created (40250 live) and Created loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:24 +09:00

501 lines
14 KiB
C++

#include "Win32Crt.h"
#if !defined(_WIN32)
#include <cctype>
#include <chrono>
#include <atomic>
#include <condition_variable>
#include <system_error>
#include <memory>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <new>
#include <string>
#include <thread>
#include <vector>
// ANSI code pages come from Microsoft's own tables, not the host iconv (bionic has no GBK).
#include "codepage.h"
#include <algorithm>
#include <dirent.h>
#include <fnmatch.h>
#include <sys/stat.h>
#include <unistd.h>
static_assert(sizeof(std::recursive_mutex) <= sizeof(CRITICAL_SECTION::storage), "CRITICAL_SECTION storage");
static_assert(alignof(std::recursive_mutex) <= 16, "CRITICAL_SECTION alignment");
static std::recursive_mutex& cs_mutex(LPCRITICAL_SECTION cs)
{
return *std::launder(reinterpret_cast<std::recursive_mutex*>(cs->storage));
}
void InitializeCriticalSection(LPCRITICAL_SECTION cs) { new (cs->storage) std::recursive_mutex(); }
void DeleteCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).~recursive_mutex(); }
void EnterCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).lock(); }
void LeaveCriticalSection(LPCRITICAL_SECTION cs) { cs_mutex(cs).unlock(); }
// Win32 takes '\\' and '/' as separators alike.
static std::string native_path(const char* path)
{
std::string normalized(path ? path : "");
for (char& ch : normalized)
if (ch == '\\') ch = '/';
return normalized;
}
BOOL CreateDirectory(LPCSTR path, void*)
{
return mkdir(native_path(path).c_str(), 0777) == 0 ? TRUE : FALSE;
}
BOOL DeleteFile(LPCSTR path)
{
return unlink(path) == 0 ? TRUE : FALSE;
}
int _access(const char* path, int mode)
{
int m = F_OK;
if (mode & 2)
m |= W_OK;
if (mode & 4)
m |= R_OK;
return access(native_path(path).c_str(), m);
}
int _unlink(const char* path)
{
return unlink(path);
}
namespace
{
struct FindState
{
std::vector<std::string> paths;
size_t next = 0;
};
void fill_find_data(const std::string& path, WIN32_FIND_DATA* data)
{
const size_t slash = path.find_last_of('/');
const std::string name = path.substr(slash == std::string::npos ? 0 : slash + 1);
std::snprintf(data->cFileName, sizeof(data->cFileName), "%s", name.c_str());
struct stat st{};
data->dwFileAttributes = stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode) ? FILE_ATTRIBUTE_DIRECTORY
: FILE_ATTRIBUTE_NORMAL;
}
}
BOOL CopyFile(LPCSTR existing_path, LPCSTR new_path, BOOL fail_if_exists)
{
if (!existing_path || !new_path)
return FALSE;
if (fail_if_exists && _access(new_path, 0) == 0)
return FALSE;
FILE* in = std::fopen(existing_path, "rb");
if (!in)
return FALSE;
FILE* out = std::fopen(new_path, "wb");
if (!out)
{
std::fclose(in);
return FALSE;
}
char buffer[65536];
size_t n;
bool ok = true;
while ((n = std::fread(buffer, 1, sizeof(buffer), in)) > 0)
ok = ok && std::fwrite(buffer, 1, n, out) == n;
std::fclose(in);
ok = std::fclose(out) == 0 && ok;
return ok ? TRUE : FALSE;
}
HANDLE FindFirstFile(const char* pattern, WIN32_FIND_DATA* data)
{
if (!pattern || !data)
return INVALID_HANDLE_VALUE;
std::string normalized(pattern);
for (char& ch : normalized)
if (ch == '\\') ch = '/';
// Win32 wildcards only appear in the last component; match it against the directory like
// glob(3) would (sorted, '*' skips dot files). glob itself is API 28+ on Android.
std::vector<std::string> paths;
const size_t slash = normalized.rfind('/');
const std::string dir = slash == std::string::npos ? std::string() : normalized.substr(0, slash + 1);
const std::string name = normalized.substr(dir.size());
if (name.find_first_of("*?[") == std::string::npos)
{
struct stat st;
if (::stat(normalized.c_str(), &st) == 0)
paths.push_back(normalized);
}
else if (DIR* d = ::opendir(dir.empty() ? "." : dir.c_str()))
{
while (const dirent* entry = ::readdir(d))
if (::fnmatch(name.c_str(), entry->d_name, FNM_PERIOD) == 0)
paths.push_back(dir + entry->d_name);
::closedir(d);
std::sort(paths.begin(), paths.end());
}
if (paths.empty())
return INVALID_HANDLE_VALUE;
auto* state = new FindState;
state->paths = std::move(paths);
fill_find_data(state->paths[0], data);
state->next = 1;
return state;
}
BOOL FindNextFile(HANDLE handle, WIN32_FIND_DATA* data)
{
if (handle == INVALID_HANDLE_VALUE || !handle || !data)
return FALSE;
auto* state = static_cast<FindState*>(handle);
if (state->next >= state->paths.size())
return FALSE;
fill_find_data(state->paths[state->next++], data);
return TRUE;
}
BOOL FindClose(HANDLE handle)
{
if (handle == INVALID_HANDLE_VALUE || !handle)
return FALSE;
delete static_cast<FindState*>(handle);
return TRUE;
}
void Sleep(DWORD milliseconds)
{
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
// A HANDLE from CreateSemaphore or _beginthreadex points at one of these. The waitable state is shared
// with a running thread, so CloseHandle on a live thread only drops the handle, as on Win32.
namespace
{
struct KernelObject
{
virtual ~KernelObject() = default;
virtual DWORD Wait(DWORD milliseconds) = 0;
};
template <class Ready>
DWORD wait_until(std::unique_lock<std::mutex>& lock, std::condition_variable& cv, DWORD milliseconds, Ready ready)
{
if (milliseconds == INFINITE)
cv.wait(lock, ready);
else if (!cv.wait_for(lock, std::chrono::milliseconds(milliseconds), ready))
return WAIT_TIMEOUT;
return WAIT_OBJECT_0;
}
struct Semaphore final : KernelObject
{
std::mutex mutex;
std::condition_variable cv;
LONG count = 0;
LONG maximum = 0;
DWORD Wait(DWORD milliseconds) override
{
std::unique_lock<std::mutex> lock(mutex);
const DWORD result = wait_until(lock, cv, milliseconds, [this] { return count > 0; });
if (result == WAIT_OBJECT_0)
--count;
return result;
}
};
struct ThreadState
{
std::mutex mutex;
std::condition_variable cv;
bool done = false;
};
struct Thread final : KernelObject
{
std::shared_ptr<ThreadState> state = std::make_shared<ThreadState>();
DWORD Wait(DWORD milliseconds) override
{
std::unique_lock<std::mutex> lock(state->mutex);
return wait_until(lock, state->cv, milliseconds, [this] { return state->done; });
}
};
}
HANDLE CreateSemaphore(void*, LONG initial_count, LONG maximum_count, LPCSTR)
{
if (maximum_count <= 0 || initial_count < 0 || initial_count > maximum_count)
return NULL;
Semaphore* semaphore = new Semaphore;
semaphore->count = initial_count;
semaphore->maximum = maximum_count;
return static_cast<KernelObject*>(semaphore);
}
// Fails without changing the count when the release would pass the maximum (ERROR_TOO_MANY_POSTS).
BOOL ReleaseSemaphore(HANDLE handle, LONG release_count, LONG* previous_count)
{
Semaphore* semaphore = handle ? dynamic_cast<Semaphore*>(static_cast<KernelObject*>(handle)) : nullptr;
if (!semaphore || release_count <= 0)
return FALSE;
{
std::lock_guard<std::mutex> lock(semaphore->mutex);
if (release_count > semaphore->maximum - semaphore->count)
return FALSE;
if (previous_count)
*previous_count = semaphore->count;
semaphore->count += release_count;
}
semaphore->cv.notify_all();
return TRUE;
}
DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds)
{
if (!handle || handle == INVALID_HANDLE_VALUE)
return WAIT_FAILED;
return static_cast<KernelObject*>(handle)->Wait(milliseconds);
}
BOOL CloseHandle(HANDLE handle)
{
if (!handle || handle == INVALID_HANDLE_VALUE)
return FALSE;
delete static_cast<KernelObject*>(handle);
return TRUE;
}
BOOL SetThreadPriority(HANDLE handle, int)
{
return handle ? TRUE : FALSE;
}
uintptr_t _beginthreadex(void*, unsigned, unsigned (*start_address)(void*), void* arglist, unsigned, unsigned* thrdaddr)
{
Thread* thread = new Thread;
std::shared_ptr<ThreadState> state = thread->state;
try
{
std::thread([state, start_address, arglist] {
start_address(arglist);
{
std::lock_guard<std::mutex> lock(state->mutex);
state->done = true;
}
state->cv.notify_all();
}).detach();
}
catch (const std::system_error&)
{
delete thread;
return 0;
}
if (thrdaddr)
{
static std::atomic<unsigned> next_id{1};
*thrdaddr = next_id++;
}
return reinterpret_cast<uintptr_t>(static_cast<KernelObject*>(thread));
}
char* _strlwr(char* s)
{
for (char* p = s; *p; ++p)
*p = static_cast<char>(std::tolower(static_cast<unsigned char>(*p)));
return s;
}
char* _strupr(char* s)
{
for (char* p = s; *p; ++p)
*p = static_cast<char>(std::toupper(static_cast<unsigned char>(*p)));
return s;
}
int _vsnprintf(char* buf, size_t count, const char* fmt, va_list ap)
{
va_list probe;
va_copy(probe, ap);
const int len = std::vsnprintf(nullptr, 0, fmt, probe);
va_end(probe);
if (len < 0)
return -1;
const size_t need = static_cast<size_t>(len);
if (need < count)
return std::vsnprintf(buf, count, fmt, ap);
std::vector<char> full(need + 1);
std::vsnprintf(full.data(), full.size(), fmt, ap);
std::memcpy(buf, full.data(), count);
return need == count ? len : -1;
}
int _snprintf(char* buf, size_t count, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
const int ret = _vsnprintf(buf, count, fmt, ap);
va_end(ap);
return ret;
}
DWORD timeGetTime()
{
using namespace std::chrono;
const auto ms = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
return static_cast<DWORD>(static_cast<uint64_t>(ms));
}
DWORD GetTickCount()
{
return timeGetTime();
}
namespace {
// CP_ACP is the Western system code page here, as on the 40250 target machines.
UINT ansi(UINT cp) { return cp == CP_ACP ? 1252 : cp; }
bool decode_utf8(const unsigned char* p, size_t n, std::u32string& out)
{
bool ok = true;
for (size_t i = 0; i < n;) {
const unsigned char c = p[i];
size_t len = c < 0x80 ? 1 : c >= 0xC2 && c <= 0xDF ? 2 : c >= 0xE0 && c <= 0xEF ? 3 : c >= 0xF0 && c <= 0xF4 ? 4 : 0;
char32_t cp = len == 1 ? c : len == 2 ? (c & 0x1F) : len == 3 ? (c & 0x0F) : (c & 0x07);
if (len == 0 || i + len > n) { out.push_back(0xFFFD); ok = false; ++i; continue; }
bool valid = true;
for (size_t k = 1; k < len; ++k) {
if ((p[i + k] & 0xC0) != 0x80) { valid = false; break; }
cp = (cp << 6) | (p[i + k] & 0x3F);
}
if (!valid || (len == 3 && cp < 0x800) || (len == 4 && (cp < 0x10000 || cp > 0x10FFFF)) || (cp >= 0xD800 && cp <= 0xDFFF)) {
out.push_back(0xFFFD); ok = false; ++i; continue;
}
out.push_back(cp);
i += len;
}
return ok;
}
void encode_utf8(char32_t cp, std::string& out)
{
if (cp < 0x80) out.push_back(char(cp));
else if (cp < 0x800) { out.push_back(char(0xC0 | (cp >> 6))); out.push_back(char(0x80 | (cp & 0x3F))); }
else if (cp < 0x10000) { out.push_back(char(0xE0 | (cp >> 12))); out.push_back(char(0x80 | ((cp >> 6) & 0x3F))); out.push_back(char(0x80 | (cp & 0x3F))); }
else { out.push_back(char(0xF0 | (cp >> 18))); out.push_back(char(0x80 | ((cp >> 12) & 0x3F))); out.push_back(char(0x80 | ((cp >> 6) & 0x3F))); out.push_back(char(0x80 | (cp & 0x3F))); }
}
bool decode(UINT cp, const unsigned char* p, size_t n, std::u32string& out)
{
if (cp == CP_UTF8) return decode_utf8(p, n, out);
if (mt_codepage::supported(ansi(cp))) return mt_codepage::decode(ansi(cp), p, n, out);
// No table for this code page: ASCII passes, the rest becomes U+FFFD.
for (size_t i = 0; i < n; ++i) out.push_back(p[i] < 0x80 ? char32_t(p[i]) : char32_t(0xFFFD));
return true;
}
void encode(UINT cp, DWORD flags, const std::u32string& in, std::string& out, bool* used_default)
{
if (cp == CP_UTF8) {
for (char32_t c : in) encode_utf8(c, out);
return;
}
if (mt_codepage::supported(ansi(cp))) {
mt_codepage::encode(ansi(cp), in, out, (flags & WC_NO_BEST_FIT_CHARS) == 0, used_default);
return;
}
for (char32_t c : in) {
if (c < 0x80) out.push_back(char(c));
else { out.push_back('?'); *used_default = true; }
}
}
} // namespace
int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
LPWSTR lpWideCharStr, int cchWideChar)
{
if (!lpMultiByteStr || cbMultiByte == 0 || cchWideChar < 0) return 0;
const size_t n = cbMultiByte < 0 ? std::strlen(lpMultiByteStr) + 1 : size_t(cbMultiByte);
std::u32string wide;
const bool ok = decode(CodePage, reinterpret_cast<const unsigned char*>(lpMultiByteStr), n, wide);
if (!ok && (dwFlags & MB_ERR_INVALID_CHARS)) return 0;
if (cchWideChar == 0) return int(wide.size());
if (wide.size() > size_t(cchWideChar)) return 0;
for (size_t i = 0; i < wide.size(); ++i) lpWideCharStr[i] = WCHAR(wide[i]);
return int(wide.size());
}
int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
{
if (!lpWideCharStr || cchWideChar == 0 || cbMultiByte < 0) return 0;
size_t n = 0;
if (cchWideChar < 0) { while (lpWideCharStr[n]) ++n; ++n; }
else n = size_t(cchWideChar);
std::u32string wide(n, 0);
for (size_t i = 0; i < n; ++i) wide[i] = char32_t(lpWideCharStr[i]);
std::string bytes;
bool used_default = false;
encode(CodePage, dwFlags, wide, bytes, &used_default);
if (used_default && lpDefaultChar && CodePage != CP_UTF8) {
// '?' was the placeholder; the caller's default char replaces it only where a fallback happened.
std::string redo;
for (char32_t c : wide) {
std::string one;
bool miss = false;
encode(CodePage, dwFlags, std::u32string(1, c), one, &miss);
redo += miss ? std::string(1, *lpDefaultChar) : one;
}
bytes.swap(redo);
}
if (lpUsedDefaultChar) *lpUsedDefaultChar = used_default ? TRUE : FALSE;
if (cbMultiByte == 0) return int(bytes.size());
if (bytes.size() > size_t(cbMultiByte)) return 0;
std::memcpy(lpMultiByteStr, bytes.data(), bytes.size());
return int(bytes.size());
}
BOOL IsDBCSLeadByteEx(UINT CodePage, BYTE TestChar)
{
return mt_codepage::is_lead_byte(ansi(CodePage), TestChar) ? TRUE : FALSE;
}
LPSTR CharNextExA(WORD CodePage, LPCSTR lpCurrentChar, DWORD)
{
if (!*lpCurrentChar) return const_cast<LPSTR>(lpCurrentChar);
if (IsDBCSLeadByteEx(CodePage, BYTE(*lpCurrentChar)) && lpCurrentChar[1]) return const_cast<LPSTR>(lpCurrentChar + 2);
return const_cast<LPSTR>(lpCurrentChar + 1);
}
LPSTR CharPrevExA(WORD CodePage, LPCSTR lpStart, LPCSTR lpCurrentChar, DWORD dwFlags)
{
// Walk forward from lpStart so a trail byte is never taken for a lead byte.
LPCSTR prev = lpStart;
for (LPCSTR p = lpStart; p < lpCurrentChar;) {
prev = p;
LPCSTR next = CharNextExA(CodePage, p, dwFlags);
if (next == p) break;
p = next;
}
return const_cast<LPSTR>(prev);
}
#endif // !_WIN32
int MessageBox(HWND, LPCSTR lpText, LPCSTR lpCaption, UINT)
{
std::fprintf(stderr, "[MessageBox] %s: %s\n", lpCaption ? lpCaption : "", lpText ? lpText : "");
return IDOK;
}