diff --git a/audit/history.jsonl b/audit/history.jsonl index 70ea8c37..5bc056c3 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -617,3 +617,5 @@ {"date": "2026-09-30", "unit": "EterLib/TextBar.cpp", "action": "all PORTED verbatim; GetTextExtentPoint32A added to the GDI shim", "evidence": "port.text_bar"} {"date": "2026-09-30", "unit": "EterLib/DibBar.cpp", "action": "all PORTED verbatim", "evidence": "port.text_bar"} {"date": "2026-09-30", "unit": "EterLib/BlockTexture.cpp", "action": "Render ADAPTED (UI image of the CPU texture), rest PORTED verbatim", "evidence": "port.text_bar"} +{"date": "2026-09-30", "unit": "UserInterface/ProcessCRC.cpp", "action": "port", "evidence": "verbatim; process introspection ADAPTED (no module snapshot -> ProcCRC 0); port.process_crc"} +{"date": "2026-09-30", "unit": "UserInterface/WiseLogicXTrap.cpp", "action": "port", "evidence": "verbatim non-XTRAP build; mapped PORTED"} diff --git a/audit/port-map/UserInterface/ProcessCRC.cpp.json b/audit/port-map/UserInterface/ProcessCRC.cpp.json new file mode 100644 index 00000000..1195b818 --- /dev/null +++ b/audit/port-map/UserInterface/ProcessCRC.cpp.json @@ -0,0 +1,65 @@ +{ + "reference": "UserInterface/ProcessCRC.cpp", + "reference_sha256": "5d7e9065b473da7dbf043c5251087114064d8f9a3b0d77108f5a4d71ca15e41c", + "priority": "P4", + "contracts": [], + "functions": { + "stristr": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:stristr" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ] + }, + "GetProcessInformation": { + "status": "ADAPTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:GetProcessInformation" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ], + "note": "40250: Toolhelp32 module snapshot, returns the path/base of the module whose path contains the exe name, false if none. Port: no module snapshot on the platform, always false. Invariant: __GetExeCRC then takes the not-found path (ProcCRC = 0)." + }, + "GetProcessMemoryCRC": { + "status": "ADAPTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:GetProcessMemoryCRC" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ], + "note": "40250: CRC32 of the first 1 MB of the module image via ReadProcessMemory, 0 on failure. Port: returns 0 (no ReadProcessMemory); unreachable since GetProcessInformation is always false. Invariant: ProcCRC bytes of the magic cube are 0." + }, + "__GetExeCRC": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:__GetExeCRC" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ], + "note": "FileCRC = GetFileCRC32(GetExcutedFileName), whose PORT returns the working folder" + }, + "BuildProcessCRC": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:BuildProcessCRC" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ] + }, + "GetProcessCRCMagicCubePiece": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/ProcessCRC.cpp:GetProcessCRCMagicCubePiece" + ], + "test": [ + "tests/port/port_process_crc_test.cpp" + ] + } + } +} diff --git a/audit/port-map/UserInterface/WiseLogicXTrap.cpp.json b/audit/port-map/UserInterface/WiseLogicXTrap.cpp.json new file mode 100644 index 00000000..b916dc22 --- /dev/null +++ b/audit/port-map/UserInterface/WiseLogicXTrap.cpp.json @@ -0,0 +1,43 @@ +{ + "reference": "UserInterface/WiseLogicXTrap.cpp", + "reference_sha256": "429871b63fecbc1c89f2d5522fa13637103602af72c54a0b24b74cdf36a4c8ab", + "priority": "P4", + "contracts": [], + "functions": { + "XTrap_Init": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/WiseLogicXTrap.cpp:XTrap_Init" + ], + "note": "the #else (no XTRAP_CLIENT_ENABLE) build: the shipped Locale_inc.h (LOCALE_SERVICE_SINGAPORE) does not define it" + }, + "XTrap_CheckInit": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/WiseLogicXTrap.cpp:XTrap_CheckInit" + ], + "note": "the #else (no XTRAP_CLIENT_ENABLE) build: the shipped Locale_inc.h (LOCALE_SERVICE_SINGAPORE) does not define it" + }, + "XTrap_PollEvent": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/WiseLogicXTrap.cpp:XTrap_PollEvent" + ], + "note": "the #else (no XTRAP_CLIENT_ENABLE) build: the shipped Locale_inc.h (LOCALE_SERVICE_SINGAPORE) does not define it" + }, + "XTrap_ValidateCheckStream": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/WiseLogicXTrap.cpp:XTrap_ValidateCheckStream" + ], + "note": "the #else (no XTRAP_CLIENT_ENABLE) build: the shipped Locale_inc.h (LOCALE_SERVICE_SINGAPORE) does not define it" + }, + "XTrap_SetUserInfo": { + "status": "PORTED", + "impl": [ + "src/platform/UserInterface/WiseLogicXTrap.cpp:XTrap_SetUserInfo" + ], + "note": "the #else (no XTRAP_CLIENT_ENABLE) build: the shipped Locale_inc.h (LOCALE_SERVICE_SINGAPORE) does not define it" + } + } +} diff --git a/src/platform/UserInterface/ProcessCRC.cpp b/src/platform/UserInterface/ProcessCRC.cpp index d2078c87..dc551a84 100644 --- a/src/platform/UserInterface/ProcessCRC.cpp +++ b/src/platform/UserInterface/ProcessCRC.cpp @@ -1,23 +1,87 @@ -// Platform skeleton for UserInterface/ProcessCRC.h (40250 UserInterface/ProcessCRC.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 UserInterface/ProcessCRC.cpp, verbatim except the Win32 process introspection (see PORT below). +// GetExeCRC is declared in ProcessCRC.h but only __GetExeCRC is defined in 40250. #include "UserInterface/StdAfx.h" #include "UserInterface/ProcessCRC.h" -#include "../PlatformStub.h" +static BYTE abCRCMagicCube[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static BYTE abCRCXorTable[8] = { 102, 30, 188, 44, 39, 201, 43, 5 }; +static BYTE bMagicCubeIdx = 0; -auto GetExeCRC(DWORD &, DWORD &) -> bool +const char * stristr(const char * big, const char * little) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + const char * t = big; + size_t len = strlen(little) - 1; + + for (t = big; *t; ++t) + if (!_strnicmp(t, little, len)) + return t; + + return NULL; } -auto BuildProcessCRC() -> void +// PORT: 40250 walks a Toolhelp32 module snapshot for the module whose path contains the executable name and +// returns its path and load address. The platform has no module snapshot, so the executable is never found: +// the path in which 40250 gives up the same way (then ProcCRC = 0). +bool GetProcessInformation(std::string & exeFileName, LPCVOID * ppvAddress) { - MT_PLATFORM_STUB(); + return false; } -auto GetProcessCRCMagicCubePiece() -> BYTE +// PORT: 40250 ReadProcessMemory's the first 1 MB of the module image and CRCs it (a partial copy counts). +// Unreachable here (GetProcessInformation never finds the module); returns 0 as 40250 does on a failed read. +DWORD GetProcessMemoryCRC(LPCVOID c_pvBaseAddress) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + return 0; +} + +bool __GetExeCRC(DWORD & r_dwProcCRC, DWORD & r_dwFileCRC) +{ + std::string exeFileName; + LPCVOID c_pvBaseAddress; + + GetExcutedFileName(exeFileName); + + if (GetProcessInformation(exeFileName, &c_pvBaseAddress)) + r_dwProcCRC = GetProcessMemoryCRC(c_pvBaseAddress); + else + r_dwProcCRC = 0; + + r_dwFileCRC = GetFileCRC32(exeFileName.c_str()); + return true; +} + +void BuildProcessCRC() +{ + if (LocaleService_IsHONGKONG() || LocaleService_IsTAIWAN()) + { + memset(abCRCMagicCube, 0, sizeof(abCRCMagicCube)); + bMagicCubeIdx = 0; + return; + } + + DWORD dwProcCRC, dwFileCRC; + + if (__GetExeCRC(dwProcCRC, dwFileCRC)) + { + abCRCMagicCube[0] = BYTE(dwProcCRC & 0x000000ff); + abCRCMagicCube[1] = BYTE(dwFileCRC & 0x000000ff); + abCRCMagicCube[2] = BYTE( (dwProcCRC & 0x0000ff00) >> 8 ); + abCRCMagicCube[3] = BYTE( (dwFileCRC & 0x0000ff00) >> 8 ); + abCRCMagicCube[4] = BYTE( (dwProcCRC & 0x00ff0000) >> 16 ); + abCRCMagicCube[5] = BYTE( (dwFileCRC & 0x00ff0000) >> 16 ); + abCRCMagicCube[6] = BYTE( (dwProcCRC & 0xff000000) >> 24 ); + abCRCMagicCube[7] = BYTE( (dwFileCRC & 0xff000000) >> 24 ); + + bMagicCubeIdx = 0; + } +} + +BYTE GetProcessCRCMagicCubePiece() +{ + BYTE bPiece = BYTE(abCRCMagicCube[bMagicCubeIdx] ^ abCRCXorTable[bMagicCubeIdx]); + + if (!(++bMagicCubeIdx & 7)) + bMagicCubeIdx = 0; + + return bPiece; } diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index 9af7a92e..bebbf9f9 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -199,6 +199,10 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) add_test(NAME port.text_bar COMMAND $) set_tests_properties(port.text_bar PROPERTIES SKIP_RETURN_CODE 77) + add_executable(port_process_crc_test ${PROJECT_SOURCE_DIR}/tests/port/port_process_crc_test.cpp) + target_link_libraries(port_process_crc_test PRIVATE port_platform) + add_test(NAME port.process_crc COMMAND $) + # MilesLib: the 40250 sound managers over the Miles emulation (platform/MilesLib/MilesAil.cpp). add_executable(port_miles_test ${PROJECT_SOURCE_DIR}/tests/port/port_miles_test.cpp) target_link_libraries(port_miles_test PRIVATE port_platform) diff --git a/tests/port/port_process_crc_test.cpp b/tests/port/port_process_crc_test.cpp new file mode 100644 index 00000000..ef9ee846 --- /dev/null +++ b/tests/port/port_process_crc_test.cpp @@ -0,0 +1,57 @@ +#include "UserInterface/StdAfx.h" +#include "UserInterface/ProcessCRC.h" + +#include +#include +#include + +const char * stristr(const char * big, const char * little); +bool GetProcessInformation(std::string & exeFileName, LPCVOID * ppvAddress); +bool __GetExeCRC(DWORD & r_dwProcCRC, DWORD & r_dwFileCRC); + +int main() +{ + int failures = 0; + auto check = [&failures](bool ok, const char *name) { + if (!ok) { + std::fprintf(stderr, "FAIL %s\n", name); + ++failures; + } + }; + static const BYTE xorTable[8] = { 102, 30, 188, 44, 39, 201, 43, 5 }; + + // stristr (40250): case-insensitive, and it compares one character less than the needle's length. + const char * hay = "C:\\Game\\METIN2.BIN"; + check(stristr(hay, "metin2.bin") == hay + 8, "stristr case fold"); + check(stristr(hay, "metin2.biX") == hay + 8, "stristr ignores the last needle character"); + check(stristr(hay, "client.exe") == NULL, "stristr miss"); + + // Before BuildProcessCRC the cube is zero: the pieces are the XOR table, index cycling & 7. + bool pristine = true; + for (int i = 0; i < 16; ++i) + pristine &= GetProcessCRCMagicCubePiece() == xorTable[i & 7]; + check(pristine, "pieces before BuildProcessCRC"); + + // ADAPTED: no module snapshot -> ProcCRC 0; FileCRC is the CRC32 of GetExcutedFileName, as in 40250. + std::string exe; + LPCVOID base = NULL; + check(!GetProcessInformation(exe, &base), "GetProcessInformation finds no module"); + DWORD proc = 1, file = 0; + check(__GetExeCRC(proc, file) && proc == 0, "__GetExeCRC proc 0"); + GetExcutedFileName(exe); + check(file == GetFileCRC32(exe.c_str()), "__GetExeCRC file CRC"); + + // BuildProcessCRC interleaves proc/file bytes low to high and restarts the index. + GetProcessCRCMagicCubePiece(); + BuildProcessCRC(); + BYTE expect[8]; + for (int i = 0; i < 4; ++i) { + expect[2 * i] = BYTE(proc >> (8 * i)); + expect[2 * i + 1] = BYTE(file >> (8 * i)); + } + bool built = true; + for (int i = 0; i < 8; ++i) + built &= GetProcessCRCMagicCubePiece() == BYTE(expect[i] ^ xorTable[i]); + check(built, "pieces after BuildProcessCRC"); + return failures ? 1 : 0; +}