Use inline ctors for GameObjectTypeWrapper, LuaMemberFunctionWrapper

This commit is contained in:
2026-03-14 13:04:22 -05:00
parent 7deb2ff293
commit 67adfd1e08
4 changed files with 76 additions and 36 deletions

View File

@@ -8,6 +8,19 @@
// SmartPtr stores the raw pointer as its first member.
static constexpr int OFFSET_OBJECT = 0x70;
// MSVC std::string layout (x64, MSVC 14.0+).
// Used to read the std::string at a known field offset within GameObjectTypeClass.
struct MSVCString {
union {
char buf[16]; // SSO buffer (up to 15 chars + null)
char* ptr; // heap pointer when capacity > 15
};
size_t size;
size_t capacity; // <= 15 means SSO, > 15 means heap
const char* c_str() const { return (capacity <= 15) ? buf : ptr; }
};
// MSVC std::wstring layout (x64, MSVC 14.0+).
// Used to read the wstring* returned by GameTextClass::Get.
struct MSVCWString {
@@ -31,11 +44,10 @@ static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*
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);
// 2. Read the TextNameID string directly from the GameObjectTypeClass field.
// Get_Text_Name_ID() is always inlined by the compiler, so we use the field offset.
MSVCString* text_name = (MSVCString*)((char*)got_class + g_text_name_id_offset);
std::string text_id(text_name->c_str(), text_name->size);
// 3. Resolve the localized display name via TheGameText.Get(text_id, false).
// Returns a pointer to an internal wstring (the translated text).
@@ -64,26 +76,36 @@ static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*
void Register_GOTMembers(void* wrapper)
{
if (!pfn_lmfw_ctor || !pfn_register_member)
if (!pfn_lua_uservar_ctor || !pfn_register_member)
return;
if (!pfn_get_text_name_id || !pfn_game_text_get || !g_the_game_text)
if (!g_text_name_id_offset || !pfn_game_text_get || !g_the_game_text)
return;
if (!pfn_return_variable)
if (!pfn_return_variable || !g_rvas || !g_rvas->lmfw_vftable)
return;
// Allocate a LuaMemberFunctionWrapper<GameObjectTypeWrapper> (0x88 bytes).
void* lmfw = ::operator new(0x88);
const LuaRVAs* rvas = g_rvas;
// Allocate a LuaMemberFunctionWrapper<GameObjectTypeWrapper>.
void* lmfw = ::operator new(rvas->lmfw_alloc_size);
if (!lmfw) return;
memset(lmfw, 0, 0x88);
memset(lmfw, 0, rvas->lmfw_alloc_size);
// Build the 16-byte pointer-to-member-function structure.
// Initialize the LuaUserVar base class.
pfn_lua_uservar_ctor(lmfw, 0, false);
// Set the LuaMemberFunctionWrapper<> vftable.
*(uintptr_t*)lmfw = g_base + rvas->lmfw_vftable;
// Write the 16-byte pointer-to-member-function (PMF) into the MemberFunction field.
// 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;
memcpy((char*)lmfw + rvas->lmfw_pmf_offset, pmf, 16);
// Construct the LuaMemberFunctionWrapper via the game's ctor.
pfn_lmfw_ctor(lmfw, wrapper, pmf, false);
// Set the Object (wrapper pointer) and UseMaps flag.
*(void**)((char*)lmfw + rvas->lmfw_object_offset) = wrapper;
*(bool*)((char*)lmfw + rvas->lmfw_usemaps_offset) = false;
// Register as a member callable via wrapper.Get_Display_Name()
pfn_register_member(wrapper, "Get_Display_Name", lmfw);