Skip to content

Commit dc4e70f

Browse files
committed
GameFunctionProxy
1 parent bb1bd4c commit dc4e70f

2 files changed

Lines changed: 187 additions & 0 deletions

File tree

docs/.vitepress/config.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export default defineConfig({
4141
{ text: 'Mod Base', link: '/api-documentation/mod/mod-base' },
4242
{ text: 'Mod Configuration', link: '/api-documentation/mod/configuration' }
4343
]
44+
},
45+
{
46+
text: 'Recreate UE/Game Functions',
47+
link: '/api-documentation/advanced/game-function-proxy'
4448
}
4549
]
4650
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Recreating engine function with `GameFunctionProxy<T>` <Badge type="warning" text="^RC1.10" />
2+
Game Function Proxies are a clean and reusable way to interface with native game functions discovered trough rever engineering.
3+
They encapsulate pattern scanning, address resolution and call safety into a simple C++ class based wrapper.
4+
5+
The [GameFunctionProxy](https://github.com/ReDevCafe/FantasyLifeI-ModLoader-Headers/tree/main/API/GameFunctionProxy.hpp) base class is provided as part of the **FLiAPI** and allows your mod to safely call internal game functions by signature.
6+
7+
Here's a simple example of a use case:
8+
9+
`FNameToString.hpp`
10+
```cpp
11+
// Defines the native function signature as seen in disassembly (RCX = thisptr, RDX = FString*)
12+
typedef void (__fastcall *FLIAPI_DEF_FNameToString)(FName* thisptr, FString* a2);
13+
14+
// Wrapper for the internal FName::ToString() engine function
15+
class FNameToString : public GameFunctionProxy<FLIAPI_DEF_FNameToString>
16+
{
17+
private:
18+
19+
/***
20+
* Function pattern (unique byte signature used to locate the function in memory)
21+
* Wildcards (??) mark bytes that may vary between builds.
22+
*/
23+
static constexpr uint8_t pattern[] =
24+
{
25+
0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x20, 0x80,
26+
0x3D, 0x38, 0x54, 0x2E, 0x09, 0x00, 0x48, 0x8B, 0xFA, 0x8B, 0x19, 0x48, 0x8B, 0xF1, 0x74, 0x09,
27+
0x48, 0x8D, 0x15, 0x59, 0x17, 0x31, 0x09, 0xEB, 0x16, 0x48, 0x8D, 0x0D, 0x50, 0x17, 0x31, 0x09,
28+
0xE8, 0x5B, 0x19, 0xFE, 0xFF, 0x48, 0x8B, 0xD0, 0xC6, 0x05, 0x0F, 0x54, 0x2E, 0x09, 0x01, 0x8B,
29+
0xCB, 0x0F, 0xB7, 0xC3, 0xC1, 0xE9, 0x10, 0x89, 0x4C, 0x24, 0x30, 0x89, 0x44, 0x24, 0x34, 0x48,
30+
};
31+
32+
FNameToString() :
33+
GameFunctionProxy<FLIAPI_DEF_FNameToString>(
34+
"FName::ToString()", // Unique function identifier (for logging)
35+
const_cast<uint8_t*>(pattern), // Byte pattern
36+
"xxxxxxxxxxxxxxxxx????xxxxxxxxxxxxxxx???xxxxxx??????xxxxxxx????xxxxxxxxxxxxxxxxxx", // Mask
37+
0 // Offset fallback (unused, unsafe)
38+
)
39+
{}
40+
41+
public:
42+
FLIAPI_FUNCTION_INSTANCE_MACRO(FNameToString); // Creates static instance()
43+
FLIAPI_FUNCTION_CALL_MACRO(FLIAPI_DEF_FNameToString); // Creates static call(...) wrapper
44+
};
45+
```
46+
47+
48+
`Mod.cpp`
49+
```cpp
50+
void YourMod::OnPreLoad()
51+
{
52+
...
53+
54+
FName test = gameCache->GetItem("imt01004480").getObject().nameId; // Get item name ID (FName)
55+
FString fstrtest; // Buffer to receive string result
56+
FNameToString::call(&test, &fstrtest); // Call native FName::ToString()
57+
58+
...
59+
}
60+
```
61+
62+
### What is a GameFunctionProxy?
63+
A GameFunctionProxy is a small helper class that defines how to locate and call an internal engine / game function, it contains:
64+
- `id`: A unique identifier used for logging and debugging when the function fails to load.
65+
- `pattern`: A byte signature used to locate the function inside the game’s binary.
66+
- `mask`: A wildcard string that marks which bytes in the pattern should be ignored (?).
67+
- <Badge type="warning" text="Optional" /> `offset`: A fallback address offset, used only if the pattern scan fails.
68+
69+
The first time you call a GameFunctionProxy function, the proxy automatically scans the game's memory to resolve the function's address.
70+
Once found, it lets you call the gamr function directly as if it were a regular C++ method.
71+
72+
### Defining a pattern when reversing
73+
74+
When reverse-engineering a function, you can extract it's machine code byte and build a pattern.
75+
Patterns are used to identify the function inside the game's loaded image, even if it's reloacted in memory.
76+
77+
###### Example pattern
78+
```ruby
79+
4C 8B DC 48 83 EC 78 48 8B 05 ?? ?? ?? ?? 48 85
80+
C0 0F 85 92 00 00 00 48 8D 05 ?? ?? FF FF 49 89
81+
43 08 4C 8D 0D ?? ?? ?? FF 48 8D 05 B0 DE 00 00
82+
```
83+
84+
Each pair of hexadecimal numbers represents one byte.
85+
Wildcards (`??`) mark bytes that may vary between versions (for example, relative jump offsets or addresses).
86+
87+
### The mask
88+
The mask string is a simple way to tell which bytes mst be matched (`x`) and which can be ignored (`?`)
89+
By example:
90+
91+
```bash
92+
xxxxxxxxxxxxxxxxx????xxxxxxxxxxxxxxx???xxxxxx??????xxxxxxx????xxxxxxxxxxxxxxxxxx
93+
```
94+
95+
Each character corresponds to one byte in the patern:
96+
- `'x'`: match this byte exactly.
97+
- `'?'`: ignore this byte (`wildcard`).
98+
99+
### Understanding offsets
100+
The `offset` arguments in `GameFunctionProxy` is a fallback mechanism used only when pattern scanning fails.
101+
If the pattern cannot be found in the game image, the proxy logs a warning and attempts to resolve the function by `baseAddress + offset`
102+
103+
::: warning
104+
Offsets are unsafe and version-dependent.
105+
Use them only during testing or when no reliable pattern is available.
106+
:::
107+
108+
### Creating your own proxy
109+
To define you own function proxy, inherit from `GameFunctionType<YourType>` and specify the pattern, mask, and offset (optional).
110+
111+
Example:
112+
###### Define your type
113+
```cpp
114+
typedef void (__fastcall *YourType)(FName* thisptr, FString* a2);
115+
```
116+
117+
###### Create your class
118+
```cpp
119+
class YourFunctionClass : public GameFunctionProxy<YourType>
120+
{
121+
...
122+
}
123+
```
124+
125+
##### Extract pattern from the dissassembled targeted function
126+
```cpp
127+
static constexpr uint8_t pattern[] = {
128+
0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x20, 0x80,
129+
0x3D, 0x38, 0x54, 0x2E, 0x09, 0x00, 0x48, 0x8B, 0xFA, 0x8B, 0x19, 0x48, 0x8B, 0xF1, 0x74, 0x09,
130+
0x48, 0x8D, 0x15, 0x59, 0x17, 0x31, 0x09, 0xEB, 0x16, 0x48, 0x8D, 0x0D, 0x50, 0x17, 0x31, 0x09,
131+
0xE8, 0x5B, 0x19, 0xFE, 0xFF, 0x48, 0x8B, 0xD0, 0xC6, 0x05, 0x0F, 0x54, 0x2E, 0x09, 0x01, 0x8B,
132+
0xCB, 0x0F, 0xB7, 0xC3, 0xC1, 0xE9, 0x10, 0x89, 0x4C, 0x24, 0x30, 0x89, 0x44, 0x24, 0x34, 0x48,
133+
};
134+
```
135+
136+
##### Create the constructor
137+
```cpp
138+
YourFunctionClass()
139+
: GameFunctionProxy<YourType>
140+
(
141+
// Id
142+
"YourFunctionClass::YourFunctionName()",
143+
144+
// Pattern
145+
const_cast<uint8_t*>(pattern),
146+
147+
// Mask
148+
"xxxxxxxxxxxxxxxxx????xxxxxxxxxxxxxxx???xxxxxx??????xxxxxxx????xxxxxxxxxxxxxxxxxx",
149+
150+
// Offset
151+
0,
152+
){}
153+
```
154+
155+
##### And add the extremly important magic function (trust me bro)
156+
```cpp
157+
public:
158+
FLIAPI_FUNCTION_INSTANCE_MACRO(YourFunctionClass);
159+
FLIAPI_FUNCTION_CALL_MACRO(YourType);
160+
```
161+
162+
### Calling your recreated function
163+
After defining the proxy, you can call it anywhere in your mod:
164+
```cpp
165+
YourFunctionClass::call(&a &b);
166+
logger->info(b.ToString);
167+
```
168+
169+
### Internal proccess:
170+
What is happenning when your proxy is instantiated?
171+
1. The base address of the game is retrived via `GameData::getBaseAddress()`
172+
2. The `Pattern` scanner searches the image for a matching byte sequence
173+
3. If a match is found, the address is stored as a valid function pointer.
174+
4. If not found, a warning is logged and the `offset` fallback is used.
175+
176+
::: tip NOTE
177+
if `_function` remain null, any attempt to call it throws a `std::runtime_error`, ensuring you never execute an invalid address
178+
:::
179+
180+
181+
### References
182+
- [GameFunctionProxy](https://github.com/ReDevCafe/FantasyLifeI-ModLoader-Headers/tree/main/API/GameFunctionProxy.hpp)
183+
- [Pattern](https://github.com/ReDevCafe/FantasyLifeI-ModLoader-Headers/tree/main/Hook/Pattern.hpp)

0 commit comments

Comments
 (0)