Hooking the Lua State in Train Simulator is easy, due to the Lua Library being statically linked in the games code, rather than being a .DLL
. This has some advantages but also some disadvantages.
Here we create a DllMain function so that we can inject our DLL to the game. We first set a global variable to the hInstDLL and then create ourselves a thread to work in. MainThread
is our thread
typedef unsigned __int64 QWORD;
HMODULE g_instance = nullptr;
unsigned long lua_State = 0;
BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD reason, LPVOID lpReserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hInstDLL);
g_instance = (HMODULE)hInstDLL;
CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)&MainThread, nullptr, 0, nullptr);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return true;
}
This is where we will do anything that calls Lua methods
DWORD WINAPI MainThread(LPVOID)
{
AllocConsole(); // Allocates a console so yo ucan log things
SetConsoleTitleA("Console"); // Sets the consoles window title
freopen_s((FILE**)stdin, "conin$", "r", stdin);
freopen_s((FILE**)stdout, "conout$", "w", stdout);
while (true)
{
// END Key - This ends everything and cleans up
if (GetAsyncKeyState(VK_END) & 0x8000)
{
// READ THE NEXT SECTION BEFORE UNCOMMNENTING
// DetourTransactionBegin();
// DetourUpdateThread( GetCurrentThread() );
// DetourDetach( &(LPVOID&)lua_gettop_p, (PBYTE)_gettop );
// DetourTransactionCommit();
fclose((FILE*)stdin);
fclose((FILE*)stdout);
FreeConsole(); // Deallocates the console
FreeLibraryAndExitThread(g_instance, 0); // Clean up our DLL and uninject
}
Sleep(100);
}
return 0;
}
Here we are creating a method which we can call to detour the gettop method in the game. This allows us to run our own code in place of the games code. But for it to still work as intended we must return the correct value.
typedef int(__cdecl *gettop)(unsigned long);
gettop lua_gettop_p = (gettop)0x18121A870;
// This is our version of the gettop function
DWORD _gettop(__int64 state)
{
// If Lua State is not already set then log it and set it
if (lua_State == 0) {
std::cout << "[Lua] _gettop called - State:" << state << std::endl;
lua_State = state;
}
// This is from the decompiled function and cannot be changed
return (unsigned long)(*((QWORD *)state + 2) - *((QWORD *)state + 3)) >> 4;
}
// Here we setup the detour to "replace" the games function with ours
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(LPVOID&)lua_gettop_p, (PBYTE)_gettop);
DetourTransactionCommit();
These are mostly untested and could be wrong if the game is updated
typedef int(__cdecl *call)(unsigned long, int, unsigned int);
typedef int(__cdecl *dostring)(unsigned long, const char*);
typedef int(__cdecl *error)(unsigned long, const char*, ...);
typedef int(__cdecl *gettop)(unsigned long);
typedef int(__cdecl *gettable)(unsigned long, int);
typedef int(__cdecl *pushstring)(unsigned long, const char*);
typedef int(__cdecl *pushvalue)(unsigned long, int);
typedef int(__cdecl *print)(unsigned long);
typedef int(__cdecl *settable)(unsigned long, int);
typedef int(__cdecl *settop)(unsigned long, int);
typedef const char*(__cdecl *tostring)(unsigned long, int);
call lua_call_p = (call)0x18121B420;
dostring lua_dostring_p = (dostring)0x18122E850;
error luaL_error_p = (error)0x18122E850;
gettop lua_gettop_p = (gettop)0x18121A870;
gettable lua_gettable_p = (gettable)0x18121B0C0;
pushstring lua_pushstring_p = (pushstring)0x18121AEE0;
pushvalue lua_pushvalue_p = (pushvalue)0x18121A9C0;
print luaB_print_p = (print)0x1812337F0;
settable lua_settable_p = (settable)0x18121B270;
settop lua_settop_p = (settop)0x18121A880;
tostring lua_tostring_p = (tostring)0x18121AC50;