20 Commits

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
533390606e Include MinHook as a statically linked library 2026-03-01 17:41:52 -06:00
952c233592 Rename to FoCAPI 2026-03-01 17:10:27 -06:00
f7024acd8b Add Get_All_Players() Lua function 2026-03-01 16:36:37 -06:00
7d642a8441 Ignore .claude 2026-03-01 16:36:24 -06:00
20 changed files with 864 additions and 290 deletions

4
.gitignore vendored
View File

@@ -2,8 +2,10 @@ x64/
powrprof/ powrprof/
.vscode/ .vscode/
.vs/ .vs/
.claude/
minhook/
packages/
*.vcxproj.filters
*.vcxproj.user *.vcxproj.user
# Prerequisites # Prerequisites

View File

@@ -1,19 +1,28 @@
# SWEaW Hooks # FoCAPI
## 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
1. Create `functions/<module>.cpp` — implement `L_*` function(s) and `Register_<Module>(lua_State* L)`
2. Add `void Register_<Module>(lua_State* L);` to `functions/functions.h`
3. Add `Register_<Module>(L);` to `functions/functions.cpp`
4. Add new RVAs (if needed) to `rvas.h` in the relevant section, for each supported executable
5. Add `functions\<module>.cpp` to `FoCAPI.vcxproj` and `FoCAPI.vcxproj.filters`

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.14.36127.28 d17.14 VisualStudioVersion = 17.14.36127.28 d17.14
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SWEAW Hooks", "SWEAW Hooks.vcxproj", "{F2C4477F-C9F1-4AF0-A04C-9D6ADD14E783}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FoCAPI", "FoCAPI.vcxproj", "{F2C4477F-C9F1-4AF0-A04C-9D6ADD14E783}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -22,7 +22,7 @@
<VCProjectVersion>17.0</VCProjectVersion> <VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
<ProjectGuid>{f2c4477f-c9f1-4af0-a04c-9d6add14e783}</ProjectGuid> <ProjectGuid>{f2c4477f-c9f1-4af0-a04c-9d6add14e783}</ProjectGuid>
<RootNamespace>SWEAWHooks</RootNamespace> <RootNamespace>FoCAPI</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>powrprof</ProjectName> <ProjectName>powrprof</ProjectName>
</PropertyGroup> </PropertyGroup>
@@ -50,7 +50,6 @@
<ConfigurationType>DynamicLibrary</ConfigurationType> <ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset> <PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@@ -71,6 +70,11 @@
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>packages\minhook.1.3.3\lib\native\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;$(IncludePath)</IncludePath> <IncludePath>C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;$(IncludePath)</IncludePath>
<LibraryPath>C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt\x64;$(LibraryPath)</LibraryPath> <LibraryPath>C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt\x64;$(LibraryPath)</LibraryPath>
@@ -83,7 +87,7 @@
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;SWEAWHOOKS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;FOCAPI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader> <PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@@ -92,6 +96,7 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC> <EnableUAC>false</EnableUAC>
<AdditionalDependencies>packages\minhook.1.3.3\lib\native\lib\libMinHook-x86-v141-mdd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -100,7 +105,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;SWEAWHOOKS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;FOCAPI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader> <PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@@ -109,13 +114,14 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC> <EnableUAC>false</EnableUAC>
<AdditionalDependencies>packages\minhook.1.3.3\lib\native\lib\libMinHook-x86-v141-md.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;SWEAWHOOKS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;FOCAPI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@@ -124,8 +130,8 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC> <EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>minhook\bin\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>powrprof.def</ModuleDefinitionFile> <ModuleDefinitionFile>powrprof.def</ModuleDefinitionFile>
<AdditionalDependencies>packages\minhook.1.3.3\lib\native\lib\libMinHook-x64-v141-mdd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -134,7 +140,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;SWEAWHOOKS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;FOCAPI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@@ -143,18 +149,25 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC> <EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>minhook\bin\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>powrprof.def</ModuleDefinitionFile> <ModuleDefinitionFile>powrprof.def</ModuleDefinitionFile>
<AdditionalDependencies>packages\minhook.1.3.3\lib\native\lib\libMinHook-x64-v141-md.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="lua_hook.h" /> <ClInclude Include="lua_hook.h" />
<ClInclude Include="proxy.h" /> <ClInclude Include="proxy.h" />
<ClInclude Include="rvas.h" />
<ClInclude Include="functions\functions.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="dllmain.cpp" /> <ClCompile Include="dllmain.cpp" />
<ClCompile Include="lua_hook.cpp" /> <ClCompile Include="lua_hook.cpp" />
<ClCompile Include="proxy.cpp" /> <ClCompile Include="proxy.cpp" />
<ClCompile Include="functions\functions.cpp" />
<ClCompile Include="functions\file_io.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" />

