Files
FoCAPI/lua_hook.cpp
2026-03-14 22:40:13 -05:00

230 lines
7.6 KiB
C++

#include "lua_hook.h"
#include "functions/functions.h"
#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;
// Resolved function pointers
fn_lua_open real_lua_open = nullptr;
fn_lua_pushstring pfn_pushstring = nullptr;
fn_lua_pushcclosure pfn_pushcclosure = nullptr;
fn_lua_settable pfn_settable = nullptr;
fn_lua_tostring pfn_tostring = nullptr;
fn_lua_tonumber pfn_tonumber = nullptr;
fn_lua_newtable pfn_newtable = nullptr;
fn_lua_rawseti pfn_rawseti = nullptr;
fn_GetPlayerByIndex pfn_get_player_by_index = nullptr;
void* g_player_list = nullptr;
fn_PlayerWrapperCreate pfn_player_wrapper_create = nullptr;
fn_GetScriptFromState pfn_get_script_from_state = nullptr;
fn_MapVarToLua pfn_map_var_to_lua = nullptr;
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_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;
void register_global(lua_State* L, const char* name, lua_CFunction fn) {
pfn_pushstring(L, name);
pfn_pushcclosure(L, fn, 0);
pfn_settable(L, LUA_GLOBALSINDEX);
}
void* CreateLuaValueString(const std::string& str) {
if (!g_rvas || !g_rvas->lua_value_string_vftable)
return nullptr;
void* obj = ::operator new(g_rvas->lua_value_string_size);
if (!obj)
return nullptr;
memset(obj, 0, g_rvas->lua_value_string_size);
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;
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;
}
// 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();
if (!L) {
return L;
}
Register_All(L);
return L;
}
bool LuaHook_Init() {
char exePath[MAX_PATH];
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
const LuaRVAs* rvas = nullptr;
if (strstr(exePath, "StarWarsI.exe"))
rvas = &RVAs_StarWarsI;
else if (strstr(exePath, "StarWarsG.exe"))
rvas = &RVAs_StarWarsG;
else
return false;
if (!rvas->lua_open || !rvas->lua_pushstring || !rvas->lua_pushcclosure ||
!rvas->lua_settable || !rvas->lua_tostring)
return false;
uintptr_t base = (uintptr_t)GetModuleHandleA(nullptr);
g_rvas = rvas;
g_base = base;
pfn_pushstring = (fn_lua_pushstring)(base + rvas->lua_pushstring);
pfn_pushcclosure = (fn_lua_pushcclosure)(base + rvas->lua_pushcclosure);
pfn_settable = (fn_lua_settable)(base + rvas->lua_settable);
pfn_tostring = (fn_lua_tostring)(base + rvas->lua_tostring);
if (rvas->lua_tonumber && rvas->get_player_by_index && rvas->player_list &&
rvas->player_wrapper_create && rvas->get_script_from_state && rvas->map_var_to_lua)
{
pfn_tonumber = (fn_lua_tonumber)(base + rvas->lua_tonumber);
pfn_get_player_by_index = (fn_GetPlayerByIndex)(base + rvas->get_player_by_index);
g_player_list = (void*)(base + rvas->player_list);
pfn_player_wrapper_create = (fn_PlayerWrapperCreate)(base + rvas->player_wrapper_create);
pfn_get_script_from_state = (fn_GetScriptFromState)(base + rvas->get_script_from_state);
pfn_map_var_to_lua = (fn_MapVarToLua)(base + rvas->map_var_to_lua);
}
if (rvas->command_bar && rvas->add_tutorial_text && rvas->remove_tutorial_text) {
g_command_bar = (void*)(base + rvas->command_bar);
pfn_add_tutorial_text = (fn_AddTutorialText)(base + rvas->add_tutorial_text);
pfn_remove_tutorial_text = (fn_RemoveTutorialText)(base + rvas->remove_tutorial_text);
}
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->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)
pfn_game_text_get = (fn_GameTextGet)(base + rvas->get_string);
if (rvas->got_text_name_id_offset)
g_text_name_id_offset = rvas->got_text_name_id_offset;
if (rvas->lua_uservar_ctor)
pfn_lua_uservar_ctor = (fn_LuaUserVarCtor)(base + rvas->lua_uservar_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);
}
void* const target = (void*)(base + rvas->lua_open);
if (MH_Initialize() != MH_OK) {
return false;
}
if (MH_CreateHook(target, &Hook_lua_open, (void**)&real_lua_open) != MH_OK) {
return false;
}
if (MH_EnableHook(target) != MH_OK) {
return false;
}
// Hook GameObjectTypeWrapper constructor to register custom member functions
if (rvas->got_wrapper_ctor && rvas->lmfw_vftable && pfn_lua_uservar_ctor &&
pfn_register_member &&
g_text_name_id_offset && pfn_game_text_get && g_the_game_text &&
rvas->lua_value_string_vftable && 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;
}
void LuaHook_Shutdown() {
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}