16 Commits
v0.1 ... main

Author SHA1 Message Date
8e3235b7d4 Merge pull request 'Enable Get_Display_Name in StarWarsG' (#3) from display-text into main
Reviewed-on: http://gitea.drewcav.com/drew/FoCAPI/pulls/3
2026-03-14 22:41:23 -05:00
4987ee81f6 Fix display text in StarWarsG 2026-03-14 22:40:13 -05:00
67adfd1e08 Use inline ctors for GameObjectTypeWrapper, LuaMemberFunctionWrapper 2026-03-14 13:04:22 -05:00
7deb2ff293 Support in StarWarsG for display text 2026-03-13 11:44:25 -05:00
f7b488cbf7 Update CLAUDE.md 2026-03-12 18:22:28 -05:00
9670037eae Update README.md 2026-03-12 18:19:51 -05:00
958dff3a7e Merge pull request 'Add GameObjectTypeWrapper Get_Display_Name() function' (#2) from text-id into main
Reviewed-on: http://gitea.drewcav.com/drew/FoCAPI/pulls/2
2026-03-12 18:17:45 -05:00
3215ecb9f6 Add GameObjectTypeWrapper Get_Display_Name() function 2026-03-12 18:09:46 -05:00
70b5191fcd Update README.md 2026-03-09 23:33:55 -05:00
832691e864 Merge pull request 'Adds Add_Tutorial_Text and Remove_Tutorial_Text Lua functions' (#1) from tutorial-text into main
Reviewed-on: http://gitea.drewcav.com/drew/FoCAPI/pulls/1
2026-03-09 22:52:00 -05:00
5ec3a9a758 Add RVAs for StarWarsG 2026-03-09 22:48:55 -05:00
98cdafa094 Update README.md for new functions 2026-03-09 22:26:21 -05:00
14f02697be Dumb down the function -- let Lua control the tutorial pane 2026-03-09 22:17:35 -05:00
a2fda59eeb Fix compiler error 2026-03-09 20:16:56 -05:00
Drew Cavanaugh
cce3245e4b Add Screen Text Lua functions
Committer: Drew C. <drewcav96@gmail.com>
2026-03-09 19:54:41 -05:00
b16f6b8ee1 Update StarWarsG RVAs 2026-03-01 20:03:55 -06:00
14 changed files with 501 additions and 211 deletions

View File

@@ -2,21 +2,22 @@
## Project Overview ## Project Overview
This project is a hook API for Star Wars: Empire at War: Forces of Corruption to expose custom Lua functions available for scripting. This project is a DLL hook for Star Wars: Empire at War: Forces of Corruption to add custom Lua functions available for scripting.
## Client and Debug Kits ## Client and Debug Kits
- StarWarsG.exe -- the binary shipped with the game for all clients. PDB unavailable. - StarWarsG.exe -- the binary shipped with the game for all clients. PDB unavailable.
- StarWarsI.exe -- the debug kit binary which comes with debug functionality but is too slow to run normally. PDB available. - StarWarsI.exe -- the debug kit binary which comes with debug functionality but is too slow to run normally. PDB available.
- Both binares are 64-bit Windows portable executables.
## DLL Hook ## DLL Hook
- Uses MinHook to create the DLL hook into the game via powerprof.dll - Uses MinHook to create the DLL hook into the game via powerprof.dll
- Using IDA Free to find addresses of function calls. - Using Ghidra to find addresses of function calls from StarWarsI and map them using Version Control to addresses in StarWarsG.
## Empire at War Lua Scripting Engine ## Empire at War Lua Scripting Engine
The source code for the Lua scripting engine in Empire at War was leaked at: https://github.com/PetroglyphGames/GlyphX-Reference. Use this as a guide for what Lua functions and variables are already available, and how the engine hooks Lua scripting into the rest of the game. No other engine source code is available. The source code for parts of the Lua scripting engine in Empire at War was leaked at and can be accessed [here](../GlyphX-Reference-master/Project1/Code/). Use this as a guide for what Lua functions and variables are already available, and how the engine hooks Lua scripting into the rest of the game.
## Adding a New Lua Function ## Adding a New Lua Function

View File

@@ -166,6 +166,8 @@
<ClCompile Include="functions\functions.cpp" /> <ClCompile Include="functions\functions.cpp" />
<ClCompile Include="functions\file_io.cpp" /> <ClCompile Include="functions\file_io.cpp" />
<ClCompile Include="functions\players.cpp" /> <ClCompile Include="functions\players.cpp" />
<ClCompile Include="functions\screen_text.cpp" />
<ClCompile Include="functions\game_object_type.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="powrprof.def" /> <None Include="powrprof.def" />

View File

@@ -50,12 +50,18 @@
<ClCompile Include="functions\players.cpp"> <ClCompile Include="functions\players.cpp">
<Filter>Source Files\Functions</Filter> <Filter>Source Files\Functions</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="functions\screen_text.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
<ClCompile Include="functions\file_io.cpp"> <ClCompile Include="functions\file_io.cpp">
<Filter>Source Files\Functions</Filter> <Filter>Source Files\Functions</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="functions\functions.cpp"> <ClCompile Include="functions\functions.cpp">
<Filter>Source Files\Functions</Filter> <Filter>Source Files\Functions</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="functions\game_object_type.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="powrprof.def"> <None Include="powrprof.def">

View File

@@ -4,10 +4,27 @@ A powrprof.dll hook which adds custom Lua functions to Forces of Corruption.
## Usage ## Usage
Drop powrprof.dll and MinHooks.x64.dll into the `/corruption/` folder of your Empire at War installation. Drop powrprof.dll and MinHooks.x64.dll into the `/corruption` folder of your Empire at War installation.
Currently only works with the debug kit `StarWarsI.exe`; `StarWarsG.exe` support is coming soon. Works with both the debug kit `StarWarsI.exe` and client version `StarWarsG.exe` of the Steam version.
Not supported on disc / GoG versions (sorry).
## Functions ## Global Functions
- Global `WriteToFile(str a, str b)` - Appends a line of text `b` to the file name `a` in the game directory. - Global `WriteToFile(file, text)` - Appends a line of text specified file in the game directory.
- Parameter `file` string - The file name to append.
- Parameter `text` string - The text line to append.
- Global `Get_All_Players()` - Returns a 1-based Lua table of all active players, iterable with `ipairs()`. Each entry is the same PlayerWrapper type returned by `Find_Player()`. - Global `Get_All_Players()` - Returns a 1-based Lua table of all active players, iterable with `ipairs()`. Each entry is the same PlayerWrapper type returned by `Find_Player()`.
- Global `Add_Tutorial_Text(key, text, duration, r, g, b, a)` - Adds a line to the tutorial text pane.
- Parameter `key`: string - The key used to identify this line, later can be used to update or remove the text.
- Parameter `text`: string - The text to display.
- Parameter `duration` number? - The duration to display the text. Default: -1 (infinite)
- Parameter `r` number? - The red color component. Default: 1
- Parameter `g` number? - The green color component. Default: 1
- Parameter `b` number? - The blue color component. Default: 1
- Parameter `a` number? - The alpha component. Default: 1
- Global `Remove_Tutorial_Text(key)` - Removes the line associated with the key.
- Parameter `key` string - The key used to identify the line when it was added.
## GameObjectTypeWrapper
- `Get_Display_Name()` - Returns the localized text string display name of the object type.

View File

@@ -10,4 +10,5 @@
void Register_All(lua_State* L) { void Register_All(lua_State* L) {
Register_FileIO(L); Register_FileIO(L);
Register_Players(L); Register_Players(L);
Register_ScreenText(L);
} }

View File

@@ -10,3 +10,8 @@ void Register_All(lua_State* L);
// Each checks its own required function pointer guards before registering. // Each checks its own required function pointer guards before registering.
void Register_FileIO(lua_State* L); void Register_FileIO(lua_State* L);
void Register_Players(lua_State* L); void Register_Players(lua_State* L);
void Register_ScreenText(lua_State* L);
// Called from the GameObjectTypeWrapper ctor hook (not from Register_All).
// Registers custom member functions on each wrapper instance.
void Register_GOTMembers(void* wrapper);

View File

@@ -0,0 +1,109 @@
#include "functions.h"
#include "../lua_hook.h"
#include <windows.h>
#include <string>
#include <cstring>
// 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.
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 {
union {
wchar_t buf[8]; // SSO buffer (up to 7 wchars + null)
wchar_t* ptr; // heap pointer when capacity > 7
};
size_t size;
size_t capacity; // <= 7 means SSO, > 7 means heap
const wchar_t* c_str() const { return (capacity <= 7) ? buf : ptr; }
};
// Custom member function registered on GameObjectTypeWrapper.
// Signature matches LuaTable* __thiscall (LuaScriptClass*, LuaTable*).
// 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
void* got_class = *(void**)((char*)this_wrapper + g_rvas->got_object_offset);
if (!got_class)
return nullptr;
// 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)
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
const wchar_t* wdata = display_wstr->c_str();
int wlen = (int)display_wstr->size;
int narrow_len = WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, nullptr, 0, nullptr, nullptr);
if (narrow_len <= 0)
return nullptr;
std::string narrow(narrow_len, '\0');
WideCharToMultiByte(CP_UTF8, 0, wdata, wlen, &narrow[0], narrow_len, nullptr, nullptr);
// 5. Create a LuaString and return it
void* lua_value = CreateLuaValueString(narrow);
if (!lua_value)
return nullptr;
return pfn_return_variable(this_wrapper, lua_value);
}
void Register_GOTMembers(void* wrapper)
{
if (!pfn_lua_uservar_ctor || !pfn_register_member)
return;
if (!g_text_name_id_offset || !pfn_game_text_get || !g_the_game_text)
return;
if (!pfn_return_variable || !g_rvas || !g_rvas->lmfw_vftable)
return;
const LuaRVAs* rvas = g_rvas;
// Allocate a LuaMemberFunctionWrapper<GameObjectTypeWrapper>.
void* lmfw = ::operator new(rvas->lmfw_alloc_size);
if (!lmfw) return;
memset(lmfw, 0, rvas->lmfw_alloc_size);
// 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);
// 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);
}

91
functions/screen_text.cpp Normal file
View File

@@ -0,0 +1,91 @@
#include "functions.h"
#include "../lua_hook.h"
#include <windows.h>
#include <string>
// Add_Tutorial_Text(key, text [, duration [, r, g, b, a]])
// duration defaults to -1 (perpetual). r/g/b/a default to 1.0 (white, opaque).
// The color pointer must be non-null: the game copies 16 bytes from it unconditionally
// 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;
float duration = -1.0f;
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
if (pfn_tonumber)
{
double arg3 = pfn_tonumber(L, 3);
if (arg3 != 0.0) duration = (float)arg3;
double r = pfn_tonumber(L, 4);
double g = pfn_tonumber(L, 5);
double b = pfn_tonumber(L, 6);
double a = pfn_tonumber(L, 7);
if (r != 0.0 || g != 0.0 || b != 0.0 || a != 0.0)
{
color[0] = (float)r;
color[1] = (float)g;
color[2] = (float)b;
color[3] = (float)a;
}
}
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;
}
// Remove_Tutorial_Text(key)
static int L_Remove_Tutorial_Text(lua_State* L)
{
const char* key = pfn_tostring(L, 1);
if (!key)
return 0;
pfn_remove_tutorial_text(g_command_bar, (char*)key);
return 0;
}
void Register_ScreenText(lua_State* L)
{
if (!pfn_add_tutorial_text || !pfn_remove_tutorial_text || !g_command_bar)
return;
register_global(L, "Add_Tutorial_Text", L_Add_Tutorial_Text);
register_global(L, "Remove_Tutorial_Text", L_Remove_Tutorial_Text);
}

