Enable Get_Display_Name in StarWarsG #3

Merged
drew merged 3 commits from display-text into main 2026-03-14 22:41:24 -05:00
5 changed files with 105 additions and 15 deletions
Showing only changes of commit 4987ee81f6 - Show all commits

View File

@@ -4,9 +4,8 @@
#include <string> #include <string>
#include <cstring> #include <cstring>
// Offset of SmartPtr<GameObjectTypeClass> within GameObjectTypeWrapper. // SmartPtr offset is per-executable (0x70 StarWarsI, 0x28 StarWarsG).
// SmartPtr stores the raw pointer as its first member. // Read from g_rvas->got_object_offset at runtime.
static constexpr int OFFSET_OBJECT = 0x70;
// MSVC std::string layout (x64, MSVC 14.0+). // MSVC std::string layout (x64, MSVC 14.0+).
// Used to read the std::string at a known field offset within GameObjectTypeClass. // Used to read the std::string at a known field offset within GameObjectTypeClass.
@@ -39,23 +38,21 @@ struct MSVCWString {
// On x64 this is the standard calling convention: this_wrapper in RCX, script in RDX, params in R8. // 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*/) static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*params*/)
{ {
// 1. Get GameObjectTypeClass* from SmartPtr at offset 0x70 // 1. Get GameObjectTypeClass* from SmartPtr at offset
void* got_class = *(void**)((char*)this_wrapper + OFFSET_OBJECT); void* got_class = *(void**)((char*)this_wrapper + g_rvas->got_object_offset);
if (!got_class) if (!got_class)
return nullptr; return nullptr;
// 2. Read the TextNameID string directly from the GameObjectTypeClass field. // 2. Read the TextNameID string 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); MSVCString* text_name = (MSVCString*)((char*)got_class + g_text_name_id_offset);
std::string text_id(text_name->c_str(), text_name->size); std::string text_id(text_name->c_str(), text_name->size);
// 3. Resolve the localized display name via TheGameText.Get(text_id, false). // 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); MSVCWString* display_wstr = (MSVCWString*)pfn_game_text_get(g_the_game_text, &text_id, false);
if (!display_wstr) if (!display_wstr)
return nullptr; return nullptr;
// 4. Convert the wide display name to a narrow (UTF-8) string for Lua. // 4. Convert the wide display name to a narrow (UTF-8) string for Lua
const wchar_t* wdata = display_wstr->c_str(); const wchar_t* wdata = display_wstr->c_str();
int wlen = (int)display_wstr->size; int wlen = (int)display_wstr->size;
@@ -66,7 +63,7 @@ static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*
std::string narrow(narrow_len, '\0'); std::string narrow(narrow_len, '\0');
WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, &narrow[0], narrow_len, nullptr, nullptr); WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, &narrow[0], narrow_len, nullptr, nullptr);
// 5. Create a LuaString (LuaValue<string>) and return it. // 5. Create a LuaString and return it
void* lua_value = CreateLuaValueString(narrow); void* lua_value = CreateLuaValueString(narrow);
if (!lua_value) if (!lua_value)
return nullptr; return nullptr;

View File

@@ -9,8 +9,16 @@
// before its own null check, so we always pass a valid buffer. // before its own null check, so we always pass a valid buffer.
static int L_Add_Tutorial_Text(lua_State* L) static int L_Add_Tutorial_Text(lua_State* L)
{ {
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Add_Tutorial_Text: entered\n");
const char* key = pfn_tostring(L, 1); const char* key = pfn_tostring(L, 1);
const char* text = pfn_tostring(L, 2); const char* text = pfn_tostring(L, 2);
if (pfn_debug_print) {
char msg[512];
wsprintfA(msg, "[FoCAPI] Add_Tutorial_Text: key = \"%s\", text = \"%s\"\n",
key ? key : "(null)", text ? text : "(null)");
pfn_debug_print(msg);
}
if (!key || !text) if (!key || !text)
return 0; return 0;
@@ -35,12 +43,29 @@ static int L_Add_Tutorial_Text(lua_State* L)
} }
} }
if (pfn_debug_print) {
char msg[128];
wsprintfA(msg, "[FoCAPI] Add_Tutorial_Text: duration = %d, color = [%d,%d,%d,%d]\n",
(int)(duration * 1000), (int)(color[0] * 255), (int)(color[1] * 255),
(int)(color[2] * 255), (int)(color[3] * 255));
pfn_debug_print(msg);
}
int wlen = MultiByteToWideChar(CP_ACP, 0, text, -1, nullptr, 0); int wlen = MultiByteToWideChar(CP_ACP, 0, text, -1, nullptr, 0);
std::wstring wtext(wlen - 1, L'\0'); std::wstring wtext(wlen - 1, L'\0');
MultiByteToWideChar(CP_ACP, 0, text, -1, &wtext[0], wlen); MultiByteToWideChar(CP_ACP, 0, text, -1, &wtext[0], wlen);
if (pfn_debug_print) {
char msg[128];
wsprintfA(msg, "[FoCAPI] Add_Tutorial_Text: wlen = %d, calling pfn_add_tutorial_text(%p, ...)\n",
wlen, g_command_bar);
pfn_debug_print(msg);
}
pfn_add_tutorial_text(g_command_bar, &wtext, (char*)key, duration, 0, false, color); pfn_add_tutorial_text(g_command_bar, &wtext, (char*)key, duration, 0, false, color);
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Add_Tutorial_Text: returned OK\n");
return 0; return 0;
} }

