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>
501 lines
14 KiB
C++
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;
|
|
}
|