Skip to content

Commit 2ac245c

Browse files
Sync docs sources\n\nlancommander@v2.0.2
interposer@v1.0.1 bf1942@v1.61 callofduty@v1.5b counterstrikesource@v1.0.0 darkmessiah@unversioned eliteforce@v1.30 jediacademy@v1.0.0 jedioutcast@v1.4.1 portalknights@unversioned quake3arena@v1.0.0 rtcw@unversioned ut2004@v3339
1 parent 86b889d commit 2ac245c

6 files changed

Lines changed: 344 additions & 2 deletions

File tree

.external/interposer

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit cee070268da7e7757f6411c53f03f0564aa1ff69
1+
Subproject commit 7d5567a76bf3783d3f2c0abe2b040c7f91d73685

Interposer/Overview.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ Configuration and use of the Interposer is broken down on this site under the fo
3535
- [File Redirection](FileRedirection)
3636
- [Registry Emulation](RegistryEmulation)
3737
- [FastDL](FastDL)
38-
- [Borderless Fullscreen](BorderlessFullscreen)
38+
- [Borderless Fullscreen](BorderlessFullscreen)
39+
- [Plugins](Plugins)

Interposer/Plugins/CDKey.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
sidebar_label: CD Key
3+
sidebar_position: 3
4+
---
5+
6+
# CD Key Plugin
7+
8+
The CD Key plugin generates a deterministic CD key from the player's username and injects it into a registry value so the game reads it as if the game were installed with that key. The same username always produces the same key for a given mask, so every player on a LAN has a unique key that is consistent across sessions without any manual entry.
9+
10+
## How It Works
11+
12+
On load the plugin:
13+
14+
1. Reads the key mask, registry key path suffix, and value name from `Config.yml`.
15+
2. Resolves the player username via the Interposer identity system (the `Player.Username` config value or `--username` injector flag, falling back to the real Windows account name).
16+
3. Generates a key by hashing the username with FNV-1a and stepping an LCG to fill each `*` position in the mask with a character from `[A-Z0-9]`.
17+
4. Injects the generated key into the virtual registry store using suffix matching, so any registered key whose path ends with the configured `KeyPath` receives the value.
18+
19+
The injection is transient — it is not written back to `.interposer\Registry.reg`.
20+
21+
## Setup
22+
23+
### 1. Add the key to Registry.reg
24+
25+
The target registry key must appear in `.interposer\Registry.reg` for registry emulation to intercept reads. If the game stores the CD key as the default (unnamed) value, an empty key header is sufficient:
26+
27+
```
28+
Windows Registry Editor Version 5.00
29+
30+
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Electronic Arts\EA Games\Battlefield 1942\ergc]
31+
```
32+
33+
If the key already has other values you want to preserve, export it from `regedit.exe` and include the full entry.
34+
35+
### 2. Configure the plugin
36+
37+
Add a `Plugins.CDKey` section to `.interposer\Config.yml`:
38+
39+
```yaml
40+
Plugins:
41+
CDKey:
42+
Mask: "****-**********-*******-****"
43+
KeyPath: "Battlefield 1942\\ergc"
44+
ValueName: "@"
45+
```
46+
47+
| Option | Required | Description |
48+
|---|---|---|
49+
| `Mask` | Yes | Key template — `*` is replaced with a generated character, all other characters are copied verbatim |
50+
| `KeyPath` | Yes | Suffix of the registry key path to target. Matched case-insensitively on a backslash boundary against all keys in the virtual store |
51+
| `ValueName` | No | Name of the registry value to write. Use `@` or omit entirely to target the default (unnamed) value. Defaults to `@` |
52+
53+
### 3. Place the plugin
54+
55+
Copy `LANCommander.Interposer.Plugin.CDKey.dll` into `.interposer\Plugins\` next to the main DLL:
56+
57+
```
58+
C:\Games\Battlefield 1942\
59+
BF1942.exe
60+
LANCommander.Interposer.dll
61+
.interposer\
62+
Config.yml
63+
Registry.reg
64+
Plugins\
65+
LANCommander.Interposer.Plugin.CDKey.dll
66+
```
67+
68+
## Mask Syntax
69+
70+
The mask defines the shape of the generated key. Any `*` character is replaced with a letter or digit (`[A-Z0-9]`). All other characters — including hyphens, spaces, and brackets — are preserved exactly as written.
71+
72+
| Mask | Example output |
73+
|---|---|
74+
| `****-****-****-****` | `K7MN-2BPQ-X4RT-9LWA` |
75+
| `****-**********-*******-****` | `K7MN-2BPQ3X4RT9L-WA5FM2Z-8QBR` |
76+
| `{****-****}` | `{K7MN-2BPQ}` |
77+
78+
## Log Output
79+
80+
With the plugin loaded, the session log shows the injected value:
81+
82+
```
83+
2025-03-14 12:00:01 [PLUGIN LOAD] ...\.interposer\Plugins\LANCommander.Interposer.Plugin.CDKey.dll
84+
2025-03-14 12:00:01 [CDKEY] Battlefield 1942\ergc\@ -> K7MN-2BPQ3X4RT9L-WA5FM2Z-8QBR
85+
```
86+
87+
If the configured `KeyPath` suffix matches no keys in the virtual store, a warning is logged instead:
88+
89+
```
90+
2025-03-14 12:00:01 [CDKEY] No virtual store keys matched suffix "Battlefield 1942\ergc" — add the key to Registry.reg
91+
```
92+
93+
## Notes
94+
95+
- Key generation is deterministic: the same username and mask always produce the same key.
96+
- The generated key is uppercase alphanumeric only. If a game requires a specific character set or checksum validation, a custom plugin with a tailored generation algorithm will be needed instead.
97+
- If `KeyPath` matches more than one key in the virtual store (e.g. two subkeys both ending with the same suffix), the value is injected into all of them and the log line notes how many keys were updated.
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
sidebar_label: Creating a Plugin
3+
sidebar_position: 2
4+
---
5+
6+
# Creating a Plugin
7+
8+
A plugin is a standard Windows DLL (`.dll`) or ASI file (`.asi`) placed in `.interposer\Plugins\`. It has no link-time dependency on the Interposer — all API functions are resolved at runtime via `GetProcAddress`.
9+
10+
## Project Setup
11+
12+
Create a new DLL project targeting the same architecture as the game (x86 for 32-bit games, x64 for 64-bit games). No additional libraries or headers are required beyond the Windows SDK.
13+
14+
The only entry point needed is `DllMain`:
15+
16+
```cpp
17+
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD fdwReason, LPVOID /*lpReserved*/)
18+
{
19+
if (fdwReason == DLL_PROCESS_ATTACH)
20+
Initialize();
21+
return TRUE;
22+
}
23+
```
24+
25+
## Resolving the API
26+
27+
Declare function pointer types for the Interposer exports you need and resolve them with `GetProcAddress`. The Interposer may be loaded under different filenames depending on the deployment variant, so check the known names in order:
28+
29+
```cpp
30+
using FnInterposerLog = void (WINAPI*)(const wchar_t* verb, const wchar_t* message);
31+
using FnInterposerGetConfigString = BOOL (WINAPI*)(const wchar_t* dotPath, wchar_t* buf, DWORD bufSize);
32+
33+
static FnInterposerLog pfnLog = nullptr;
34+
static FnInterposerGetConfigString pfnGetConfig = nullptr;
35+
36+
static bool ResolveAPI()
37+
{
38+
static const wchar_t* kCandidates[] = {
39+
L"LANCommander.Interposer.dll",
40+
L"version.dll", // proxy variant
41+
};
42+
43+
HMODULE hInterposer = nullptr;
44+
for (const wchar_t* name : kCandidates)
45+
{
46+
hInterposer = GetModuleHandleW(name);
47+
if (hInterposer) break;
48+
}
49+
50+
if (!hInterposer) return false;
51+
52+
pfnLog = (FnInterposerLog) GetProcAddress(hInterposer, "InterposerLog");
53+
pfnGetConfig = (FnInterposerGetConfigString)GetProcAddress(hInterposer, "InterposerGetConfigString");
54+
55+
return pfnLog && pfnGetConfig;
56+
}
57+
```
58+
59+
## API Reference
60+
61+
All exported functions use the `WINAPI` (`__stdcall`) calling convention and undecorated `extern "C"` names.
62+
63+
### `InterposerLog`
64+
65+
```cpp
66+
void InterposerLog(const wchar_t* verb, const wchar_t* message);
67+
```
68+
69+
Writes a line to the session log regardless of the `Logging` flags in `Config.yml`. The log line format matches the rest of the session log:
70+
71+
```
72+
YYYY-MM-DD HH:MM:SS [VERB] <message>
73+
```
74+
75+
`verb` is normalised automatically: any existing `[`/`]` brackets and surrounding whitespace are stripped, the content is truncated to 16 characters, and it is re-wrapped as `[verb]` right-padded to 18 characters. Pass a plain string such as `L"MYPLUGIN"` — no manual padding required.
76+
77+
---
78+
79+
### `InterposerGetConfigString`
80+
81+
```cpp
82+
BOOL InterposerGetConfigString(const wchar_t* dotPath, wchar_t* buffer, DWORD bufferSize);
83+
```
84+
85+
Reads a scalar value from `Config.yml` by dot-separated YAML path. Returns `TRUE` on success, `FALSE` if the key does not exist, is not a scalar, or the buffer is too small.
86+
87+
`bufferSize` is in `wchar_t` units and must include room for the null terminator.
88+
89+
```cpp
90+
wchar_t setting[256];
91+
if (pfnGetConfig(L"Plugins.MyPlugin.Setting", setting, ARRAYSIZE(setting)))
92+
{
93+
// use setting
94+
}
95+
```
96+
97+
Plugin configuration should live under a `Plugins.<PluginName>` namespace in `Config.yml` to avoid collisions:
98+
99+
```yaml
100+
Plugins:
101+
MyPlugin:
102+
Setting: hello
103+
Count: 42
104+
```
105+
106+
---
107+
108+
### `InterposerGetUsername`
109+
110+
```cpp
111+
BOOL InterposerGetUsername(wchar_t* buffer, DWORD bufferSize);
112+
```
113+
114+
Returns the effective player username: the value configured in `Config.yml` under `Player.Username` or passed via the `--username` injector flag. Falls back to the real Windows account name (`GetUserNameW`) if no override is configured.
115+
116+
`bufferSize` is in `wchar_t` units including the null terminator. Returns `TRUE` on success.
117+
118+
---
119+
120+
### `InterposerSetRegistryValue`
121+
122+
```cpp
123+
void InterposerSetRegistryValue(const wchar_t* keyPath, const wchar_t* valueName, const wchar_t* value);
124+
```
125+
126+
Injects a `REG_SZ` string value into the in-memory virtual registry store. Subsequent `RegQueryValueEx` calls for `keyPath\valueName` return `value` without touching the real registry. The injection is transient — it is not persisted to `.interposer\Registry.reg`.
127+
128+
`keyPath` must be a full path beginning with a hive name:
129+
130+
```
131+
HKEY_LOCAL_MACHINE\SOFTWARE\MyGame\1.0
132+
```
133+
134+
Set `valueName` to `L"@"`, `L""`, or `nullptr` to target the default (unnamed) registry value — the entry shown as `(Default)` in Registry Editor.
135+
136+
:::note
137+
The target key must already exist in `.interposer\Registry.reg` for reads to be intercepted. Add an empty key header if no values need to be pre-populated:
138+
139+
```
140+
[HKEY_LOCAL_MACHINE\SOFTWARE\MyGame\1.0]
141+
```
142+
:::
143+
144+
---
145+
146+
### `InterposerSetRegistryValueBySuffix`
147+
148+
```cpp
149+
DWORD InterposerSetRegistryValueBySuffix(const wchar_t* keySuffix, const wchar_t* valueName, const wchar_t* value);
150+
```
151+
152+
Like `InterposerSetRegistryValue`, but matches by suffix rather than exact path. Any key in the virtual store whose path ends with `\keySuffix` (matched case-insensitively on a backslash component boundary) receives the injected value.
153+
154+
Returns the number of keys updated. A return value of `0` means the suffix matched nothing in the virtual store — check that the target key is present in `.interposer\Registry.reg`.
155+
156+
This is useful when the full registry path varies between game versions or installations:
157+
158+
```cpp
159+
// Matches HKEY_LOCAL_MACHINE\...\Electronic Arts\EA Games\Battlefield 1942\ergc
160+
// regardless of any intermediate path components.
161+
pfnSetBySuffix(L"Battlefield 1942\\ergc", L"@", generatedKey);
162+
```
163+
164+
## Minimal Example
165+
166+
```cpp
167+
#define WIN32_LEAN_AND_MEAN
168+
#include <windows.h>
169+
#include <string>
170+
171+
using FnInterposerLog = void (WINAPI*)(const wchar_t*, const wchar_t*);
172+
using FnInterposerGetConfigString = BOOL (WINAPI*)(const wchar_t*, wchar_t*, DWORD);
173+
174+
static FnInterposerLog pfnLog = nullptr;
175+
static FnInterposerGetConfigString pfnGetConfig = nullptr;
176+
177+
static void Initialize()
178+
{
179+
HMODULE h = GetModuleHandleW(L"LANCommander.Interposer.dll");
180+
if (!h) h = GetModuleHandleW(L"version.dll");
181+
if (!h) return;
182+
183+
pfnLog = (FnInterposerLog) GetProcAddress(h, "InterposerLog");
184+
pfnGetConfig = (FnInterposerGetConfigString)GetProcAddress(h, "InterposerGetConfigString");
185+
if (!pfnLog || !pfnGetConfig) return;
186+
187+
wchar_t greeting[256] = L"hello";
188+
pfnGetConfig(L"Plugins.MyPlugin.Greeting", greeting, ARRAYSIZE(greeting));
189+
190+
pfnLog(L"MYPLUGIN", greeting);
191+
}
192+
193+
BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID)
194+
{
195+
if (reason == DLL_PROCESS_ATTACH)
196+
Initialize();
197+
return TRUE;
198+
}
199+
```
200+
201+
```yaml
202+
# .interposer\Config.yml
203+
Plugins:
204+
MyPlugin:
205+
Greeting: "Plugin loaded successfully"
206+
```

Interposer/Plugins/Overview.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
sidebar_label: Overview
3+
sidebar_position: 1
4+
---
5+
6+
# Plugins
7+
8+
The Interposer supports plugins — DLL or ASI files placed in `.interposer\Plugins\` next to the main DLL. Plugins are loaded automatically after all built-in hooks are active, so they can rely on the full Interposer API from the moment they initialise.
9+
10+
## How Plugins Are Loaded
11+
12+
On startup, after installing and enabling all built-in hooks, the Interposer scans `.interposer\Plugins\` for files matching `*.dll` and `*.asi` and loads each one with `LoadLibrary`. Plugins are unloaded in reverse order when the process exits.
13+
14+
Each successful load is recorded in the session log:
15+
16+
```
17+
2025-03-14 12:00:01 [PLUGIN LOAD] C:\Games\MyGame\.interposer\Plugins\CDKey.dll
18+
```
19+
20+
If a plugin fails to load, the error is logged with the Win32 error code and the remaining plugins continue loading:
21+
22+
```
23+
2025-03-14 12:00:01 [PLUGIN ERROR] C:\Games\MyGame\.interposer\Plugins\broken.dll (error 126)
24+
```
25+
26+
## Available Plugins
27+
28+
| Plugin | Description |
29+
|---|---|
30+
| [CD Key](CDKey) | Generates a deterministic CD key from the player username and injects it into a registry value |
31+
32+
## Writing Your Own Plugin
33+
34+
See [Creating a Plugin](CreatingPlugins) for the full API reference and a worked example.

Interposer/Plugins/_category_.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label": "Plugins",
3+
"position": 8
4+
}

0 commit comments

Comments
 (0)