View File

@@ -3,8 +3,28 @@
#include <MinHook.h> #include <MinHook.h>
#include <windows.h> #include <windows.h>
#include <cstring> #include <cstring>
#include <cstdarg>
#include <cstdio>
#include <new> #include <new>
void debug_log(const char* fmt, ...) {
char buf[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
// Also route through in-game debug if available
if (pfn_debug_print)
pfn_debug_print(buf);
FILE* f = nullptr;
if (fopen_s(&f, "FoCAPI_debug.log", "a") == 0 && f) {
fputs(buf, f);
fclose(f);
}
}
// Cached during init — used by CreateLuaValueString and Register_GOTMembers // Cached during init — used by CreateLuaValueString and Register_GOTMembers
const LuaRVAs* g_rvas = nullptr; const LuaRVAs* g_rvas = nullptr;
uintptr_t g_base = 0; uintptr_t g_base = 0;
@@ -32,6 +52,8 @@ fn_ReturnVariable pfn_return_variable = nullptr;
fn_GotWrapperCtor real_got_wrapper_ctor = nullptr; fn_GotWrapperCtor real_got_wrapper_ctor = nullptr;
fn_LuaUserVarCtor pfn_lua_uservar_ctor = nullptr; fn_LuaUserVarCtor pfn_lua_uservar_ctor = nullptr;
size_t g_text_name_id_offset = 0; size_t g_text_name_id_offset = 0;
fn_LuaValueStringCtor pfn_lua_value_string_ctor = nullptr;
fn_GameOperatorNew pfn_game_operator_new = nullptr;
fn_GameTextGet pfn_game_text_get = nullptr; fn_GameTextGet pfn_game_text_get = nullptr;
void* g_the_game_text = nullptr; void* g_the_game_text = nullptr;
@@ -50,11 +72,35 @@ void* CreateLuaValueString(const std::string& str) {
return nullptr; return nullptr;
memset(obj, 0, g_rvas->lua_value_string_size); memset(obj, 0, g_rvas->lua_value_string_size);
// Set the vftable pointer if (pfn_lua_value_string_ctor) {
// Use the game's own constructor (StarWarsI).
// Handles all base class init (RefCountClass, RTTI, PooledObjectClass).
pfn_lua_value_string_ctor(obj, (void*)&str);
} else {
// Manual construction for builds where the ctor is inlined (StarWarsG).
*(uintptr_t*)obj = g_base + g_rvas->lua_value_string_vftable; *(uintptr_t*)obj = g_base + g_rvas->lua_value_string_vftable;
// Placement-new the string into the member offset char* s = (char*)obj + g_rvas->lua_value_string_offset;
new ((char*)obj + g_rvas->lua_value_string_offset) std::string(str); size_t len = str.size();
if (len < 16) {
memcpy(s, str.c_str(), len + 1);
*(size_t*)(s + 16) = len;
*(size_t*)(s + 24) = 15;
} else {
size_t cap = len | 0xF;
// Use the game's operator new so the game's destructor can free
// the buffer with its own operator delete (same CRT heap).
char* buf = pfn_game_operator_new
? (char*)pfn_game_operator_new(cap + 1)
: (char*)::operator new(cap + 1);
memcpy(buf, str.c_str(), len);
buf[len] = '\0';
*(char**)(s) = buf;
*(size_t*)(s + 16) = len;
*(size_t*)(s + 24) = cap;
}
}
return obj; return obj;
} }
@@ -129,6 +175,11 @@ bool LuaHook_Init() {
if (rvas->return_variable) if (rvas->return_variable)
pfn_return_variable = (fn_ReturnVariable)(base + rvas->return_variable); pfn_return_variable = (fn_ReturnVariable)(base + rvas->return_variable);
if (rvas->lua_value_string_ctor)
pfn_lua_value_string_ctor = (fn_LuaValueStringCtor)(base + rvas->lua_value_string_ctor);
if (rvas->game_operator_new)
pfn_game_operator_new = (fn_GameOperatorNew)(base + rvas->game_operator_new);
if (rvas->the_game_text) if (rvas->the_game_text)
g_the_game_text = (void*)(base + rvas->the_game_text); g_the_game_text = (void*)(base + rvas->the_game_text);
if (rvas->get_string) if (rvas->get_string)

View File

@@ -27,6 +27,9 @@ typedef void* (*fn_ReturnVariable)(void* uservar, void* lua_var);
// Game Object Type Wrapper internals // Game Object Type Wrapper internals
typedef void* (*fn_GotWrapperCtor)(void* this_wrapper); // GameObjectTypeWrapper::GameObjectTypeWrapper() typedef void* (*fn_GotWrapperCtor)(void* this_wrapper); // GameObjectTypeWrapper::GameObjectTypeWrapper()
typedef void* (*fn_LuaUserVarCtor)(void* this_ptr, int param, bool flag); // LuaUserVar::LuaUserVar(int, bool) typedef void* (*fn_LuaUserVarCtor)(void* this_ptr, int param, bool flag); // LuaUserVar::LuaUserVar(int, bool)
// LuaValue<basic_string<>> constructor -- available in StarWarsI, inlined in StarWarsG
typedef void* (*fn_LuaValueStringCtor)(void* this_ptr, void* str_ptr); // LuaValue<basic_string<>>::LuaValue(basic_string*)
typedef void* (*fn_GameOperatorNew)(size_t size); // operator new(size_t) — game's CRT allocator
// GameObjectTypeClass::TextNameID is accessed via field offset (always inlined by compiler) // GameObjectTypeClass::TextNameID is accessed via field offset (always inlined by compiler)
typedef void* (*fn_GameTextGet)(void* game_text, void* text_id_str, bool insert); // GameTextClass::Get(basic_string*, bool) -- returns wstring* typedef void* (*fn_GameTextGet)(void* game_text, void* text_id_str, bool insert); // GameTextClass::Get(basic_string*, bool) -- returns wstring*
@@ -53,6 +56,8 @@ extern fn_ReturnVariable pfn_return_variable;
extern fn_GotWrapperCtor real_got_wrapper_ctor; extern fn_GotWrapperCtor real_got_wrapper_ctor;
extern fn_LuaUserVarCtor pfn_lua_uservar_ctor; extern fn_LuaUserVarCtor pfn_lua_uservar_ctor;
extern size_t g_text_name_id_offset; extern size_t g_text_name_id_offset;
extern fn_LuaValueStringCtor pfn_lua_value_string_ctor;
extern fn_GameOperatorNew pfn_game_operator_new;
extern fn_GameTextGet pfn_game_text_get; extern fn_GameTextGet pfn_game_text_get;
extern void* g_the_game_text; extern void* g_the_game_text;
extern const LuaRVAs* g_rvas; extern const LuaRVAs* g_rvas;
@@ -67,5 +72,8 @@ void register_global(lua_State* L, const char* name, lua_CFunction fn);
// Reproduces the inlined constructor pattern using per-executable layout constants. // Reproduces the inlined constructor pattern using per-executable layout constants.
void* CreateLuaValueString(const std::string& str); void* CreateLuaValueString(const std::string& str);
// File-based debug logging (works on both executables; pfn_debug_print is unavailable on StarWarsG).
void debug_log(const char* fmt, ...);
bool LuaHook_Init(); bool LuaHook_Init();
void LuaHook_Shutdown(); void LuaHook_Shutdown();

9
rvas.h
View File

@@ -19,6 +19,8 @@ struct LuaRVAs {
uintptr_t lua_value_string_vftable; // LuaValue<basic_string<>>::vftable uintptr_t lua_value_string_vftable; // LuaValue<basic_string<>>::vftable
size_t lua_value_string_size; // allocation size of LuaValue<basic_string<>> size_t lua_value_string_size; // allocation size of LuaValue<basic_string<>>
size_t lua_value_string_offset; // offset of basic_string member within LuaValue size_t lua_value_string_offset; // offset of basic_string member within LuaValue
uintptr_t lua_value_string_ctor; // LuaValue<basic_string<>>::LuaValue(basic_string*) — 0 if inlined
uintptr_t game_operator_new; // operator new(size_t) — game's CRT allocator, 0 if ctor available
uintptr_t return_variable; // LuaUserVar::Return_Variable(LuaVar*) uintptr_t return_variable; // LuaUserVar::Return_Variable(LuaVar*)
// --- TheGameText --- // --- TheGameText ---
@@ -37,6 +39,7 @@ struct LuaRVAs {
// --- Game Object Type Wrapper --- // --- Game Object Type Wrapper ---
uintptr_t got_wrapper_ctor; // GameObjectTypeWrapper::GameObjectTypeWrapper() uintptr_t got_wrapper_ctor; // GameObjectTypeWrapper::GameObjectTypeWrapper()
size_t got_object_offset; // offset of SmartPtr<GameObjectTypeClass> within GameObjectTypeWrapper
size_t got_text_name_id_offset; // offset of TextNameID (std::string) within GameObjectTypeClass size_t got_text_name_id_offset; // offset of TextNameID (std::string) within GameObjectTypeClass
// LuaMemberFunctionWrapper<GameObjectTypeWrapper> layout (ctor always inlined) // LuaMemberFunctionWrapper<GameObjectTypeWrapper> layout (ctor always inlined)
uintptr_t lmfw_vftable; // LuaMemberFunctionWrapper<>::vftable uintptr_t lmfw_vftable; // LuaMemberFunctionWrapper<>::vftable
@@ -69,6 +72,8 @@ constexpr LuaRVAs RVAs_StarWarsI = {
0x1453310, // LuaValue<basic_string<>>::vftable 0x1453310, // LuaValue<basic_string<>>::vftable
0x78, // LuaValue<basic_string<>> allocation size 0x78, // LuaValue<basic_string<>> allocation size
0x58, // basic_string member offset within LuaValue 0x58, // basic_string member offset within LuaValue
0x0122920, // LuaValue<basic_string<>>::LuaValue(basic_string*)
0, // operator new(size_t) — not needed, ctor available
0x0058887, // LuaUserVar::Return_Variable 0x0058887, // LuaUserVar::Return_Variable
// --- TheGameText --- // --- TheGameText ---
@@ -87,6 +92,7 @@ constexpr LuaRVAs RVAs_StarWarsI = {
// --- Game Object Type Wrapper --- // --- Game Object Type Wrapper ---
0x004f6c9, // GameObjectTypeWrapper::GameObjectTypeWrapper() 0x004f6c9, // GameObjectTypeWrapper::GameObjectTypeWrapper()
0x70, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
0x160, // GameObjectTypeClass::TextNameID field offset 0x160, // GameObjectTypeClass::TextNameID field offset
0x158a680, // LuaMemberFunctionWrapper<>::vftable 0x158a680, // LuaMemberFunctionWrapper<>::vftable
0x0074f55, // LuaUserVar::LuaUserVar 0x0074f55, // LuaUserVar::LuaUserVar
@@ -118,6 +124,8 @@ constexpr LuaRVAs RVAs_StarWarsG = {
0x0855a98, // LuaValue<basic_string<>>::vftable 0x0855a98, // LuaValue<basic_string<>>::vftable
0x30, // LuaValue<basic_string<>> allocation size 0x30, // LuaValue<basic_string<>> allocation size
0x10, // basic_string member offset within LuaValue 0x10, // basic_string member offset within LuaValue
0, // LuaValue<basic_string<>>::LuaValue(basic_string*) — inlined in StarWarsG
0x0769c58, // operator new(size_t) — game's CRT allocator for manual string construction
0x0256d40, // LuaUserVar::Return_Variable 0x0256d40, // LuaUserVar::Return_Variable
// --- TheGameText --- // --- TheGameText ---
@@ -136,6 +144,7 @@ constexpr LuaRVAs RVAs_StarWarsG = {
// --- Game Object Type Wrapper --- // --- Game Object Type Wrapper ---
0x0604a10, // GameObjectTypeWrapper::GameObjectTypeWrapper() 0x0604a10, // GameObjectTypeWrapper::GameObjectTypeWrapper()
0x28, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
0x120, // GameObjectTypeClass::TextNameID field offset 0x120, // GameObjectTypeClass::TextNameID field offset
0x08ab770, // LuaMemberFunctionWrapper<>::vftable 0x08ab770, // LuaMemberFunctionWrapper<>::vftable
0x0249c20, // LuaUserVar::LuaUserVar 0x0249c20, // LuaUserVar::LuaUserVar