Fix display text in StarWarsG
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
lua_hook.cpp
57
lua_hook.cpp
@@ -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)
|
||||||
|
|||||||
@@ -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
9
rvas.h
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user