From 3215ecb9f62ada227d9331dc057601ecc40fb3b4 Mon Sep 17 00:00:00 2001 From: "Drew C." Date: Thu, 12 Mar 2026 18:09:46 -0500 Subject: [PATCH] Add GameObjectTypeWrapper Get_Display_Name() function --- FoCAPI.vcxproj | 1 + FoCAPI.vcxproj.filters | 3 ++ functions/functions.h | 4 ++ functions/game_object_type.cpp | 92 ++++++++++++++++++++++++++++++++++ lua_hook.cpp | 43 ++++++++++++++++ lua_hook.h | 17 +++++++ rvas.h | 62 ++++++++++++++++++----- 7 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 functions/game_object_type.cpp diff --git a/FoCAPI.vcxproj b/FoCAPI.vcxproj index fa53cc3..5559077 100644 --- a/FoCAPI.vcxproj +++ b/FoCAPI.vcxproj @@ -167,6 +167,7 @@ + diff --git a/FoCAPI.vcxproj.filters b/FoCAPI.vcxproj.filters index 0ae6219..bbbfbaa 100644 --- a/FoCAPI.vcxproj.filters +++ b/FoCAPI.vcxproj.filters @@ -59,6 +59,9 @@ Source Files\Functions + + Source Files\Functions + diff --git a/functions/functions.h b/functions/functions.h index 7651e33..13337e5 100644 --- a/functions/functions.h +++ b/functions/functions.h @@ -11,3 +11,7 @@ void Register_All(lua_State* L); void Register_FileIO(lua_State* L); void Register_Players(lua_State* L); void Register_ScreenText(lua_State* L); + +// Called from the GameObjectTypeWrapper ctor hook (not from Register_All). +// Registers custom member functions on each wrapper instance. +void Register_GOTMembers(void* wrapper); diff --git a/functions/game_object_type.cpp b/functions/game_object_type.cpp new file mode 100644 index 0000000..5c91e48 --- /dev/null +++ b/functions/game_object_type.cpp @@ -0,0 +1,92 @@ +#include "functions.h" +#include "../lua_hook.h" +#include +#include +#include + +// Offset of SmartPtr within GameObjectTypeWrapper. +// SmartPtr stores the raw pointer as its first member. +static constexpr int OFFSET_OBJECT = 0x70; + +// MSVC std::wstring layout (x64, MSVC 14.0+). +// Used to read the wstring* returned by GameTextClass::Get. +struct MSVCWString { + union { + wchar_t buf[8]; // SSO buffer (up to 7 wchars + null) + wchar_t* ptr; // heap pointer when capacity > 7 + }; + size_t size; + size_t capacity; // <= 7 means SSO, > 7 means heap + + const wchar_t* c_str() const { return (capacity <= 7) ? buf : ptr; } +}; + +// Custom member function registered on GameObjectTypeWrapper. +// Signature matches LuaTable* __thiscall (LuaScriptClass*, LuaTable*). +// On x64 this is the standard calling convention: this_wrapper in RCX, script in RDX, params in R8. +static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*params*/) +{ + // 1. Get GameObjectTypeClass* from SmartPtr at offset 0x70 + void* got_class = *(void**)((char*)this_wrapper + OFFSET_OBJECT); + if (!got_class) + return nullptr; + + // 2. Get the text ID string (e.g. "TEXT_UNIT_X_WING") via Get_Text_Name_ID. + // The game's function constructs a std::string into our buffer via copy-ctor. + // Assumes shared CRT (both DLL and game link to vcruntime140.dll dynamically). + std::string text_id; + pfn_get_text_name_id(got_class, &text_id); + + // 3. Resolve the localized display name via TheGameText.Get(text_id, false). + // Returns a pointer to an internal wstring (the translated text). + MSVCWString* display_wstr = (MSVCWString*)pfn_game_text_get(g_the_game_text, &text_id, false); + if (!display_wstr) + return nullptr; + + // 4. Convert the wide display name to a narrow (UTF-8) string for Lua. + const wchar_t* wdata = display_wstr->c_str(); + int wlen = (int)display_wstr->size; + + int narrow_len = WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, nullptr, 0, nullptr, nullptr); + if (narrow_len <= 0) + return nullptr; + + std::string narrow(narrow_len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, &narrow[0], narrow_len, nullptr, nullptr); + + // 5. Create a LuaString (LuaValue, 0x78 bytes) and return it. + void* lua_value = ::operator new(0x78); + if (!lua_value) + return nullptr; + memset(lua_value, 0, 0x78); + pfn_lua_value_ctor(lua_value, &narrow); + + return pfn_return_variable(this_wrapper, lua_value); +} + +void Register_GOTMembers(void* wrapper) +{ + if (!pfn_lmfw_ctor || !pfn_register_member) + return; + if (!pfn_get_text_name_id || !pfn_game_text_get || !g_the_game_text) + return; + if (!pfn_lua_value_ctor || !pfn_return_variable) + return; + + // Allocate a LuaMemberFunctionWrapper (0x88 bytes). + void* lmfw = ::operator new(0x88); + if (!lmfw) return; + memset(lmfw, 0, 0x88); + + // Build the 16-byte pointer-to-member-function structure. + // MSVC x64 multiple-inheritance PMF: [8-byte func addr][8-byte this-adjustment]. + // this-adjustment is 0 (our function expects the full GameObjectTypeWrapper*). + alignas(8) uint8_t pmf[16] = {}; + *(uintptr_t*)pmf = (uintptr_t)&Lua_Get_Display_Name; + + // Construct the LuaMemberFunctionWrapper via the game's ctor. + pfn_lmfw_ctor(lmfw, wrapper, pmf, false); + + // Register as a member callable via wrapper.Get_Display_Name() + pfn_register_member(wrapper, "Get_Display_Name", lmfw); +} diff --git a/lua_hook.cpp b/lua_hook.cpp index 1f9132b..0dfefe8 100644 --- a/lua_hook.cpp +++ b/lua_hook.cpp @@ -21,6 +21,14 @@ fn_AddTutorialText pfn_add_tutorial_text = nullptr; fn_RemoveTutorialText pfn_remove_tutorial_text = nullptr; void* g_command_bar = nullptr; fn_DebugPrint pfn_debug_print = nullptr; +fn_RegisterMember pfn_register_member = nullptr; +fn_LuaValueCtor pfn_lua_value_ctor = nullptr; +fn_ReturnVariable pfn_return_variable = nullptr; +fn_GotWrapperCtor real_got_wrapper_ctor = nullptr; +fn_LmfwCtor pfn_lmfw_ctor = nullptr; +fn_GetTextNameId pfn_get_text_name_id = nullptr; +fn_GameTextGet pfn_game_text_get = nullptr; +void* g_the_game_text = nullptr; void register_global(lua_State* L, const char* name, lua_CFunction fn) { pfn_pushstring(L, name); @@ -28,6 +36,13 @@ void register_global(lua_State* L, const char* name, lua_CFunction fn) { pfn_settable(L, LUA_GLOBALSINDEX); } +// Fired every time the game constructs a GameObjectTypeWrapper +static void* Hook_GOTWrapperCtor(void* this_wrapper) { + void* result = real_got_wrapper_ctor(this_wrapper); + Register_GOTMembers(this_wrapper); + return result; +} + // Fired every time the game creates a new Lua state static lua_State* Hook_lua_open() { lua_State* L = real_lua_open(); @@ -84,6 +99,22 @@ bool LuaHook_Init() { if (rvas->debug_print) pfn_debug_print = (fn_DebugPrint)(base + rvas->debug_print); + if (rvas->register_member) + pfn_register_member = (fn_RegisterMember)(base + rvas->register_member); + if (rvas->lua_value_ctor) + pfn_lua_value_ctor = (fn_LuaValueCtor)(base + rvas->lua_value_ctor); + if (rvas->return_variable) + pfn_return_variable = (fn_ReturnVariable)(base + rvas->return_variable); + + if (rvas->the_game_text) + g_the_game_text = (void*)(base + rvas->the_game_text); + if (rvas->get_string) + pfn_game_text_get = (fn_GameTextGet)(base + rvas->get_string); + if (rvas->got_get_text_name_id) + pfn_get_text_name_id = (fn_GetTextNameId)(base + rvas->got_get_text_name_id); + if (rvas->got_lmfw_ctor) + pfn_lmfw_ctor = (fn_LmfwCtor)(base + rvas->got_lmfw_ctor); + if (rvas->lua_newtable && rvas->lua_rawseti) { pfn_newtable = (fn_lua_newtable)(base + rvas->lua_newtable); pfn_rawseti = (fn_lua_rawseti)(base + rvas->lua_rawseti); @@ -103,6 +134,18 @@ bool LuaHook_Init() { return false; } + // Hook GameObjectTypeWrapper constructor to register custom member functions + if (rvas->got_wrapper_ctor && rvas->got_lmfw_ctor && + pfn_register_member && pfn_lmfw_ctor && + pfn_get_text_name_id && pfn_game_text_get && g_the_game_text && + pfn_lua_value_ctor && pfn_return_variable) + { + void* got_ctor_target = (void*)(base + rvas->got_wrapper_ctor); + if (MH_CreateHook(got_ctor_target, &Hook_GOTWrapperCtor, (void**)&real_got_wrapper_ctor) == MH_OK) { + MH_EnableHook(got_ctor_target); + } + } + return true; } diff --git a/lua_hook.h b/lua_hook.h index f5a2835..5bfe20a 100644 --- a/lua_hook.h +++ b/lua_hook.h @@ -20,6 +20,15 @@ typedef void (*fn_MapVarToLua)(lua_State*, void*); // LuaScriptCl typedef void (*fn_AddTutorialText)(void*, void*, const char*, float, unsigned int, bool, void*); // CommandBarClass::Add_Tutorial_Text (inner) typedef void (*fn_RemoveTutorialText)(void*, const char*); // CommandBarClass::Remove_Tutorial_Text typedef void (*fn_DebugPrint)(const char*); // Debug_Print +// Scripting internals (LuaUserVar) -- usable by any wrapper module +typedef void (*fn_RegisterMember)(void* uservar, const char* name, void* fn_ptr); // LuaUserVar::Register_Member +typedef void (*fn_LuaValueCtor)(void* lua_value, void* basic_string_ptr); // LuaValue<>::LuaValue<>(basic_string<>*) +typedef void* (*fn_ReturnVariable)(void* uservar, void* lua_var); // LuaUserVar::Return_Variable(LuaVar*) -> LuaTable* +// Game Object Type Wrapper internals +typedef void* (*fn_GotWrapperCtor)(void* this_wrapper); // GameObjectTypeWrapper::GameObjectTypeWrapper() +typedef void* (*fn_LmfwCtor)(void* lmfw, void* wrapper, void* pmf, bool use_maps); // LuaMemberFunctionWrapper::ctor +typedef void* (*fn_GetTextNameId)(void* got_class, void* out_string); // GameObjectTypeClass::Get_Text_Name_ID() -- returns via hidden out-param +typedef void* (*fn_GameTextGet)(void* game_text, void* text_id_str, bool insert); // GameTextClass::Get(basic_string*, bool) -- returns wstring* // Resolved function pointers (defined in lua_hook.cpp) extern fn_lua_open real_lua_open; @@ -39,6 +48,14 @@ extern fn_AddTutorialText pfn_add_tutorial_text; extern fn_RemoveTutorialText pfn_remove_tutorial_text; extern void* g_command_bar; extern fn_DebugPrint pfn_debug_print; +extern fn_RegisterMember pfn_register_member; +extern fn_LuaValueCtor pfn_lua_value_ctor; +extern fn_ReturnVariable pfn_return_variable; +extern fn_GotWrapperCtor real_got_wrapper_ctor; +extern fn_LmfwCtor pfn_lmfw_ctor; +extern fn_GetTextNameId pfn_get_text_name_id; +extern fn_GameTextGet pfn_game_text_get; +extern void* g_the_game_text; constexpr int LUA_GLOBALSINDEX = -10001; diff --git a/rvas.h b/rvas.h index fccaeee..9fade65 100644 --- a/rvas.h +++ b/rvas.h @@ -12,20 +12,32 @@ struct LuaRVAs { uintptr_t lua_newtable; uintptr_t lua_rawseti; + // --- Scripts --- + uintptr_t get_script_from_state; // LuaScriptClass::Get_Script_From_State (static) + uintptr_t map_var_to_lua; // LuaScriptClass::Map_Var_To_Lua (member) + uintptr_t register_member; // LuaUserVar::Register_Member(char*, LuaVar*) + uintptr_t lua_value_ctor; // LuaValue<>::LuaValue<>(basic_string<>*) + uintptr_t return_variable; // LuaUserVar::Return_Variable(LuaVar*) + + // --- TheGameText --- + uintptr_t the_game_text; // TheGameText global object (data, not a function) + uintptr_t get_string; // GameTextClass::Get(char*, bool) + // --- Players --- uintptr_t get_player_by_index; // PlayerListClass::Get_Player_By_Index uintptr_t player_list; // PlayerList global object (data, not a function) uintptr_t player_wrapper_create; // PlayerWrapper::Create (static) - // --- Scripts --- - uintptr_t get_script_from_state; // LuaScriptClass::Get_Script_From_State (static) - uintptr_t map_var_to_lua; // LuaScriptClass::Map_Var_To_Lua (member) - // --- Screen Text --- uintptr_t command_bar; // TheCommandBar global object (data, not a function) uintptr_t add_tutorial_text; // CommandBarClass::Add_Tutorial_Text (inner overload: wstring*, char*, float, uint, bool, RGBAClass*) uintptr_t remove_tutorial_text; // CommandBarClass::Remove_Tutorial_Text(char*) + // --- Game Object Type Wrapper --- + uintptr_t got_wrapper_ctor; // GameObjectTypeWrapper::GameObjectTypeWrapper() + uintptr_t got_get_text_name_id; // GameObjectTypeClass::Get_Text_Name_ID() + uintptr_t got_lmfw_ctor; // LuaMemberFunctionWrapper::LuaMemberFunctionWrapper<>() + // --- Debug --- uintptr_t debug_print; // Debug_Print(char*) }; @@ -42,20 +54,32 @@ constexpr LuaRVAs RVAs_StarWarsI = { 0x12AC460, // lua_newtable 0x12ACD70, // lua_rawseti + // --- Scripts --- + 0x0537BB0, // LuaScriptClass::Get_Script_From_State + 0x0536FE0, // LuaScriptClass::Map_Var_To_Lua + 0x0046f51, // LuaUserVar::Register_Member + 0x004df5e, // LuaValue<>::LuaValue<> + 0x0058887, // LuaUserVar::Return_Variable + + // --- TheGameText --- + 0x18578b0, // TheGameText global object + 0x002b4cc, // GameTextClass::Get + // --- Players --- 0x000b0e1, // PlayerListClass::Get_Player_By_Index 0x1913158, // PlayerList global object 0x0F12010, // PlayerWrapper::Create - // --- Scripts --- - 0x0537BB0, // LuaScriptClass::Get_Script_From_State - 0x0536FE0, // LuaScriptClass::Map_Var_To_Lua - // --- Screen Text --- 0x1916b80, // TheCommandBar global object 0x003c83a, // CommandBarClass::Add_Tutorial_Text inner 0x006e1eb, // CommandBarClass::Remove_Tutorial_Text + // --- Game Object Type Wrapper --- + 0x004f6c9, // GameObjectTypeWrapper::GameObjectTypeWrapper() + 0x007b747, // GameObjectTypeClass::Get_Text_Name_ID() + 0x0f08450, // LuaMemberFunctionWrapper::ctor + // --- Debug --- 0x0476AE0, // Debug_Print }; @@ -72,20 +96,32 @@ constexpr LuaRVAs RVAs_StarWarsG = { 0x07b9140, // lua_newtable 0x07b9820, // lua_rawseti + // --- Scripts --- + 0x0245790, // LuaScriptClass::Get_Script_From_State + 0x0247700, // LuaScriptClass::Map_Var_To_Lua + 0, // LuaUserVar::Register_Member -- not mapped + 0, // LuaString::LuaString -- not mapped + 0, // LuaUserVar::Return_Variable -- not mapped + + // --- TheGameText --- + 0, // TheGameText global object + 0, // GameTextClass::Get + // --- Players --- 0x0294bc0, // PlayerListClass::Get_Player_By_Index 0x0a16fd0, // PlayerList global object 0x06019f0, // PlayerWrapper::Create - // --- Scripts --- - 0x0245790, // LuaScriptClass::Get_Script_From_State - 0x0247700, // LuaScriptClass::Map_Var_To_Lua - // --- Screen Text --- - 0x0b27f60, // TheCommandBar + 0x0b27f60, // TheCommandBar global object 0x02dfd10, // CommandBarClass::Add_Tutorial_Text inner 0x02ff290, // CommandBarClass::Remove_Tutorial_Text + // --- Game Object Type Wrapper --- + 0, // GameObjectTypeWrapper::GameObjectTypeWrapper() -- not mapped + 0, // GameObjectTypeClass::Get_Text_Name_ID() -- not mapped + 0, // LuaMemberFunctionWrapper::ctor -- not mapped + // --- Debug --- 0, // Debug_Print (not mapped) };