View File

@@ -2,6 +2,32 @@
#include "functions/functions.h" #include "functions/functions.h"
#include <MinHook.h> #include <MinHook.h>
#include <windows.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 // Resolved function pointers
fn_lua_open real_lua_open = nullptr; fn_lua_open real_lua_open = nullptr;
@@ -17,7 +43,19 @@ void* g_player_list = nullptr;
fn_PlayerWrapperCreate pfn_player_wrapper_create = nullptr; fn_PlayerWrapperCreate pfn_player_wrapper_create = nullptr;
fn_GetScriptFromState pfn_get_script_from_state = nullptr; fn_GetScriptFromState pfn_get_script_from_state = nullptr;
fn_MapVarToLua pfn_map_var_to_lua = 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_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) { void register_global(lua_State* L, const char* name, lua_CFunction fn) {
pfn_pushstring(L, name); pfn_pushstring(L, name);
@@ -25,6 +63,55 @@ void register_global(lua_State* L, const char* name, lua_CFunction fn) {
pfn_settable(L, LUA_GLOBALSINDEX); 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 // Fired every time the game creates a new Lua state
static lua_State* Hook_lua_open() { static lua_State* Hook_lua_open() {
lua_State* L = real_lua_open(); lua_State* L = real_lua_open();
@@ -55,6 +142,8 @@ bool LuaHook_Init() {
return false; return false;
uintptr_t base = (uintptr_t)GetModuleHandleA(nullptr); uintptr_t base = (uintptr_t)GetModuleHandleA(nullptr);
g_rvas = rvas;
g_base = base;
pfn_pushstring = (fn_lua_pushstring)(base + rvas->lua_pushstring); pfn_pushstring = (fn_lua_pushstring)(base + rvas->lua_pushstring);
pfn_pushcclosure = (fn_lua_pushcclosure)(base + rvas->lua_pushcclosure); pfn_pushcclosure = (fn_lua_pushcclosure)(base + rvas->lua_pushcclosure);
@@ -72,9 +161,34 @@ bool LuaHook_Init() {
pfn_map_var_to_lua = (fn_MapVarToLua)(base + rvas->map_var_to_lua); 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) if (rvas->debug_print)
pfn_debug_print = (fn_DebugPrint)(base + 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) { if (rvas->lua_newtable && rvas->lua_rawseti) {
pfn_newtable = (fn_lua_newtable)(base + rvas->lua_newtable); pfn_newtable = (fn_lua_newtable)(base + rvas->lua_newtable);
pfn_rawseti = (fn_lua_rawseti)(base + rvas->lua_rawseti); pfn_rawseti = (fn_lua_rawseti)(base + rvas->lua_rawseti);
@@ -94,6 +208,18 @@ bool LuaHook_Init() {
return false; 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; return true;
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "rvas.h" #include "rvas.h"
#include <string>
struct lua_State; struct lua_State;
typedef int (*lua_CFunction)(lua_State*); typedef int (*lua_CFunction)(lua_State*);
@@ -17,7 +18,20 @@ typedef void* (*fn_GetPlayerByIndex)(void*, int); // PlayerListC
typedef void* (*fn_PlayerWrapperCreate)(void*, void*); // PlayerWrapper::Create(PlayerClass*, LuaScriptClass*) typedef void* (*fn_PlayerWrapperCreate)(void*, void*); // PlayerWrapper::Create(PlayerClass*, LuaScriptClass*)
typedef void* (*fn_GetScriptFromState)(lua_State*); // LuaScriptClass::Get_Script_From_State typedef void* (*fn_GetScriptFromState)(lua_State*); // LuaScriptClass::Get_Script_From_State
typedef void (*fn_MapVarToLua)(lua_State*, void*); // LuaScriptClass::Map_Var_To_Lua (static) typedef void (*fn_MapVarToLua)(lua_State*, void*); // LuaScriptClass::Map_Var_To_Lua (static)
typedef void (*fn_AddTutorialText)(void*, void*, const char*, float, unsigned int, bool, void*); // CommandBarClass::Add_Tutorial_Text (inner)
typedef void (*fn_RemoveTutorialText)(void*, const char*); // CommandBarClass::Remove_Tutorial_Text
typedef void (*fn_DebugPrint)(const char*); // Debug_Print typedef void (*fn_DebugPrint)(const char*); // Debug_Print
// Scripting internals (LuaUserVar) -- usable by any wrapper module
typedef void (*fn_RegisterMember)(void* uservar, const char* name, void* fn_ptr); // LuaUserVar::Register_Member
typedef void* (*fn_ReturnVariable)(void* uservar, void* lua_var); // LuaUserVar::Return_Variable(LuaVar*) -> LuaTable*
// 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*
// Resolved function pointers (defined in lua_hook.cpp) // Resolved function pointers (defined in lua_hook.cpp)
extern fn_lua_open real_lua_open; extern fn_lua_open real_lua_open;
@@ -33,12 +47,33 @@ extern void* g_player_list;
extern fn_PlayerWrapperCreate pfn_player_wrapper_create; extern fn_PlayerWrapperCreate pfn_player_wrapper_create;
extern fn_GetScriptFromState pfn_get_script_from_state; extern fn_GetScriptFromState pfn_get_script_from_state;
extern fn_MapVarToLua pfn_map_var_to_lua; extern fn_MapVarToLua pfn_map_var_to_lua;
extern fn_AddTutorialText pfn_add_tutorial_text;
extern fn_RemoveTutorialText pfn_remove_tutorial_text;
extern void* g_command_bar;
extern fn_DebugPrint pfn_debug_print; extern fn_DebugPrint pfn_debug_print;
extern fn_RegisterMember pfn_register_member;
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;
extern uintptr_t g_base;
constexpr int LUA_GLOBALSINDEX = -10001; constexpr int LUA_GLOBALSINDEX = -10001;
// Equivalent of lua_setglobal — registers a C function in the Lua global table // Equivalent of lua_setglobal — registers a C function in the Lua global table
void register_global(lua_State* L, const char* name, lua_CFunction fn); void register_global(lua_State* L, const char* name, lua_CFunction fn);
// Creates a LuaValue<basic_string<>> from a std::string.
// 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(); bool LuaHook_Init();
void LuaHook_Shutdown(); void LuaHook_Shutdown();

Binary file not shown.

Binary file not shown.

View File

@@ -1,185 +0,0 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszProcName [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszProcName [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
// ppTarget [out] A pointer to the target function, which will be used
// with other functions.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
// Removes an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// Enables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// enabled in one go.
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// Disables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// disabled in one go.
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// Queues to enable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be enabled.
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// Queues to disable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be disabled.
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// Applies all queued changes in one go.
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// Translates the MH_STATUS to its name as a string.
const char * WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif

120
rvas.h
View File

@@ -12,14 +12,42 @@ struct LuaRVAs {
uintptr_t lua_newtable; uintptr_t lua_newtable;
uintptr_t lua_rawseti; uintptr_t lua_rawseti;
// --- Scripts ---
uintptr_t get_script_from_state; // LuaScriptClass::Get_Script_From_State (static)
uintptr_t map_var_to_lua; // LuaScriptClass::Map_Var_To_Lua (member)
uintptr_t register_member; // LuaUserVar::Register_Member(char*, LuaVar*)
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 ---
uintptr_t the_game_text; // TheGameText global object (data, not a function)
uintptr_t get_string; // GameTextClass::Get(char*, bool)
// --- Players --- // --- Players ---
uintptr_t get_player_by_index; // PlayerListClass::Get_Player_By_Index uintptr_t get_player_by_index; // PlayerListClass::Get_Player_By_Index
uintptr_t player_list; // PlayerList global object (data, not a function) uintptr_t player_list; // PlayerList global object (data, not a function)
uintptr_t player_wrapper_create; // PlayerWrapper::Create (static) uintptr_t player_wrapper_create; // PlayerWrapper::Create (static)
// --- Scripts --- // --- Screen Text ---
uintptr_t get_script_from_state; // LuaScriptClass::Get_Script_From_State (static) uintptr_t command_bar; // TheCommandBar global object (data, not a function)
uintptr_t map_var_to_lua; // LuaScriptClass::Map_Var_To_Lua (member) uintptr_t add_tutorial_text; // CommandBarClass::Add_Tutorial_Text (inner overload: wstring*, char*, float, uint, bool, RGBAClass*)
uintptr_t remove_tutorial_text; // CommandBarClass::Remove_Tutorial_Text(char*)
// --- 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
uintptr_t lua_uservar_ctor; // LuaUserVar::LuaUserVar(int, bool)
size_t lmfw_alloc_size; // allocation size of LuaMemberFunctionWrapper<>
size_t lmfw_pmf_offset; // offset of MemberFunction (16-byte PMF)
size_t lmfw_object_offset; // offset of Object (wrapper pointer)
size_t lmfw_usemaps_offset; // offset of UseMaps (bool)
// --- Debug --- // --- Debug ---
uintptr_t debug_print; // Debug_Print(char*) uintptr_t debug_print; // Debug_Print(char*)
@@ -37,14 +65,41 @@ constexpr LuaRVAs RVAs_StarWarsI = {
0x12AC460, // lua_newtable 0x12AC460, // lua_newtable
0x12ACD70, // lua_rawseti 0x12ACD70, // lua_rawseti
// --- Players ---
0x06FA990, // PlayerListClass::Get_Player_By_Index
0x1913158, // PlayerList global object
0x0F12010, // PlayerWrapper::Create
// --- Scripts --- // --- Scripts ---
0x0537BB0, // LuaScriptClass::Get_Script_From_State 0x0537BB0, // LuaScriptClass::Get_Script_From_State
0x0536FE0, // LuaScriptClass::Map_Var_To_Lua 0x0536FE0, // LuaScriptClass::Map_Var_To_Lua
0x0046f51, // LuaUserVar::Register_Member
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 ---
0x18578b0, // TheGameText global object
0x002b4cc, // GameTextClass::Get
// --- Players ---
0x000b0e1, // PlayerListClass::Get_Player_By_Index
0x1913158, // PlayerList global object
0x0F12010, // PlayerWrapper::Create
// --- Screen Text ---
0x1916b80, // TheCommandBar global object
0x003c83a, // CommandBarClass::Add_Tutorial_Text inner
0x006e1eb, // CommandBarClass::Remove_Tutorial_Text
// --- Game Object Type Wrapper ---
0x004f6c9, // GameObjectTypeWrapper::GameObjectTypeWrapper()
0x70, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
0x160, // GameObjectTypeClass::TextNameID field offset
0x158a680, // LuaMemberFunctionWrapper<>::vftable
0x0074f55, // LuaUserVar::LuaUserVar
0x88, // LuaMemberFunctionWrapper<> allocation size
0x68, // MemberFunction offset
0x78, // Object offset
0x80, // UseMaps offset
// --- Debug --- // --- Debug ---
0x0476AE0, // Debug_Print 0x0476AE0, // Debug_Print
@@ -58,19 +113,46 @@ constexpr LuaRVAs RVAs_StarWarsG = {
0x07b9340, // lua_pushcclosure 0x07b9340, // lua_pushcclosure
0x07b9a60, // lua_settable 0x07b9a60, // lua_settable
0x07b9cc0, // lua_tostring 0x07b9cc0, // lua_tostring
0, // lua_tonumber (TODO) 0x07b9bc0, // lua_tonumber
0, // lua_newtable (TODO) 0x07b9140, // lua_newtable
0, // lua_rawseti (TODO) 0x07b9820, // lua_rawseti
// --- Players ---
0, // PlayerListClass::Get_Player_By_Index (TODO)
0, // PlayerList global object (TODO)
0, // PlayerWrapper::Create (TODO)
// --- Scripts --- // --- Scripts ---
0, // LuaScriptClass::Get_Script_From_State (TODO) 0x0245790, // LuaScriptClass::Get_Script_From_State
0, // LuaScriptClass::Map_Var_To_Lua (TODO) 0x0247700, // LuaScriptClass::Map_Var_To_Lua
0x024be40, // LuaUserVar::Register_Member
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 ---
0x0a7bc58, // TheGameText global object
0x01fa680, // GameTextClass::Get
// --- Players ---
0x0294bc0, // PlayerListClass::Get_Player_By_Index
0x0a16fd0, // PlayerList global object
0x06019f0, // PlayerWrapper::Create
// --- Screen Text ---
0x0b27f60, // TheCommandBar global object
0x02dfd10, // CommandBarClass::Add_Tutorial_Text inner
0x02ff290, // CommandBarClass::Remove_Tutorial_Text
// --- Game Object Type Wrapper ---
0x0604a10, // GameObjectTypeWrapper::GameObjectTypeWrapper()
0x28, // SmartPtr<GameObjectTypeClass> offset within GameObjectTypeWrapper
0x120, // GameObjectTypeClass::TextNameID field offset
0x08ab770, // LuaMemberFunctionWrapper<>::vftable
0x0249c20, // LuaUserVar::LuaUserVar
0x48, // LuaMemberFunctionWrapper<> allocation size
0x28, // MemberFunction offset
0x38, // Object offset
0x40, // UseMaps offset
// --- Debug --- // --- Debug ---
0, // Debug_Print (TODO) 0, // Debug_Print (not mapped)
}; };