From e72f996619b359ca0ec3eb15dde740949b54e17b Mon Sep 17 00:00:00 2001 From: TheRedDaemon <66257843+TheRedDaemon@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:14:24 +0100 Subject: [PATCH 1/2] [MACRO_APPROACH] test function if-else with macro focus --- cmake/core-sources.txt | 2 + src/core/MacroApproachTest.cpp | 104 ++++++++++++++++++++++++++++ src/core/MacroApproachTest.h | 86 +++++++++++++++++++++++ src/core/MainImplementation.cpp | 5 +- src/core/ViewportRenderState.2.cpp | 50 +++++++++++++ src/core/ViewportRenderState.cpp | 3 - src/core/ViewportRenderState.func.h | 4 -- 7 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 src/core/MacroApproachTest.cpp create mode 100644 src/core/MacroApproachTest.h create mode 100644 src/core/ViewportRenderState.2.cpp diff --git a/cmake/core-sources.txt b/cmake/core-sources.txt index e9f61df..a0e4b2c 100644 --- a/cmake/core-sources.txt +++ b/cmake/core-sources.txt @@ -1,5 +1,7 @@ +src/core/MacroApproachTest.cpp src/core/MainImplementation.cpp src/core/Resolver.cpp +src/core/ViewportRenderState.2.cpp src/core/ViewportRenderState.cpp src/core/entry.cpp src/core/windowslib.cpp diff --git a/src/core/MacroApproachTest.cpp b/src/core/MacroApproachTest.cpp new file mode 100644 index 0000000..12fc246 --- /dev/null +++ b/src/core/MacroApproachTest.cpp @@ -0,0 +1,104 @@ + +#include "ucp3.h" + +#include "MacroApproachTest.h" + +#include + +// TODO?: When in doubt regarding the transition to full exe: Add other logging system. + +#ifdef OPEN_SHC_DLL + +static void createRelativeJump(int from, int to) +{ + const int instructionLength = 5; + const int relativeOffset = to - (from + instructionLength); + + DWORD oldProtection; + if (!VirtualProtect((void*)from, instructionLength, PAGE_EXECUTE_READWRITE, &oldProtection)) { + std::ostringstream oss; + oss << "Error while trying to remove memory protection to create relative jump from '" << from << "' to '" << to + << "': " << GetLastError(); + ucp_log(Verbosity_FATAL, oss.str().c_str()); + } + + // create relative jmp + *((char*)from) = (char)0xe9; + *((int*)(from + 1)) = relativeOffset; + + DWORD dummyProtection; + if (!VirtualProtect((void*)from, instructionLength, oldProtection, &dummyProtection)) { + std::ostringstream oss; + oss << "Error while trying to re-enable memory protection after creating relative jump from '" << from + << "' to '" << to << "': " << GetLastError(); + ucp_log(Verbosity_FATAL, oss.str().c_str()); + } +} + +#endif + +void Initializer::initializeStruct( + bool& initialized, bool isImplemented, int gameAddress, const void* funcPtr, const char* funcName) +{ + if (initialized) { +#ifdef OPEN_SHC_EXE + abort(); // crash silently; if we switch to exe, we should already be sure to never trigger this +#endif + +#ifdef OPEN_SHC_DLL + std::ostringstream oss; + oss << "Abort execution, since address '" << gameAddress << "' was resolved more then once."; + ucp_log(Verbosity_FATAL, oss.str().c_str()); +#endif + return; + } + initialized = true; + +#ifdef OPEN_SHC_DLL + if (ucp_logLevel() < Verbosity_1) { + return; + } + std::ostringstream oss; + if (isImplemented) { + oss << "Implemented '" << (void*)gameAddress << "' at address '" << funcPtr << "' as a '" << funcName; + } else { + oss << "Use '" << (void*)gameAddress << "' as a '" << funcName; + } + ucp_log(Verbosity_1, oss.str().c_str()); +#endif +} + +void Initializer::initializeFunction( + bool& initialized, bool isImplemented, int gameAddress, const void* funcPtr, const char* funcName) +{ + if (initialized) { +#ifdef OPEN_SHC_EXE + abort(); // crash silently; if we switch to exe, we should already be sure to never trigger this +#endif + +#ifdef OPEN_SHC_DLL + std::ostringstream oss; + oss << "Abort execution, since address '" << gameAddress << "' was resolved more then once."; + ucp_log(Verbosity_FATAL, oss.str().c_str()); +#endif + return; + } + initialized = true; + +#ifdef OPEN_SHC_DLL + if (isImplemented) { + createRelativeJump(gameAddress, (int)funcPtr); + } + + if (ucp_logLevel() < Verbosity_1) { + return; + } + std::ostringstream oss; + if (isImplemented) { + oss << "Implemented '" << (void*)gameAddress << "' at address '" << funcPtr << "' as '" << funcName; + } else { + oss << "Use '" << (void*)gameAddress << "' as '" << funcName; + } + ucp_log(Verbosity_1, oss.str().c_str()); +#endif +} diff --git a/src/core/MacroApproachTest.h b/src/core/MacroApproachTest.h new file mode 100644 index 0000000..758770a --- /dev/null +++ b/src/core/MacroApproachTest.h @@ -0,0 +1,86 @@ +#pragma once + +#define TRUE 1 +#define FALSE 0 + +#ifdef OPEN_SHC_DLL +#define CHECK_IMPLEMENTED(IMPLEMENTED) IMPLEMENTED +#endif + +// would be always "TRUE" in real scenario +#ifdef OPEN_SHC_EXE +#define CHECK_IMPLEMENTED(IMPLEMENTED) TRUE +#endif + +// Enums can only be used, if they are simple values, so the namespace needs to be removed or not even used (addresses +// as simple macros in global namespace) +// using a body macro might be trick with inline assembly macros, had weird errors during tests +#define JMP_TO_GAME(DECLARATION, ADDR) \ + __declspec(naked) DECLARATION { __asm mov eax, ADDR __asm jmp eax } + +namespace Address { +enum { + F_00401040 = 0x00401040, +}; +} + +// apparently, another way to force init code is adding function ptrs to .CRT$XCU +// #pragma section(".CRT$XCU", read) +//__declspec(allocate(".CRT$XCU")) void(*pInit)() = myInitFunction; + +struct Initializer { +private: + template struct AddressUsageKeeper { + static bool initialized; + }; + + static void initializeStruct( + bool& initialized, bool isImplemented, int gameAddress, const void* structPtr, const char* structName); + + static void initializeFunction( + bool& initialized, bool isImplemented, int gameAddress, const void* funcPtr, const char* funcName); + +public: + template struct StructInitializer { + private: + struct Init { + Init(); + }; + static const Init init; + }; + + template + struct FunctionInitializer { + private: + struct Init { + Init(); + }; + static const Init init; + }; +}; + +template bool Initializer::AddressUsageKeeper
::initialized = false; + +template +Initializer::StructInitializer::Init::Init() +{ + initializeStruct( + AddressUsageKeeper::initialized, implemented, gameAddress, structAddress, getTypeName()); +} + +template +const typename Initializer::StructInitializer::Init + Initializer::StructInitializer::init; + +template +Initializer::FunctionInitializer::Init::Init() +{ + const FuncPtrType funcPtr = funcAddress; + const void* func = *((void**)&funcPtr); + initializeFunction(AddressUsageKeeper::initialized, implemented, gameAddress, func, + getFuncPtrName()); +} + +template +const typename Initializer::FunctionInitializer::Init + Initializer::FunctionInitializer::init; diff --git a/src/core/MainImplementation.cpp b/src/core/MainImplementation.cpp index 8f74307..a5e1963 100644 --- a/src/core/MainImplementation.cpp +++ b/src/core/MainImplementation.cpp @@ -31,10 +31,7 @@ int WINAPI Main::WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCm // in a anonymous namespace might help BOOL result = MACRO_CALL_MEMBER(ViewportRenderState_Func::xyAreValid, ViewportRenderState_Struct::ptr)(100, 100); std::cout << "The answer is: " << result << " " << sizeof(ViewportRenderState) << std::endl; - std::cout << "The answer is: " - << MACRO_CALL_MEMBER(ViewportRenderState_Func::translateXYToTile, ViewportRenderState_Struct::ptr)( - 200, 150) - << std::endl; + std::cout << "The answer is: " << ViewportRenderState_Struct::ptr->translateXYToTile(200, 150) << std::endl; std::cout << "The answer is: " << MACRO_CALL_MEMBER(ViewportRenderState_Func::meth_0x4092e0, ViewportRenderState_Struct::ptr)(200, 150) << std::endl; diff --git a/src/core/ViewportRenderState.2.cpp b/src/core/ViewportRenderState.2.cpp new file mode 100644 index 0000000..8c4f1e4 --- /dev/null +++ b/src/core/ViewportRenderState.2.cpp @@ -0,0 +1,50 @@ +#include "MacroApproachTest.h" + +#include "ViewportRenderState.h" + +// would need to be defined in every function case +// here, if causes no issues, and can even be undefined at the end +// it could even be left out completely, and the "safety" check and hooking via macro could be hardcoded at both +// conditions BUT issue: +// Structs need this switch in the header, but also in the implementation files, which would mean setting the state +// twice, if not moved to a central position, where the naming could be an issue again (a central file would trigger +// recomp for everything, so that would be bad) +#define IMPLEMENTED CHECK_IMPLEMENTED(FALSE) + +// using macro to run init, requires also complete definition of type, otherwise we can not pass the function pointer +template struct Initializer::FunctionInitializer; + +#if IMPLEMENTED + +// FUNCTION: STRONGHOLDCRUSADER 0x00401040 +int ViewportRenderState::translateXYToTile(int x, int y) { return this->translationMatrix[y].addXgetTile + x; } + +#else // !IMPLEMENTED + +using namespace Address; +JMP_TO_GAME(int ViewportRenderState::translateXYToTile(int x, int y), F_00401040) + +// of course, if generated anyway a helper macro could be omitted, but "using" the address namespace would be needed +// anyway + +// would convert to a jmp, but would require to define the ptr type for all functions and consider the thiscall != +// memberfunc difference +// int ViewportRenderState::translateXYToTile(int x, int y) +//{ +// return ((int(__thiscall*)(ViewportRenderState*, int, int))Address::F_00401040)(this, x, y); +//} + +#endif + +#undef IMPLEMENTED + +// even if everything is stripped out and the init done by an explicit inti function, we would have issues with the +// structs needing a definition for implemented and none for not in both header and implementation file +// in fact, the structs would not really need a definition at all if the struct is not implemented +// There could be a case that works on file level, but that runs into the issue with the structs once again, since they +// would still require the separation in the header, which would mean still two positions + +// another way could be to simple generate a macro for the structs, or even for the functions +// the functions could have it here, but the structs would need it in their headers, essentially +// _IMPLEMENTED and the the bool indicator diff --git a/src/core/ViewportRenderState.cpp b/src/core/ViewportRenderState.cpp index 20146bf..f59a3f9 100644 --- a/src/core/ViewportRenderState.cpp +++ b/src/core/ViewportRenderState.cpp @@ -67,9 +67,6 @@ BOOL ViewportRenderState::xyAreValid(uint x, uint y) return 0; } -// FUNCTION: STRONGHOLDCRUSADER 0x00401040 -int ViewportRenderState::translateXYToTile(int x, int y) { return this->translationMatrix[y].addXgetTile + x; } - // FUNCTION: STRONGHOLDCRUSADER 0x004092e0 int ViewportRenderState::meth_0x4092e0(int param_1, int param_2) { diff --git a/src/core/ViewportRenderState.func.h b/src/core/ViewportRenderState.func.h index 888bd95..8e53caa 100644 --- a/src/core/ViewportRenderState.func.h +++ b/src/core/ViewportRenderState.func.h @@ -11,10 +11,6 @@ _constructor_; MACRO_FUNCTION_RESOLVER(BOOL (ViewportRenderState::*)(uint, uint), true, 0x00401000, &ViewportRenderState::xyAreValid) xyAreValid; -MACRO_FUNCTION_RESOLVER( - int (ViewportRenderState::*)(int, int), false, 0x00401040, &ViewportRenderState::translateXYToTile) -translateXYToTile; - MACRO_FUNCTION_RESOLVER(int (ViewportRenderState::*)(int, int), false, 0x004092e0, &ViewportRenderState::meth_0x4092e0) meth_0x4092e0; From 50da6b1b2f35bf5a05d86f8fc1ca3ee4169ab6be Mon Sep 17 00:00:00 2001 From: TheRedDaemon <66257843+TheRedDaemon@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:21:40 +0100 Subject: [PATCH 2/2] [MACRO_APPROACH] add test regarding MSVC init functions --- src/core/MacroApproachTest.h | 28 +++++++++++++++++++++++++--- src/core/ViewportRenderState.2.cpp | 25 ++++++++++++++++++------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/core/MacroApproachTest.h b/src/core/MacroApproachTest.h index 758770a..517de66 100644 --- a/src/core/MacroApproachTest.h +++ b/src/core/MacroApproachTest.h @@ -14,10 +14,33 @@ // Enums can only be used, if they are simple values, so the namespace needs to be removed or not even used (addresses // as simple macros in global namespace) -// using a body macro might be trick with inline assembly macros, had weird errors during tests +// using a body macro might be tricky with inline assembly macros, had weird errors during tests #define JMP_TO_GAME(DECLARATION, ADDR) \ __declspec(naked) DECLARATION { __asm mov eax, ADDR __asm jmp eax } +// example for macro that could create a MICROSOFT startup function +// execution apparently in name order? If the name is not important, it could also be possible to generate the name from +// something (or random) or place it in an anonymous namespace, that being said, if only function hooking should be done +// this way, the macro could also completely generate the code +#define STARTUP_FUNCTION(NAME, BODY) \ + static void NAME() BODY __pragma(section(".CRT$XCU", read)) \ + __declspec(allocate(".CRT$XCU")) void (*_init_##NAME)() \ + = NAME; + +// taken from std::type_identity, which only becomes available with C++20+ +template struct type_identity { + typedef T type; +}; + +// example for generating the init body +#define INIT_FUNCTION_BODY(TYPE, IMPLEMENTED, GAME_ADDRESS, POINTER) \ + { \ + const type_identity::type funcPtr = POINTER; \ + Initializer::initializeFunction(Initializer::AddressUsageKeeper::initialized, IMPLEMENTED, \ + GAME_ADDRESS, *((void**)&funcPtr), getFuncPtrName()); \ + } + +// for testing only namespace Address { enum { F_00401040 = 0x00401040, @@ -29,7 +52,7 @@ enum { //__declspec(allocate(".CRT$XCU")) void(*pInit)() = myInitFunction; struct Initializer { -private: +public: // everything is public, but only for testing template struct AddressUsageKeeper { static bool initialized; }; @@ -40,7 +63,6 @@ struct Initializer { static void initializeFunction( bool& initialized, bool isImplemented, int gameAddress, const void* funcPtr, const char* funcName); -public: template struct StructInitializer { private: struct Init { diff --git a/src/core/ViewportRenderState.2.cpp b/src/core/ViewportRenderState.2.cpp index 8c4f1e4..b048c71 100644 --- a/src/core/ViewportRenderState.2.cpp +++ b/src/core/ViewportRenderState.2.cpp @@ -3,17 +3,26 @@ #include "ViewportRenderState.h" // would need to be defined in every function case -// here, if causes no issues, and can even be undefined at the end +// here, it causes no issues, and can even be undefined at the end // it could even be left out completely, and the "safety" check and hooking via macro could be hardcoded at both // conditions BUT issue: -// Structs need this switch in the header, but also in the implementation files, which would mean setting the state -// twice, if not moved to a central position, where the naming could be an issue again (a central file would trigger -// recomp for everything, so that would be bad) -#define IMPLEMENTED CHECK_IMPLEMENTED(FALSE) +// Structs need this switch in the header, but also in the implementation files, which would mean resolving and setting +// the implementation state twice, if not moved to a central position, where the naming could be an issue again (a +// central file would trigger recomp for everything, so that would be bad) +// the macro in the header could also used to define a macro to indicate that the struct definition should happen, but +// these would also need to be unique for the specific struct +#define IMPLEMENTED CHECK_IMPLEMENTED(TRUE) // using macro to run init, requires also complete definition of type, otherwise we can not pass the function pointer -template struct Initializer::FunctionInitializer; + +/* example using the template: */ +// template struct Initializer::FunctionInitializer; + +/* example using a start up function: */ +STARTUP_FUNCTION(initViewportRenderStateTranslateXYToTile, + INIT_FUNCTION_BODY(int (ViewportRenderState::*)(int, int), IMPLEMENTED, Address::F_00401040, + &ViewportRenderState::translateXYToTile)) #if IMPLEMENTED @@ -30,6 +39,8 @@ JMP_TO_GAME(int ViewportRenderState::translateXYToTile(int x, int y), F_00401040 // would convert to a jmp, but would require to define the ptr type for all functions and consider the thiscall != // memberfunc difference +// could also use the template created for the function resolver that can take a function pointer type and transform +// it to another function type, although member ptr always require their class at the moment // int ViewportRenderState::translateXYToTile(int x, int y) //{ // return ((int(__thiscall*)(ViewportRenderState*, int, int))Address::F_00401040)(this, x, y);