#include "Win32Crt.h" #if !defined(_WIN32) #include #include #include #include #include #include #include #include #include #include #include #include #include // ANSI code pages come from Microsoft's own tables, not the host iconv (bionic has no GBK). #include "codepage.h" #include #include #include #include #include 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(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 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 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(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(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 DWORD wait_until(std::unique_lock& 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 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 state = std::make_shared(); DWORD Wait(DWORD milliseconds) override { std::unique_lock 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(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(static_cast(handle)) : nullptr; if (!semaphore || release_count <= 0) return FALSE; { std::lock_guard 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(handle)->Wait(milliseconds); } BOOL CloseHandle(HANDLE handle) { if (!handle || handle == INVALID_HANDLE_VALUE) return FALSE; delete static_cast(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 state = thread->state; try { std::thread([state, start_address, arglist] { start_address(arglist); { std::lock_guard 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 next_id{1}; *thrdaddr = next_id++; } return reinterpret_cast(static_cast(thread)); } char* _strlwr(char* s) { for (char* p = s; *p; ++p) *p = static_cast(std::tolower(static_cast(*p))); return s; } char* _strupr(char* s) { for (char* p = s; *p; ++p) *p = static_cast(std::toupper(static_cast(*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(len); if (need < count) return std::vsnprintf(buf, count, fmt, ap); std::vector 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(steady_clock::now().time_since_epoch()).count(); return static_cast(static_cast(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(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(lpCurrentChar); if (IsDBCSLeadByteEx(CodePage, BYTE(*lpCurrentChar)) && lpCurrentChar[1]) return const_cast(lpCurrentChar + 2); return const_cast(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(prev); } #endif // !_WIN32 int MessageBox(HWND, LPCSTR lpText, LPCSTR lpCaption, UINT) { std::fprintf(stderr, "[MessageBox] %s: %s\n", lpCaption ? lpCaption : "", lpText ? lpText : ""); return IDOK; }