layout: restructure into src/ tests/ android/ scripts/ tools/
- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
(+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten
No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -0,0 +1,350 @@
|
||||
#include "Win32Crt.h"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#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(); }
|
||||
|
||||
BOOL CreateDirectory(LPCSTR path, void*)
|
||||
{
|
||||
return mkdir(path, 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(path, m);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user