From 9ec8cb365aeac4c1219e61f0ba3bbf5bffc181a9 Mon Sep 17 00:00:00 2001 From: wowjinxy Date: Thu, 31 Jul 2025 20:57:50 -0500 Subject: [PATCH 1/4] Initialize hooks from worker thread --- digi_analysis/dllmain.cpp | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/digi_analysis/dllmain.cpp b/digi_analysis/dllmain.cpp index 1a229e4..43f0e13 100644 --- a/digi_analysis/dllmain.cpp +++ b/digi_analysis/dllmain.cpp @@ -11,6 +11,15 @@ #include "opengl_utils.h" #include "d3d8_gl_bridge.h" +// Store the module handle for potential future use. +static HINSTANCE g_hModule = nullptr; + +// Forward declaration for InstallHooks function +void InstallHooks(); + +// Forward declaration for the initialization thread. +static DWORD WINAPI InitializationThread(LPVOID); + // Apply an in‑memory patch to bypass the CD check. The patch data // and offsets were extracted from the original project. On success // this routine writes a few bytes to specific offsets relative to @@ -40,8 +49,14 @@ static void ApplyNoCD() { } } -// Forward declaration for InstallHooks function -void InstallHooks(); +// Perform initialization work once the DLL is fully loaded. +static DWORD WINAPI InitializationThread(LPVOID) { + if (MH_Initialize() == MH_OK) { + InstallHooks(); + ApplyNoCD(); + } + return 0; +} // Exported Direct3DCreate8 replacement. This function is exported from // our DLL under both the decorated and undecorated names via the linker @@ -58,18 +73,21 @@ extern "C" __declspec(dllexport) IDirect3D8* WINAPI Direct3DCreate8(UINT /*sdkVe // game's import table. #pragma comment(linker, "/export:Direct3DCreate8=_Direct3DCreate8@4") -// DLL entry point. On process attach we install hooks and apply the -// No‑CD patch. On detach we remove hooks. The original Direct3D8 -// library is no longer loaded. +// DLL entry point. On process attach we spin up a worker thread to +// perform initialization after the loader lock is released. On detach +// we remove hooks and clean up. The original Direct3D8 library is no +// longer loaded. BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: - // Initialize MinHook - if (MH_Initialize() != MH_OK) { - return FALSE; + g_hModule = hinstDLL; + DisableThreadLibraryCalls(hinstDLL); + { + HANDLE thread = CreateThread(nullptr, 0, InitializationThread, nullptr, 0, nullptr); + if (thread) { + CloseHandle(thread); + } } - InstallHooks(); - ApplyNoCD(); break; case DLL_PROCESS_DETACH: MH_DisableHook(MH_ALL_HOOKS); From f7c70c4c70f4edb6a49267b8cad188c5f712089a Mon Sep 17 00:00:00 2001 From: wowjinxy Date: Thu, 31 Jul 2025 20:59:02 -0500 Subject: [PATCH 2/4] Handle delayed game window for OpenGL init --- digi_analysis/opengl_utils.cpp | 65 ++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/digi_analysis/opengl_utils.cpp b/digi_analysis/opengl_utils.cpp index a5f70b1..d0bd38e 100644 --- a/digi_analysis/opengl_utils.cpp +++ b/digi_analysis/opengl_utils.cpp @@ -18,6 +18,7 @@ namespace { int g_width = 0; int g_height = 0; bool g_resizePending = false; + bool g_fallbackWindowCreated = false; // Draw call queues. The game thread submits into g_frameQueue. // PresentFrame() promotes the data into g_renderQueue which the @@ -131,13 +132,42 @@ bool InitOpenGL() { return true; } - g_hWndGL = FindGameWindow(); + // Try to locate the game's window first. It may not be ready when + // the device is created, so wait a bit and retry. + const int kMaxRetries = 50; + for (int i = 0; i < kMaxRetries && !g_hWndGL; ++i) { + g_hWndGL = FindGameWindow(); + if (!g_hWndGL) { + Sleep(100); + } + } + + // If the window still isn't available, create a tiny hidden fallback + // window so OpenGL can be initialised and the device creation can + // succeed. This ensures we only fail for genuine initialisation + // errors and not simply because the real window wasn't ready. if (!g_hWndGL) { - return false; + WNDCLASSA wc = {}; + wc.lpfnWndProc = DefWindowProcA; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = "DWGLFallback"; + RegisterClassA(&wc); + g_hWndGL = CreateWindowExA(0, wc.lpszClassName, "DWGL", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 1, 1, + nullptr, nullptr, wc.hInstance, nullptr); + if (!g_hWndGL) { + return false; + } + g_fallbackWindowCreated = true; + ShowWindow(g_hWndGL, SW_HIDE); } g_hDCGL = GetDC(g_hWndGL); if (!g_hDCGL) { + if (g_fallbackWindowCreated) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } g_hWndGL = nullptr; return false; } @@ -155,23 +185,35 @@ bool InitOpenGL() { int pf = ChoosePixelFormat(g_hDCGL, &pfd); if (pf == 0) { ReleaseDC(g_hWndGL, g_hDCGL); - g_hWndGL = nullptr; g_hDCGL = nullptr; + if (g_fallbackWindowCreated) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } + g_hWndGL = nullptr; return false; } if (!SetPixelFormat(g_hDCGL, pf, &pfd)) { ReleaseDC(g_hWndGL, g_hDCGL); - g_hWndGL = nullptr; g_hDCGL = nullptr; + if (g_fallbackWindowCreated) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } + g_hWndGL = nullptr; return false; } g_hGLRC = wglCreateContext(g_hDCGL); if (!g_hGLRC) { ReleaseDC(g_hWndGL, g_hDCGL); - g_hWndGL = nullptr; g_hDCGL = nullptr; + if (g_fallbackWindowCreated) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } + g_hWndGL = nullptr; return false; } @@ -188,11 +230,15 @@ bool InitOpenGL() { if (!g_renderThread) { g_running = false; wglDeleteContext(g_hGLRC); + g_hGLRC = nullptr; ReleaseDC(g_hWndGL, g_hDCGL); + g_hDCGL = nullptr; SetWindowLongPtrA(g_hWndGL, GWLP_WNDPROC, (LONG_PTR)g_originalWndProc); g_originalWndProc = nullptr; - g_hGLRC = nullptr; - g_hDCGL = nullptr; + if (g_fallbackWindowCreated) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } g_hWndGL = nullptr; return false; } @@ -227,6 +273,11 @@ void ShutdownOpenGL() { g_hDCGL = nullptr; } + if (g_fallbackWindowCreated && g_hWndGL) { + DestroyWindow(g_hWndGL); + g_fallbackWindowCreated = false; + } + g_hWndGL = nullptr; g_OpenGLWindowCreated = false; } From 1b805979ab359ae14cb1eff80ff13d4d156dd262 Mon Sep 17 00:00:00 2001 From: wowjinxy Date: Thu, 31 Jul 2025 21:00:17 -0500 Subject: [PATCH 3/4] Use module base for hooks and patches --- digi_analysis/dllmain.cpp | 8 +++-- digi_analysis/hooks.cpp | 67 +++++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/digi_analysis/dllmain.cpp b/digi_analysis/dllmain.cpp index 1a229e4..efa9d00 100644 --- a/digi_analysis/dllmain.cpp +++ b/digi_analysis/dllmain.cpp @@ -8,6 +8,7 @@ #include "MinHook.h" #include #include +#include #include "opengl_utils.h" #include "d3d8_gl_bridge.h" @@ -17,7 +18,7 @@ // the module base to disable the CD verification. If the patch // cannot be applied nothing else is done. static void ApplyNoCD() { - uintptr_t baseAddress = reinterpret_cast(GetModuleHandleA(NULL)); + uintptr_t baseAddress = reinterpret_cast(GetModuleHandle(nullptr)); struct Patch { uintptr_t offset; unsigned char data[8]; @@ -30,12 +31,13 @@ static void ApplyNoCD() { }; for (const auto& patch : patches) { void* address = reinterpret_cast(baseAddress + patch.offset); + char buf[64]; + std::snprintf(buf, sizeof(buf), "NoCD patch @ %p\n", address); + OutputDebugStringA(buf); DWORD oldProtect; if (VirtualProtect(address, patch.size, PAGE_EXECUTE_READWRITE, &oldProtect)) { std::memcpy(address, patch.data, patch.size); VirtualProtect(address, patch.size, oldProtect, &oldProtect); - // Optionally log the patch application via debug output. - // OutputDebugStringA("Applied NoCD patch\n"); } } } diff --git a/digi_analysis/hooks.cpp b/digi_analysis/hooks.cpp index c48a60d..b429486 100644 --- a/digi_analysis/hooks.cpp +++ b/digi_analysis/hooks.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "functions.h" #include "text_renderer.h" @@ -231,17 +232,33 @@ void InstallHooks() { if (status != MH_OK && status != MH_ERROR_ALREADY_INITIALIZED) { return; } + uintptr_t base = reinterpret_cast(GetModuleHandle(nullptr)); + auto logAddr = [](const char* name, uintptr_t addr) { + char buf[64]; + std::snprintf(buf, sizeof(buf), "%s -> %p\n", name, reinterpret_cast(addr)); + OutputDebugStringA(buf); + }; // Create a hook for 0x401000. On success the original pointer // will be stored in s_orig401000. - MH_CreateHook(reinterpret_cast(0x401000), reinterpret_cast(&Detour401000), reinterpret_cast(&s_orig401000)); + uintptr_t addr401000 = base + 0x001000; + logAddr("0x401000", addr401000); + MH_CreateHook(reinterpret_cast(addr401000), reinterpret_cast(&Detour401000), reinterpret_cast(&s_orig401000)); // Create a hook for 0x401020 as well. This allows you to intercept // calls to the second sine lookup routine. - MH_CreateHook(reinterpret_cast(0x401020), reinterpret_cast(&Detour401020), reinterpret_cast(&s_orig401020)); + uintptr_t addr401020 = base + 0x001020; + logAddr("0x401020", addr401020); + MH_CreateHook(reinterpret_cast(addr401020), reinterpret_cast(&Detour401020), reinterpret_cast(&s_orig401020)); // Create hooks for the simple wrapper functions at 0x401040 and 0x401050. - MH_CreateHook(reinterpret_cast(0x401040), reinterpret_cast(&Detour401040), reinterpret_cast(&s_orig401040)); - MH_CreateHook(reinterpret_cast(0x401050), reinterpret_cast(&Detour401050), reinterpret_cast(&s_orig401050)); + uintptr_t addr401040 = base + 0x001040; + logAddr("0x401040", addr401040); + MH_CreateHook(reinterpret_cast(addr401040), reinterpret_cast(&Detour401040), reinterpret_cast(&s_orig401040)); + uintptr_t addr401050 = base + 0x001050; + logAddr("0x401050", addr401050); + MH_CreateHook(reinterpret_cast(addr401050), reinterpret_cast(&Detour401050), reinterpret_cast(&s_orig401050)); // Create a hook for the new function at 0x004A1F8A. - MH_CreateHook(reinterpret_cast(0x004A1F8A), reinterpret_cast(&Detour004A1F8A), reinterpret_cast(&s_orig004A1F8A)); + uintptr_t addr004A1F8A = base + 0x00A1F8A; + logAddr("0x004A1F8A", addr004A1F8A); + MH_CreateHook(reinterpret_cast(addr004A1F8A), reinterpret_cast(&Detour004A1F8A), reinterpret_cast(&s_orig004A1F8A)); // --------------------------------------------------------------------- // Install hooks for text and font processing functions. These // addresses are taken from the disassembly provided by the user. Each @@ -249,24 +266,46 @@ void InstallHooks() { // through the s_origXXXX function pointers defined above. Once you // understand the behaviour of these routines you can replace the // calls to the originals with custom code. - MH_CreateHook(reinterpret_cast(0x00428EE0), reinterpret_cast(&Detour00428EE0), reinterpret_cast(&s_orig00428EE0)); - MH_CreateHook(reinterpret_cast(0x00495E1A), reinterpret_cast(&Detour00495E1A), reinterpret_cast(&s_orig00495E1A)); - MH_CreateHook(reinterpret_cast(0x00495F5B), reinterpret_cast(&Detour00495F5B), reinterpret_cast(&s_orig00495F5B)); - MH_CreateHook(reinterpret_cast(0x0040EA40), reinterpret_cast(&Detour0040EA40), reinterpret_cast(&s_orig0040EA40)); - MH_CreateHook(reinterpret_cast(0x00492EFF), reinterpret_cast(&Detour00492EFF), reinterpret_cast(&s_orig00492EFF)); - MH_CreateHook(reinterpret_cast(0x00495AE4), reinterpret_cast(&Detour00495AE4), reinterpret_cast(&s_orig00495AE4)); + uintptr_t addr00428EE0 = base + 0x0028EE0; + logAddr("0x00428EE0", addr00428EE0); + MH_CreateHook(reinterpret_cast(addr00428EE0), reinterpret_cast(&Detour00428EE0), reinterpret_cast(&s_orig00428EE0)); + uintptr_t addr00495E1A = base + 0x0095E1A; + logAddr("0x00495E1A", addr00495E1A); + MH_CreateHook(reinterpret_cast(addr00495E1A), reinterpret_cast(&Detour00495E1A), reinterpret_cast(&s_orig00495E1A)); + uintptr_t addr00495F5B = base + 0x0095F5B; + logAddr("0x00495F5B", addr00495F5B); + MH_CreateHook(reinterpret_cast(addr00495F5B), reinterpret_cast(&Detour00495F5B), reinterpret_cast(&s_orig00495F5B)); + uintptr_t addr0040EA40 = base + 0x000EA40; + logAddr("0x0040EA40", addr0040EA40); + MH_CreateHook(reinterpret_cast(addr0040EA40), reinterpret_cast(&Detour0040EA40), reinterpret_cast(&s_orig0040EA40)); + uintptr_t addr00492EFF = base + 0x0092EFF; + logAddr("0x00492EFF", addr00492EFF); + MH_CreateHook(reinterpret_cast(addr00492EFF), reinterpret_cast(&Detour00492EFF), reinterpret_cast(&s_orig00492EFF)); + uintptr_t addr00495AE4 = base + 0x0095AE4; + logAddr("0x00495AE4", addr00495AE4); + MH_CreateHook(reinterpret_cast(addr00495AE4), reinterpret_cast(&Detour00495AE4), reinterpret_cast(&s_orig00495AE4)); // Hook RestoreSelectedGDIObject at 0x00495DEB. - MH_CreateHook(reinterpret_cast(0x00495DEB), reinterpret_cast(&Detour00495DEB), reinterpret_cast(&s_orig00495DEB)); + uintptr_t addr00495DEB = base + 0x0095DEB; + logAddr("0x00495DEB", addr00495DEB); + MH_CreateHook(reinterpret_cast(addr00495DEB), reinterpret_cast(&Detour00495DEB), reinterpret_cast(&s_orig00495DEB)); // Hook ExtractAndProcessFontMetrics at 0x00486730. - MH_CreateHook(reinterpret_cast(0x00486730), reinterpret_cast(&Detour00486730), reinterpret_cast(&s_orig00486730)); + uintptr_t addr00486730 = base + 0x0086730; + logAddr("0x00486730", addr00486730); + MH_CreateHook(reinterpret_cast(addr00486730), reinterpret_cast(&Detour00486730), reinterpret_cast(&s_orig00486730)); // Hook MeasureStringDimensions at 0x00429170. - MH_CreateHook(reinterpret_cast(0x00429170), reinterpret_cast(&Detour00429170), reinterpret_cast(&s_orig00429170)); + uintptr_t addr00429170 = base + 0x0029170; + logAddr("0x00429170", addr00429170); + MH_CreateHook(reinterpret_cast(addr00429170), reinterpret_cast(&Detour00429170), reinterpret_cast(&s_orig00429170)); // Install hooks for GDI functions. These hooks allow us to // intercept calls to CreateCompatibleDC, CreateFontA, etc. and // replace them with OpenGL-aware implementations. The function // is defined in gdi_hooks.cpp. extern void InstallGDIHooks(); InstallGDIHooks(); + if (IsDebuggerPresent()) { + OutputDebugStringA("Verify hook addresses and resume to enable hooks\n"); + DebugBreak(); + } // Enable all hooks at once. If needed you can enable/disable // individual hooks by passing the target address instead of // MH_ALL_HOOKS. From f7eab18cd0a748691b0f3f68ad0eef1dffb1c809 Mon Sep 17 00:00:00 2001 From: wowjinxy Date: Thu, 31 Jul 2025 21:22:56 -0500 Subject: [PATCH 4/4] Add console and file debug logging --- digi_analysis/debug_log.cpp | 54 +++++++++++++++++++++ digi_analysis/debug_log.h | 14 ++++++ digi_analysis/digi_analysis.vcxproj | 2 + digi_analysis/digi_analysis.vcxproj.filters | 6 +++ digi_analysis/dllmain.cpp | 7 ++- digi_analysis/gdi_hooks.cpp | 6 +-- digi_analysis/hooks.cpp | 23 ++++----- digi_analysis/main.cpp | 3 ++ digi_analysis/sub_004A1F8A.cpp | 24 ++++----- 9 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 digi_analysis/debug_log.cpp create mode 100644 digi_analysis/debug_log.h diff --git a/digi_analysis/debug_log.cpp b/digi_analysis/debug_log.cpp new file mode 100644 index 0000000..594701d --- /dev/null +++ b/digi_analysis/debug_log.cpp @@ -0,0 +1,54 @@ +#include "debug_log.h" + +#include +#include +#include +#include + +static std::ofstream g_logFile; +static bool g_consoleAllocated = false; + +void InitDebugLog() { + if (!g_consoleAllocated) { + AllocConsole(); +#ifdef _MSC_VER + FILE* out; + freopen_s(&out, "CONOUT$", "w", stdout); + freopen_s(&out, "CONOUT$", "w", stderr); +#else + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +#endif + g_consoleAllocated = true; + } + if (!g_logFile.is_open()) { + g_logFile.open("debug_log.txt", std::ios::out | std::ios::app); + } +} + +void DebugLog(const char* fmt, ...) { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + buf[sizeof(buf) - 1] = '\0'; + OutputDebugStringA(buf); + fputs(buf, stdout); + fflush(stdout); + if (g_logFile.is_open()) { + g_logFile << buf; + g_logFile.flush(); + } +} + +void ShutdownDebugLog() { + if (g_logFile.is_open()) { + g_logFile.close(); + } + if (g_consoleAllocated) { + FreeConsole(); + g_consoleAllocated = false; + } +} + diff --git a/digi_analysis/debug_log.h b/digi_analysis/debug_log.h new file mode 100644 index 0000000..69dda65 --- /dev/null +++ b/digi_analysis/debug_log.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +// Provide C linkage so the functions can be called from assembly. +extern "C" { +// Initialize logging by allocating a console window and opening a log file. +void InitDebugLog(); +// Log a formatted message to debugger output, console and log file. +void DebugLog(const char* fmt, ...); +// Shut down logging and release resources. +void ShutdownDebugLog(); +} + diff --git a/digi_analysis/digi_analysis.vcxproj b/digi_analysis/digi_analysis.vcxproj index bca636c..f8d9b61 100644 --- a/digi_analysis/digi_analysis.vcxproj +++ b/digi_analysis/digi_analysis.vcxproj @@ -87,6 +87,7 @@ + @@ -106,6 +107,7 @@ + diff --git a/digi_analysis/digi_analysis.vcxproj.filters b/digi_analysis/digi_analysis.vcxproj.filters index 989ce6c..33411d2 100644 --- a/digi_analysis/digi_analysis.vcxproj.filters +++ b/digi_analysis/digi_analysis.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + Source Files @@ -86,6 +89,9 @@ Header Files + + Header Files + diff --git a/digi_analysis/dllmain.cpp b/digi_analysis/dllmain.cpp index d315776..2b04a7d 100644 --- a/digi_analysis/dllmain.cpp +++ b/digi_analysis/dllmain.cpp @@ -11,6 +11,7 @@ #include #include "opengl_utils.h" #include "d3d8_gl_bridge.h" +#include "debug_log.h" // Store the module handle for potential future use. static HINSTANCE g_hModule = nullptr; @@ -42,7 +43,7 @@ static void ApplyNoCD() { void* address = reinterpret_cast(baseAddress + patch.offset); char buf[64]; std::snprintf(buf, sizeof(buf), "NoCD patch @ %p\n", address); - OutputDebugStringA(buf); + DebugLog("%s", buf); DWORD oldProtect; if (VirtualProtect(address, patch.size, PAGE_EXECUTE_READWRITE, &oldProtect)) { std::memcpy(address, patch.data, patch.size); @@ -53,6 +54,7 @@ static void ApplyNoCD() { // Perform initialization work once the DLL is fully loaded. static DWORD WINAPI InitializationThread(LPVOID) { + InitDebugLog(); if (MH_Initialize() == MH_OK) { InstallHooks(); ApplyNoCD(); @@ -65,7 +67,7 @@ static DWORD WINAPI InitializationThread(LPVOID) { // directive above. It simply allocates the bridge object and returns it // to the caller. extern "C" __declspec(dllexport) IDirect3D8* WINAPI Direct3DCreate8(UINT /*sdkVersion*/) { - OutputDebugStringA("Direct3DCreate8 called – OpenGL backend active\n"); + DebugLog("Direct3DCreate8 called – OpenGL backend active\n"); return new IDirect3D8(); } @@ -95,6 +97,7 @@ BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { MH_DisableHook(MH_ALL_HOOKS); MH_Uninitialize(); ShutdownOpenGL(); + ShutdownDebugLog(); break; } return TRUE; diff --git a/digi_analysis/gdi_hooks.cpp b/digi_analysis/gdi_hooks.cpp index 8b6f55c..817952d 100644 --- a/digi_analysis/gdi_hooks.cpp +++ b/digi_analysis/gdi_hooks.cpp @@ -14,6 +14,7 @@ #include #include "text_renderer.h" +#include "debug_log.h" // Forward declarations of the original functions. We store these in // static variables so that the detours can call through after doing @@ -52,12 +53,11 @@ static PFN_GetTextMetricsA orig_GetTextMetricsA = nullptr; static PFN_SetTextColor orig_SetTextColor = nullptr; static PFN_TextOutA orig_TextOutA = nullptr; -// Helper to log a message to the debugger. This avoids duplicating -// the OutputDebugStringA call in every detour. +// Helper to log a message. This avoids duplicating the DebugLog call in every detour. static void LogCall(const char* funcName) { char buf[128]; std::snprintf(buf, sizeof(buf), "[GDI hook] %s called\n", funcName); - OutputDebugStringA(buf); + DebugLog("%s", buf); } struct DCState { diff --git a/digi_analysis/hooks.cpp b/digi_analysis/hooks.cpp index b429486..757e054 100644 --- a/digi_analysis/hooks.cpp +++ b/digi_analysis/hooks.cpp @@ -13,6 +13,7 @@ #include #include "functions.h" #include "text_renderer.h" +#include "debug_log.h" // Forward declaration of our stub for 0x004A1F8A. Defined in // sub_004A1F8A.cpp. The calling convention is __stdcall to match @@ -116,20 +117,20 @@ static OrigFunc00429170 s_orig00429170 = nullptr; // behaviour to OpenGL. For now we leave the behaviour unchanged. static void __cdecl Detour00428EE0(int* fontMetricsArray) { - OutputDebugStringA("Detour00428EE0: ProcessFontsAndMetrics called\n"); + DebugLog("Detour00428EE0: ProcessFontsAndMetrics called\n"); if (s_orig00428EE0) { s_orig00428EE0(fontMetricsArray); } } static int __fastcall Detour00495E1A(void* _this, void* /*not used*/, int* param1) { - OutputDebugStringA("Detour00495E1A: CreateTextRenderSurface called\n"); + DebugLog("Detour00495E1A: CreateTextRenderSurface called\n"); return s_orig00495E1A ? s_orig00495E1A(_this, param1) : -1; } static unsigned int* __fastcall Detour00495F5B(void* _this, void* /*not used*/, unsigned int* a1, unsigned int* a2, unsigned int a3, int* a4, UINT a5, unsigned int a6, const wchar_t* a7) { - OutputDebugStringA("Detour00495F5B: RenderText called\n"); + DebugLog("Detour00495F5B: RenderText called\n"); if (!a7) { return a1; } @@ -143,34 +144,34 @@ static unsigned int* __fastcall Detour00495F5B(void* _this, void* /*not used*/, } static int __fastcall Detour0040EA40(void* _this, void* /*not used*/, int param) { - OutputDebugStringA("Detour0040EA40: CDWWnd::PreCreateWindow called\n"); + DebugLog("Detour0040EA40: CDWWnd::PreCreateWindow called\n"); return s_orig0040EA40 ? s_orig0040EA40(_this, param) : 0; } static int __fastcall Detour00492EFF(void* _this, void* /*not used*/, int* param1, LOGFONTA* param2) { - OutputDebugStringA("Detour00492EFF: TextRenderState::Initialize called\n"); + DebugLog("Detour00492EFF: TextRenderState::Initialize called\n"); return s_orig00492EFF ? s_orig00492EFF(_this, param1, param2) : -1; } static void __fastcall Detour00495AE4(int param1, void* unused) { - OutputDebugStringA("Detour00495AE4: FreeTextSurfaceResources called\n"); + DebugLog("Detour00495AE4: FreeTextSurfaceResources called\n"); if (s_orig00495AE4) { s_orig00495AE4(param1, unused); } } static int __fastcall Detour00495DEB(int param1, void* unused) { - OutputDebugStringA("Detour00495DEB: RestoreSelectedGDIObject called\n"); + DebugLog("Detour00495DEB: RestoreSelectedGDIObject called\n"); return s_orig00495DEB ? s_orig00495DEB(param1, unused) : 0; } static int Detour00486730(int* fontMetricsArray, HANDLE currentHandle, void** fontMetricsArrayPtr) { - OutputDebugStringA("Detour00486730: ExtractAndProcessFontMetrics called\n"); + DebugLog("Detour00486730: ExtractAndProcessFontMetrics called\n"); return s_orig00486730 ? s_orig00486730(fontMetricsArray, currentHandle, fontMetricsArrayPtr) : -1; } static void __fastcall Detour00429170(void* _this, void* /*not used*/, unsigned int param1, int param2, unsigned int param3) { - OutputDebugStringA("Detour00429170: MeasureStringDimensions called\n"); + DebugLog("Detour00429170: MeasureStringDimensions called\n"); if (s_orig00429170) { s_orig00429170(_this, param1, param2, param3); } @@ -236,7 +237,7 @@ void InstallHooks() { auto logAddr = [](const char* name, uintptr_t addr) { char buf[64]; std::snprintf(buf, sizeof(buf), "%s -> %p\n", name, reinterpret_cast(addr)); - OutputDebugStringA(buf); + DebugLog("%s", buf); }; // Create a hook for 0x401000. On success the original pointer // will be stored in s_orig401000. @@ -303,7 +304,7 @@ void InstallHooks() { extern void InstallGDIHooks(); InstallGDIHooks(); if (IsDebuggerPresent()) { - OutputDebugStringA("Verify hook addresses and resume to enable hooks\n"); + DebugLog("Verify hook addresses and resume to enable hooks\n"); DebugBreak(); } // Enable all hooks at once. If needed you can enable/disable diff --git a/digi_analysis/main.cpp b/digi_analysis/main.cpp index f0febd1..9463a40 100644 --- a/digi_analysis/main.cpp +++ b/digi_analysis/main.cpp @@ -19,6 +19,7 @@ #include "strings.h" #include "functions.h" #include "hooks.h" +#include "debug_log.h" // Forward declarations for the reconstructed functions. These // declarations match those in functions.h but are repeated here to @@ -42,6 +43,7 @@ static std::wstring ToWString(const std::string &s) { // The WinMain entry point typical of Win32 GUI applications. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + InitDebugLog(); // Install our hooks before doing anything else. In a DLL build // this would typically be done from DllMain on process attach. InstallHooks(); @@ -76,5 +78,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, std::wstring wmsg = ToWString(message); MessageBoxW(NULL, wmsg.c_str(), L"Digi EXE Reconstruction Demo", MB_OK); + ShutdownDebugLog(); return 0; } \ No newline at end of file diff --git a/digi_analysis/sub_004A1F8A.cpp b/digi_analysis/sub_004A1F8A.cpp index 36b4d89..f7b2bc6 100644 --- a/digi_analysis/sub_004A1F8A.cpp +++ b/digi_analysis/sub_004A1F8A.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "debug_log.h" // Simulated global variables corresponding to locations in the // original binary. The addresses 0x4F4C78 and 0x4F4C7C appear to be @@ -58,10 +59,10 @@ namespace { } // Inline‑assembly implementation of CallStubA. It ignores the -// parameter value and simply sends a fixed string to the debugger via -// OutputDebugStringA. The function cleans up the single integer -// argument on return. When you know the real behaviour at 0x4A7324 -// you can replace this with an accurate re‑implementation. +// parameter value and simply sends a fixed string to the shared logger. +// The function cleans up the single integer argument on return. When +// you know the real behaviour at 0x4A7324 you can replace this with an +// accurate re‑implementation. extern "C" __declspec(naked) void __stdcall CallStubA(int /*param*/) { __asm { // Establish a standard stack frame for clarity. This is not @@ -70,10 +71,10 @@ extern "C" __declspec(naked) void __stdcall CallStubA(int /*param*/) { push ebp mov ebp, esp // Push the address of our message onto the stack and call - // OutputDebugStringA. Because OutputDebugStringA uses the - // stdcall convention the callee will pop its own argument. + // Log the message using the shared DebugLog helper. push offset kMsgStubA - call OutputDebugStringA + call DebugLog + add esp, 4 // Clean up the stack frame and return, removing the one // integer argument passed to this function (4 bytes). mov esp, ebp @@ -91,7 +92,8 @@ extern "C" __declspec(naked) void __stdcall CallStubB(void) { push ebp mov ebp, esp push offset kMsgStubB - call OutputDebugStringA + call DebugLog + add esp, 4 mov esp, ebp pop ebp ret @@ -129,18 +131,18 @@ static void __stdcall internal_sub_004A1F8A() { std::snprintf(buf, sizeof(buf), "sub_004A1F8A: lookup results = %d, %d\n", result1, result2); - OutputDebugStringA(buf); + DebugLog("%s", buf); // Call our inline‑assembly stubs. The original function // pushes 2 before calling the first routine. We preserve // that behaviour here. CallStubA(2); CallStubB(); - OutputDebugStringA("sub_004A1F8A: initialisation complete\n"); + DebugLog("sub_004A1F8A: initialisation complete\n"); } else { // Subsequent calls simply log that the routine has already // executed. This mirrors the fact that the real code does // nothing on subsequent invocations once its state is set up. - OutputDebugStringA("sub_004A1F8A: already initialised\n"); + DebugLog("sub_004A1F8A: already initialised\n"); } }