Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmake/core-sources.txt
Original file line number Diff line number Diff line change
@@ -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
104 changes: 104 additions & 0 deletions src/core/MacroApproachTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

#include "ucp3.h"

#include "MacroApproachTest.h"

#include <sstream>

// 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
}
108 changes: 108 additions & 0 deletions src/core/MacroApproachTest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#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 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 <typename T> 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>::type funcPtr = POINTER; \
Initializer::initializeFunction(Initializer::AddressUsageKeeper<GAME_ADDRESS>::initialized, IMPLEMENTED, \
GAME_ADDRESS, *((void**)&funcPtr), getFuncPtrName<TYPE, POINTER>()); \
}

// for testing only
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 {
public: // everything is public, but only for testing
template <int address> 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);

template <typename T, bool implemented, int gameAddress, T* structAddress> struct StructInitializer {
private:
struct Init {
Init();
};
static const Init init;
};

template <typename FuncPtrType, bool implemented, int gameAddress, FuncPtrType funcAddress>
struct FunctionInitializer {
private:
struct Init {
Init();
};
static const Init init;
};
};

template <int address> bool Initializer::AddressUsageKeeper<address>::initialized = false;

template <typename T, bool implemented, int gameAddress, T* structAddress>
Initializer::StructInitializer<T, implemented, gameAddress, structAddress>::Init::Init()
{
initializeStruct(
AddressUsageKeeper<gameAddress>::initialized, implemented, gameAddress, structAddress, getTypeName<T>());
}

template <typename T, bool implemented, int gameAddress, T* structAddress>
const typename Initializer::StructInitializer<T, implemented, gameAddress, structAddress>::Init
Initializer::StructInitializer<T, implemented, gameAddress, structAddress>::init;

template <typename FuncPtrType, bool implemented, int gameAddress, FuncPtrType funcAddress>
Initializer::FunctionInitializer<FuncPtrType, implemented, gameAddress, funcAddress>::Init::Init()
{
const FuncPtrType funcPtr = funcAddress;
const void* func = *((void**)&funcPtr);
initializeFunction(AddressUsageKeeper<gameAddress>::initialized, implemented, gameAddress, func,
getFuncPtrName<FuncPtrType, funcAddress>());
}

template <typename FuncPtrType, bool implemented, int gameAddress, FuncPtrType funcAddress>
const typename Initializer::FunctionInitializer<FuncPtrType, implemented, gameAddress, funcAddress>::Init
Initializer::FunctionInitializer<FuncPtrType, implemented, gameAddress, funcAddress>::init;
5 changes: 1 addition & 4 deletions src/core/MainImplementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
61 changes: 61 additions & 0 deletions src/core/ViewportRenderState.2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "MacroApproachTest.h"

#include "ViewportRenderState.h"

// would need to be defined in every function case
// 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 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

/* example using the template: */
// template struct Initializer::FunctionInitializer<int (ViewportRenderState::*)(int, int), IMPLEMENTED,
// Address::F_00401040, &ViewportRenderState::translateXYToTile>;

/* example using a start up function: */
STARTUP_FUNCTION(initViewportRenderStateTranslateXYToTile,
INIT_FUNCTION_BODY(int (ViewportRenderState::*)(int, int), IMPLEMENTED, Address::F_00401040,
&ViewportRenderState::translateXYToTile))

#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
// 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);
//}

#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
// <name_enum_uppercase>_IMPLEMENTED and the the bool indicator
3 changes: 0 additions & 3 deletions src/core/ViewportRenderState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
4 changes: 0 additions & 4 deletions src/core/ViewportRenderState.func.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down