Fix display text in StarWarsG
This commit is contained in:
@@ -4,9 +4,8 @@
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
// Offset of SmartPtr<GameObjectTypeClass> within GameObjectTypeWrapper.
|
||||
// SmartPtr stores the raw pointer as its first member.
|
||||
static constexpr int OFFSET_OBJECT = 0x70;
|
||||
// SmartPtr offset is per-executable (0x70 StarWarsI, 0x28 StarWarsG).
|
||||
// Read from g_rvas->got_object_offset at runtime.
|
||||
|
||||
// MSVC std::string layout (x64, MSVC 14.0+).
|
||||
// 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.
|
||||
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);
|
||||
// 1. Get GameObjectTypeClass* from SmartPtr at offset
|
||||
void* got_class = *(void**)((char*)this_wrapper + g_rvas->got_object_offset);
|
||||
if (!got_class)
|
||||
return nullptr;
|
||||
|
||||
// 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.
|
||||
// 2. Read the TextNameID string from the GameObjectTypeClass field
|
||||
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).
|
||||
// 3. Resolve the localized display name via TheGameText.Get(text_id, false)
|
||||
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.
|
||||
// 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;
|
||||
|
||||
@@ -66,7 +63,7 @@ static void* Lua_Get_Display_Name(void* this_wrapper, void* /*script*/, void* /*
|
||||
std::string narrow(narrow_len, '\0');
|
||||
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);
|
||||
if (!lua_value)
|
||||
return nullptr;
|
||||
|
||||
@@ -9,8 +9,16 @@
|
||||
// before its own null check, so we always pass a valid buffer.
|
||||
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* 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)
|
||||
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);
|
||||
std::wstring wtext(wlen - 1, L'\0');
|
||||
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);
|
||||
|
||||
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Add_Tutorial_Text: returned OK\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
59
lua_hook.cpp
59
lua_hook.cpp
@@ -3,8 +3,28 @@
|
||||
#include <MinHook.h>
|
||||
#include <windows.h>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#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
|
||||
const LuaRVAs* g_rvas = nullptr;
|
||||
uintptr_t g_base = 0;
|
||||
@@ -32,6 +52,8 @@ fn_ReturnVariable pfn_return_variable = nullptr;
|
||||
fn_GotWrapperCtor real_got_wrapper_ctor = nullptr;
|
||||
fn_LuaUserVarCtor pfn_lua_uservar_ctor = nullptr;
|
||||
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;
|
||||
void* g_the_game_text = nullptr;
|
||||
|
||||
@@ -50,11 +72,35 @@ void* CreateLuaValueString(const std::string& str) {
|
||||
return nullptr;
|
||||
memset(obj, 0, g_rvas->lua_value_string_size);
|
||||
|
||||
// Set the vftable pointer
|
||||
*(uintptr_t*)obj = g_base + g_rvas->lua_value_string_vftable;
|
||||
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;
|
||||
|
||||
// Placement-new the string into the member offset
|
||||
new ((char*)obj + g_rvas->lua_value_string_offset) std::string(str);
|
||||
char* s = (char*)obj + g_rvas->lua_value_string_offset;
|
||||
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;
|
||||
}
|
||||
@@ -129,6 +175,11 @@ bool LuaHook_Init() {
|
||||
if (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)
|
||||
g_the_game_text = (void*)(base + rvas->the_game_text);
|
||||
if (rvas->get_string)
|
||||
|
||||
@@ -27,6 +27,9 @@ typedef void* (*fn_ReturnVariable)(void* uservar, void* lua_var);
|
||||
// Game Object Type Wrapper internals
|
||||
typedef void* (*fn_GotWrapperCtor)(void* this_wrapper); // GameObjectTypeWrapper::GameObjectTypeWrapper()
|
||||
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)
|
||||
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_LuaUserVarCtor pfn_lua_uservar_ctor;
|
||||
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 void* g_the_game_text;
|
||||
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.
|
||||
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();
|
||||
void LuaHook_Shutdown();
|
||||
|
||||
9
rvas.h
9
rvas.h
@@ -19,6 +19,8 @@ struct LuaRVAs {
|
||||
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_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*)
|
||||
|
||||
// --- TheGameText ---
|
||||
@@ -37,6 +39,7 @@ struct LuaRVAs {
|
||||
|
||||
// --- Game Object Type Wrapper ---
|
||||
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
|
||||
// LuaMemberFunctionWrapper<GameObjectTypeWrapper> layout (ctor always inlined)
|
||||
uintptr_t lmfw_vftable; // LuaMemberFunctionWrapper<>::vftable
|
||||
@@ -69,6 +72,8 @@ constexpr LuaRVAs RVAs_StarWarsI = {
|
||||
0x1453310, // LuaValue<basic_string<>>::vftable
|
||||
0x78, // LuaValue<basic_string<>> allocation size
|
||||
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
|
||||
|
||||
// --- TheGameText ---
|
||||
@@ -87,6 +92,7 @@ constexpr LuaRVAs RVAs_StarWarsI = {
|
||||
|
||||
// --- Game Object Type Wrapper ---
|
||||
0x004f6c9, // GameObjectTypeWrapper::GameObjectTypeWrapper()
|
||||
0x70, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
|
||||
0x160, // GameObjectTypeClass::TextNameID field offset
|
||||
0x158a680, // LuaMemberFunctionWrapper<>::vftable
|
||||
0x0074f55, // LuaUserVar::LuaUserVar
|
||||
@@ -118,6 +124,8 @@ constexpr LuaRVAs RVAs_StarWarsG = {
|
||||
0x0855a98, // LuaValue<basic_string<>>::vftable
|
||||
0x30, // LuaValue<basic_string<>> allocation size
|
||||
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
|
||||
|
||||
// --- TheGameText ---
|
||||
@@ -136,6 +144,7 @@ constexpr LuaRVAs RVAs_StarWarsG = {
|
||||
|
||||
// --- Game Object Type Wrapper ---
|
||||
0x0604a10, // GameObjectTypeWrapper::GameObjectTypeWrapper()
|
||||
0x28, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
|
||||
0x120, // GameObjectTypeClass::TextNameID field offset
|
||||
0x08ab770, // LuaMemberFunctionWrapper<>::vftable
|
||||
0x0249c20, // LuaUserVar::LuaUserVar
|
||||
|
||||
Reference in New Issue
Block a user