71
FoCAPI.vcxproj.filters Normal file
View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="functions">
<UniqueIdentifier>{B3D2F8A1-4E7C-4D9A-8F23-1C5A6E2B9D47}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Functions">
<UniqueIdentifier>{2e9ee1bb-cc3a-49e1-8942-619ea103a1bd}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Functions">
<UniqueIdentifier>{590318ee-4f40-4183-bc33-5ee14f70c131}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="lua_hook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="proxy.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="rvas.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="functions\functions.h">
<Filter>Header Files\Functions</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="proxy.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="lua_hook.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="functions\players.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
<ClCompile Include="functions\screen_text.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
<ClCompile Include="functions\file_io.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
<ClCompile Include="functions\functions.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
<ClCompile Include="functions\game_object_type.cpp">
<Filter>Source Files\Functions</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="powrprof.def">
<Filter>Source Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@@ -1,12 +1,30 @@
# SWEaW Hooks # FoCAPI
A powrprof.dll hook which adds custom Lua functions to Forces of Corruption. 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 `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

@@ -12,8 +12,9 @@ BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) {
} }
if (!LuaHook_Init()) { if (!LuaHook_Init()) {
OutputDebugStringA("[SWEAW Hooks] WARNING: LuaHook_Init() failed - Lua hooks are not active.\n"); OutputDebugStringA("[FoCAPI] WARNING: LuaHook_Init() failed - Lua hooks are not active.\n");
} }
break; break;
case DLL_PROCESS_DETACH: case DLL_PROCESS_DETACH:

37
functions/file_io.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "functions.h"
#include "../lua_hook.h"
#include <windows.h>
// WriteToFile(path, text) - appends text to a file
static int L_WriteToFile(lua_State* L) {
const char* path = pfn_tostring(L, 1);
const char* text = pfn_tostring(L, 2);
if (!path || !text) {
return 0;
}
HANDLE hFile = CreateFileA(
path,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD written;
SetFilePointer(hFile, 0, NULL, FILE_END);
WriteFile(hFile, text, lstrlenA(text), &written, NULL);
WriteFile(hFile, "\n", 1, &written, NULL);
CloseHandle(hFile);
}
return 0;
}
void Register_FileIO(lua_State* L) {
register_global(L, "WriteToFile", L_WriteToFile);
}

14
functions/functions.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "functions.h"
// To add a new Lua function module:
// 1. Create functions/<module>.cpp with L_* implementation(s) and Register_<Module>()
// 2. Declare void Register_<Module>(lua_State* L); in functions.h
// 3. Call Register_<Module>(L); below
// 4. Add new RVAs (if needed) to rvas.h in the relevant section
// 5. Add functions\<module>.cpp to FoCAPI.vcxproj and .vcxproj.filters
void Register_All(lua_State* L) {
Register_FileIO(L);
Register_Players(L);
Register_ScreenText(L);
}

17
functions/functions.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
struct lua_State;
// Registers all custom Lua functions into the given state.
// Called once per lua_open() from Hook_lua_open().
void Register_All(lua_State* L);
// Per-module registration functions.
// Each checks its own required function pointer guards before registering.
void Register_FileIO(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);
}

71
functions/players.cpp Normal file
View File

@@ -0,0 +1,71 @@
#include "functions.h"
#include "../lua_hook.h"
#include <windows.h>
// Offsets for reading PlayersByID count directly from the PlayerList object.
// PlayersByID (DynamicVectorClass) is at +0x18 within PlayerListClass.
// The active count field is at +0x10 within DynamicVectorClass.
static constexpr int OFFSET_PLAYERS_BY_ID = 0x18;
static constexpr int OFFSET_VECTOR_COUNT = 0x10;
// Get_All_Players() - returns a 1-based Lua table of PlayerWrappers for all players,
// iterable with ipairs(). Identical wrapper type to Find_Player().
static int L_Get_All_Players(lua_State* L) {
int count = *(int*)((char*)g_player_list + OFFSET_PLAYERS_BY_ID + OFFSET_VECTOR_COUNT);
if (pfn_debug_print) {
char msg[64];
wsprintfA(msg, "[FoCAPI] Get_All_Players: PlayersByID count = %d\n", count);
pfn_debug_print(msg);
}
if (count <= 0 || count > 64) {
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: count out of expected range, aborting\n");
pfn_newtable(L);
return 1;
}
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: calling Get_Script_From_State\n");
void* script = pfn_get_script_from_state(L);
if (pfn_debug_print) {
char msg[64];
wsprintfA(msg, "[FoCAPI] Get_All_Players: script = %p\n", script);
pfn_debug_print(msg);
}
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: calling lua_newtable\n");
pfn_newtable(L);
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: lua_newtable OK\n");
for (int i = 0; i < count; i++) {
if (pfn_debug_print) {
char msg[64];
wsprintfA(msg, "[FoCAPI] Get_All_Players: Get_Player_By_Index(%d)\n", i);
pfn_debug_print(msg);
}
void* player = pfn_get_player_by_index(g_player_list, i);
if (!player) continue;
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: PlayerWrapper::Create\n");
void* wrapper = pfn_player_wrapper_create(player, script);
if (pfn_debug_print) {
char msg[128];
wsprintfA(msg, "[FoCAPI] Get_All_Players: wrapper = %p, player = %p\n", wrapper, player);
pfn_debug_print(msg);
}
if (!wrapper) continue;
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: Map_Var_To_Lua\n");
pfn_map_var_to_lua(L, wrapper);
if (pfn_debug_print) pfn_debug_print("[FoCAPI] Get_All_Players: lua_rawseti\n");
pfn_rawseti(L, -2, i + 1);
}
return 1;
}
void Register_Players(lua_State* L) {
if (!pfn_map_var_to_lua || !pfn_newtable || !pfn_rawseti) return;
register_global(L, "Get_All_Players", L_Get_All_Players);
}

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

@@ -1,76 +1,117 @@
#include "lua_hook.h" #include "lua_hook.h"
#include "minhook/include/MinHook.h" #include "functions/functions.h"
#include <MinHook.h>
#include <windows.h> #include <windows.h>
#include <cstring>
#include <cstdarg>
#include <cstdio>
#include <new>
#if defined _M_X64 void debug_log(const char* fmt, ...) {
#pragma comment(lib, "MinHook.x64.lib") char buf[1024];
#elif defined _M_IX86 va_list args;
#pragma comment(lib, "MinHook.x86.lib") va_start(args, fmt);
#endif vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
// Opaque Lua state - never dereferenced, just passed around // Also route through in-game debug if available
struct lua_State; if (pfn_debug_print)
typedef int (*lua_CFunction)(lua_State*); pfn_debug_print(buf);
// Function pointer types FILE* f = nullptr;
typedef lua_State* (*fn_lua_open)(); if (fopen_s(&f, "FoCAPI_debug.log", "a") == 0 && f) {
typedef void (*fn_lua_pushstring)(lua_State*, const char*); fputs(buf, f);
typedef void (*fn_lua_pushcclosure)(lua_State*, lua_CFunction, int); fclose(f);
typedef void (*fn_lua_settable)(lua_State*, int); }
typedef const char* (*fn_lua_tostring)(lua_State*, int); }
// 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
static fn_lua_open real_lua_open = nullptr; fn_lua_open real_lua_open = nullptr;
static fn_lua_pushstring pfn_pushstring = nullptr; fn_lua_pushstring pfn_pushstring = nullptr;
static fn_lua_pushcclosure pfn_pushcclosure = nullptr; fn_lua_pushcclosure pfn_pushcclosure = nullptr;
static fn_lua_settable pfn_settable = nullptr; fn_lua_settable pfn_settable = nullptr;
static fn_lua_tostring pfn_tostring = 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;
// Helper, equivalent of lua_setglobal void register_global(lua_State* L, const char* name, lua_CFunction fn) {
static void register_global(lua_State* L, const char* name, lua_CFunction fn) {
pfn_pushstring(L, name); pfn_pushstring(L, name);
pfn_pushcclosure(L, fn, 0); pfn_pushcclosure(L, fn, 0);
pfn_settable(L, LUA_GLOBALSINDEX); pfn_settable(L, LUA_GLOBALSINDEX);
} }
// ========================= void* CreateLuaValueString(const std::string& str) {
// Custom Lua file functions if (!g_rvas || !g_rvas->lua_value_string_vftable)
// ========================= return nullptr;
// WriteToFile(path, text) - appends text to a file void* obj = ::operator new(g_rvas->lua_value_string_size);
static int L_WriteToFile(lua_State* L) { if (!obj)
const char* path = pfn_tostring(L, 1); return nullptr;
const char* text = pfn_tostring(L, 2); memset(obj, 0, g_rvas->lua_value_string_size);
if (!path || !text) { if (pfn_lua_value_string_ctor) {
return 0; // 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;
}
} }
HANDLE hFile = CreateFileA( return obj;
path,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD written;
SetFilePointer(hFile, 0, NULL, FILE_END);
WriteFile(hFile, text, lstrlenA(text), &written, NULL);
WriteFile(hFile, "\n", 1, &written, NULL);
CloseHandle(hFile);
} }
return 0; // 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;
} }
// =========================
// Hooks
// =========================
// 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();
@@ -79,8 +120,7 @@ static lua_State* Hook_lua_open() {
return L; return L;
} }
// Register custom Lua functions Register_All(L);
register_global(L, "WriteToFile", L_WriteToFile);
return L; return L;
} }
@@ -102,12 +142,58 @@ 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);
pfn_settable = (fn_lua_settable)(base + rvas->lua_settable); pfn_settable = (fn_lua_settable)(base + rvas->lua_settable);
pfn_tostring = (fn_lua_tostring)(base + rvas->lua_tostring); 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); void* const target = (void*)(base + rvas->lua_open);
if (MH_Initialize() != MH_OK) { if (MH_Initialize() != MH_OK) {
@@ -122,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,33 +1,79 @@
#pragma once #pragma once
#include <cstdint> #include "rvas.h"
#include <string>
struct LuaRVAs { struct lua_State;
uintptr_t lua_open; typedef int (*lua_CFunction)(lua_State*);
uintptr_t lua_pushstring;
uintptr_t lua_pushcclosure;
uintptr_t lua_settable;
uintptr_t lua_tostring;
};
// StarWarsI.exe -- debug kit, PDB available // Function pointer types
constexpr LuaRVAs RVAs_StarWarsI = { typedef lua_State* (*fn_lua_open)();
0x12AB790, // lua_open typedef void (*fn_lua_pushstring)(lua_State*, const char*);
0x12AC930, // lua_pushstring typedef void (*fn_lua_pushcclosure)(lua_State*, lua_CFunction, int);
0x12AC6E0, // lua_pushcclosure typedef void (*fn_lua_settable)(lua_State*, int);
0x12AD020, // lua_settable typedef const char* (*fn_lua_tostring)(lua_State*, int);
0x12AD430, // lua_tostring typedef double (*fn_lua_tonumber)(lua_State*, int);
}; typedef void (*fn_lua_newtable)(lua_State*);
typedef void (*fn_lua_rawseti)(lua_State*, int, int);
typedef void* (*fn_GetPlayerByIndex)(void*, int); // PlayerListClass::Get_Player_By_Index
typedef void* (*fn_PlayerWrapperCreate)(void*, void*); // PlayerWrapper::Create(PlayerClass*, LuaScriptClass*)
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_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
// 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*
// StarWarsG.exe -- client binary, no PDB // Resolved function pointers (defined in lua_hook.cpp)
constexpr LuaRVAs RVAs_StarWarsG = { extern fn_lua_open real_lua_open;
0x07b8930, // lua_open extern fn_lua_pushstring pfn_pushstring;
0x07b9540, // lua_pushstring extern fn_lua_pushcclosure pfn_pushcclosure;
0x07b9340, // lua_pushcclosure extern fn_lua_settable pfn_settable;
0x07b9a60, // lua_settable extern fn_lua_tostring pfn_tostring;
0x07b9cc0, // lua_tostring extern fn_lua_tonumber pfn_tonumber;
}; extern fn_lua_newtable pfn_newtable;
extern fn_lua_rawseti pfn_rawseti;
extern fn_GetPlayerByIndex pfn_get_player_by_index;
extern void* g_player_list;
extern fn_PlayerWrapperCreate pfn_player_wrapper_create;
extern fn_GetScriptFromState pfn_get_script_from_state;
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_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
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

4
packages.config Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="minhook" version="1.3.3.0" targetFramework="native" />
</packages>

158
rvas.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include <cstdint>
struct LuaRVAs {
// --- Lua Core ---
uintptr_t lua_open;
uintptr_t lua_pushstring;
uintptr_t lua_pushcclosure;
uintptr_t lua_settable;
uintptr_t lua_tostring;
uintptr_t lua_tonumber;
uintptr_t lua_newtable;
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 ---
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_wrapper_create; // PlayerWrapper::Create (static)
// --- Screen Text ---
uintptr_t command_bar; // TheCommandBar global object (data, not a function)
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 ---
uintptr_t debug_print; // Debug_Print(char*)
};
// StarWarsI.exe -- debug kit, PDB available
constexpr LuaRVAs RVAs_StarWarsI = {
// --- Lua Core ---
0x12AB790, // lua_open
0x12AC930, // lua_pushstring
0x12AC6E0, // lua_pushcclosure
0x12AD020, // lua_settable
0x12AD430, // lua_tostring
0x12AD300, // lua_tonumber
0x12AC460, // lua_newtable
0x12ACD70, // lua_rawseti
// --- Scripts ---
0x0537BB0, // LuaScriptClass::Get_Script_From_State
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 ---
0x0476AE0, // Debug_Print
};
// StarWarsG.exe -- client binary, no PDB
constexpr LuaRVAs RVAs_StarWarsG = {
// --- Lua Core ---
0x07b8930, // lua_open
0x07b9540, // lua_pushstring
0x07b9340, // lua_pushcclosure
0x07b9a60, // lua_settable
0x07b9cc0, // lua_tostring
0x07b9bc0, // lua_tonumber
0x07b9140, // lua_newtable
0x07b9820, // lua_rawseti
// --- Scripts ---
0x0245790, // LuaScriptClass::Get_Script_From_State
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 ---
0, // Debug_Print (not mapped)
};