enfusion-mcp 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +194 -188
  2. package/dist/pak/reader.d.ts +28 -0
  3. package/dist/pak/reader.d.ts.map +1 -0
  4. package/dist/pak/reader.js +141 -0
  5. package/dist/pak/reader.js.map +1 -0
  6. package/dist/pak/vfs.d.ts +56 -0
  7. package/dist/pak/vfs.d.ts.map +1 -0
  8. package/dist/pak/vfs.js +200 -0
  9. package/dist/pak/vfs.js.map +1 -0
  10. package/dist/prompts/create-mod.d.ts.map +1 -1
  11. package/dist/prompts/create-mod.js +151 -139
  12. package/dist/prompts/create-mod.js.map +1 -1
  13. package/dist/prompts/modify-mod.d.ts.map +1 -1
  14. package/dist/prompts/modify-mod.js +72 -70
  15. package/dist/prompts/modify-mod.js.map +1 -1
  16. package/dist/tools/asset-search.d.ts.map +1 -1
  17. package/dist/tools/asset-search.js +28 -8
  18. package/dist/tools/asset-search.js.map +1 -1
  19. package/dist/tools/game-browse.d.ts.map +1 -1
  20. package/dist/tools/game-browse.js +39 -3
  21. package/dist/tools/game-browse.js.map +1 -1
  22. package/dist/tools/game-read.d.ts.map +1 -1
  23. package/dist/tools/game-read.js +67 -30
  24. package/dist/tools/game-read.js.map +1 -1
  25. package/dist/tools/wb-entities.d.ts.map +1 -1
  26. package/dist/tools/wb-entities.js +33 -6
  27. package/dist/tools/wb-entities.js.map +1 -1
  28. package/dist/tools/wiki-search.d.ts.map +1 -1
  29. package/dist/tools/wiki-search.js +2 -1
  30. package/dist/tools/wiki-search.js.map +1 -1
  31. package/dist/workbench/client.d.ts +9 -3
  32. package/dist/workbench/client.d.ts.map +1 -1
  33. package/dist/workbench/client.js +27 -16
  34. package/dist/workbench/client.js.map +1 -1
  35. package/mod/Scripts/WorkbenchGame/EnfusionMCP/EMCP_WB_ModifyEntity.c +472 -295
  36. package/package.json +58 -58
package/README.md CHANGED
@@ -1,188 +1,194 @@
1
- # enfusion-mcp
2
-
3
- MCP server for Arma Reforger modding. Describe what you want to build, and Claude handles everything — API research, code generation, project scaffolding, Workbench control, and in-editor testing. Zero modding experience required.
4
-
5
- ## Install
6
-
7
- ### Claude Code (Windows)
8
-
9
- ```bash
10
- claude mcp add --scope user enfusion-mcp -- cmd /c npx -y enfusion-mcp
11
- ```
12
-
13
- ### Claude Code (macOS / Linux)
14
-
15
- ```bash
16
- claude mcp add --scope user enfusion-mcp -- npx -y enfusion-mcp
17
- ```
18
-
19
- Restart Claude Code. Verify with `/mcp`.
20
-
21
- ### Claude Desktop
22
-
23
- Add to your `claude_desktop_config.json`:
24
-
25
- **Windows:**
26
-
27
- ```json
28
- {
29
- "mcpServers": {
30
- "enfusion-mcp": {
31
- "command": "cmd",
32
- "args": ["/c", "npx", "-y", "enfusion-mcp"]
33
- }
34
- }
35
- }
36
- ```
37
-
38
- **macOS / Linux:**
39
-
40
- ```json
41
- {
42
- "mcpServers": {
43
- "enfusion-mcp": {
44
- "command": "npx",
45
- "args": ["-y", "enfusion-mcp"]
46
- }
47
- }
48
- }
49
- ```
50
-
51
- ### Workbench Plugin
52
-
53
- The live Workbench tools (`wb_*`) require handler scripts running inside Workbench. These ship with the package in `mod/Scripts/WorkbenchGame/EnfusionMCP/` and are installed automatically when Claude launches Workbench via `wb_launch`.
54
-
55
- ## Usage
56
-
57
- Just ask Claude to make a mod:
58
-
59
- - *"Create a HUD widget that shows player health and stamina"*
60
- - *"Make a zombie survival game mode with wave spawning"*
61
- - *"Create a custom faction called CSAT with desert camo soldiers"*
62
- - *"Add an interactive object that heals the player when used"*
63
- - *"Override the damage system to add armor mechanics"*
64
-
65
- Or use the guided prompts for structured workflows:
66
-
67
- | Prompt | Description |
68
- |--------|-------------|
69
- | `/create-mod` | Full guided mod creation — from idea to built addon |
70
- | `/modify-mod` | Modify or extend an existing mod project |
71
-
72
- Claude will:
73
-
74
- 1. **Assess complexity** — simple mods are built in one pass; large mods (e.g., a DayZ-style overhaul) get broken into phases with a plan you approve before any code is written
75
- 2. **Research** the Enfusion API (8,693 indexed classes) and the Arma Reforger wiki (250+ guides and tutorials) to find the right approach
76
- 3. **Scaffold** the full addon `.gproj`, scripts, prefabs, configs, UI layouts
77
- 4. **Launch Workbench** if it's not already running
78
- 5. **Load the project**, reload scripts, register resources
79
- 6. **Validate and build** the addon
80
- 7. **Enter play mode** so you can test in-game
81
-
82
- For complex mods, a `MODPLAN.md` is written to the project root tracking the full vision, completed phases, and what's next — so any future session can pick up right where the last one left off via `/modify-mod`.
83
-
84
- ## Tools
85
-
86
- ### Offline Tools
87
-
88
- Work without Workbench running — API search, mod scaffolding, code generation, validation, and building.
89
-
90
- | Tool | What it does |
91
- |------|-------------|
92
- | `api_search` | Search 8,693 Enfusion/Arma Reforger API classes and methods |
93
- | `wiki_search` | Search 250+ tutorials and guides from the Enfusion engine docs and BI Community Wiki |
94
- | `project_browse` | List files in a mod project directory |
95
- | `project_read` | Read any project file |
96
- | `project_write` | Write or update project files |
97
- | `mod_create` | Scaffold a complete addon with directory structure and `.gproj` |
98
- | `script_create` | Generate Enforce Script (`.c`) files 7 types: component, gamemode, action, entity, manager, modded, basic |
99
- | `prefab_create` | Generate Entity Template (`.et`) prefabs 7 types: character, vehicle, weapon, spawnpoint, gamemode, interactive, generic |
100
- | `layout_create` | Generate UI layout (`.layout`) files — 5 types: hud, menu, dialog, list, custom |
101
- | `config_create` | Generate config files factions, missions, entity catalogs, editor placeables |
102
- | `server_config` | Generate dedicated server config for local testing |
103
- | `mod_validate` | Validate project structure, scripts, prefabs, configs, and naming |
104
- | `mod_build` | Build the addon using the Workbench CLI |
105
-
106
- ### Live Workbench Tools
107
-
108
- Control a running Workbench instance over TCP. Requires the handler scripts installed (see setup above).
109
-
110
- | Tool | What it does |
111
- |------|-------------|
112
- | `wb_launch` | Start Workbench if not running, wait for NET API |
113
- | `wb_connect` | Test connection to Workbench |
114
- | `wb_state` | Full state snapshot — mode, world, entity count, selection |
115
- | `wb_play` | Switch to game mode (Play in Editor) |
116
- | `wb_stop` | Return to edit mode |
117
- | `wb_save` | Save the current world |
118
- | `wb_undo_redo` | Undo or redo the last action |
119
- | `wb_open_resource` | Open a resource in its editor |
120
- | `wb_reload` | Reload scripts or plugins without restarting |
121
- | `wb_execute_action` | Run any Workbench menu action by path |
122
- | `wb_entity_create` | Create entity from prefab at a position |
123
- | `wb_entity_delete` | Delete entity by name |
124
- | `wb_entity_list` | List and search entities in the world |
125
- | `wb_entity_inspect` | Get entity details properties, components, children |
126
- | `wb_entity_modify` | Move, rotate, rename, reparent, set properties |
127
- | `wb_entity_select` | Select, deselect, clear, get current selection |
128
- | `wb_component` | Add, remove, list entity components |
129
- | `wb_terrain` | Query terrain height and world bounds |
130
- | `wb_layers` | Create, delete, rename layers, set visibility/active |
131
- | `wb_resources` | Register resources, rebuild database |
132
- | `wb_prefabs` | Create templates, save, GUID lookup |
133
- | `wb_clipboard` | Copy, cut, paste, duplicate entities |
134
- | `wb_script_editor` | Read/write lines in the open script file |
135
- | `wb_localization` | String table CRUD for localization |
136
- | `wb_projects` | List loaded projects, open `.gproj` files |
137
- | `wb_validate` | Material and texture validation |
138
-
139
- ### Mod Patterns
140
-
141
- 10 built-in templates for `mod_create`:
142
-
143
- `game-mode` `custom-faction` `custom-action` `spawn-system` `custom-component` `modded-behavior` `admin-tool` `custom-vehicle` `weapon-reskin` `hud-widget`
144
-
145
- ### MCP Resources
146
-
147
- | URI | Description |
148
- |-----|-------------|
149
- | `enfusion://class/{className}` | Full class docs with inheritance, methods, ancestors/descendants |
150
- | `enfusion://pattern/{patternName}` | Mod pattern definition with all templates |
151
- | `enfusion://group/{groupName}` | API group with class list |
152
-
153
- ## Configuration
154
-
155
- All optional. Sensible defaults are used when nothing is set.
156
-
157
- | Environment Variable | Description | Default |
158
- |---------------------|-------------|---------|
159
- | `ENFUSION_PROJECT_PATH` | Default mod output directory | `~/Documents/My Games/ArmaReforgerWorkbench/addons` |
160
- | `ENFUSION_WORKBENCH_PATH` | Path to Arma Reforger Tools | `C:\Program Files (x86)\Steam\steamapps\common\Arma Reforger Tools` |
161
- | `ENFUSION_WORKBENCH_HOST` | NET API host | `127.0.0.1` |
162
- | `ENFUSION_WORKBENCH_PORT` | NET API port | `5775` |
163
-
164
- Config can also be loaded from `~/.enfusion-mcp/config.json`. Environment variables take priority.
165
-
166
- ## Requirements
167
-
168
- - **Node.js 20+**
169
- - **Arma Reforger Tools** (Steam) — needed for `mod_build` and all `wb_*` tools
170
-
171
- ## Development
172
-
173
- ```bash
174
- git clone https://github.com/Articulated7/enfusion-mcp.git
175
- cd enfusion-mcp
176
- npm install
177
- npm run scrape # Build API index from Workbench docs
178
- npm run build
179
- npm test # 163 tests
180
- ```
181
-
182
- ## Documentation
183
-
184
- Full documentation is on the [GitHub Wiki](https://github.com/Articulated7/enfusion-mcp/wiki).
185
-
186
- ## License
187
-
188
- MIT
1
+ # enfusion-mcp
2
+
3
+ MCP server for Arma Reforger modding. Describe what you want to build, and Claude handles everything — API research, code generation, project scaffolding, Workbench control, and in-editor testing. Zero modding experience required.
4
+
5
+ ## Install
6
+
7
+ ### Claude Code (Windows)
8
+
9
+ ```bash
10
+ claude mcp add --scope user enfusion-mcp -- cmd /c npx -y enfusion-mcp
11
+ ```
12
+
13
+ ### Claude Code (macOS / Linux)
14
+
15
+ ```bash
16
+ claude mcp add --scope user enfusion-mcp -- npx -y enfusion-mcp
17
+ ```
18
+
19
+ Restart Claude Code. Verify with `/mcp`.
20
+
21
+ ### Claude Desktop
22
+
23
+ Add to your `claude_desktop_config.json`:
24
+
25
+ **Windows:**
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "enfusion-mcp": {
31
+ "command": "cmd",
32
+ "args": ["/c", "npx", "-y", "enfusion-mcp"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ **macOS / Linux:**
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "enfusion-mcp": {
44
+ "command": "npx",
45
+ "args": ["-y", "enfusion-mcp"]
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Restart Claude Desktop. Verify with `/mcp`.
52
+
53
+ ### Workbench Plugin
54
+
55
+ The live Workbench tools (`wb_*`) require handler scripts running inside Workbench. These ship with the package in `mod/Scripts/WorkbenchGame/EnfusionMCP/` and are installed automatically when Claude launches Workbench via `wb_launch`.
56
+
57
+ ## Usage
58
+
59
+ Just ask Claude to make a mod:
60
+
61
+ - *"Create a HUD widget that shows player health and stamina"*
62
+ - *"Make a zombie survival game mode with wave spawning"*
63
+ - *"Create a custom faction called CSAT with desert camo soldiers"*
64
+ - *"Add an interactive object that heals the player when used"*
65
+ - *"Override the damage system to add armor mechanics"*
66
+
67
+ Or use the guided prompts for structured workflows:
68
+
69
+ | Prompt | Description |
70
+ |--------|-------------|
71
+ | `/create-mod` | Full guided mod creation — from idea to built addon |
72
+ | `/modify-mod` | Modify or extend an existing mod project |
73
+
74
+ Claude will:
75
+
76
+ 1. **Assess complexity** simple mods are built in one pass; large mods (e.g., a DayZ-style overhaul) get broken into phases with a plan you approve before any code is written
77
+ 2. **Research** the Enfusion API (8,693 indexed classes), the Arma Reforger wiki (250+ guides), and base game assets (read directly from `.pak` archives) to find the right approach
78
+ 3. **Scaffold** the full addon — `.gproj`, scripts, prefabs, configs, UI layouts
79
+ 4. **Launch Workbench** if it's not already running
80
+ 5. **Load the project**, reload scripts, register resources
81
+ 6. **Validate and build** the addon
82
+ 7. **Enter play mode** so you can test in-game
83
+
84
+ For complex mods, a `MODPLAN.md` is written to the project root tracking the full vision, completed phases, and what's next — so any future session can pick up right where the last one left off via `/modify-mod`.
85
+
86
+ ## Tools
87
+
88
+ ### Offline Tools
89
+
90
+ Work without Workbench running API search, mod scaffolding, code generation, validation, and building.
91
+
92
+ | Tool | What it does |
93
+ |------|-------------|
94
+ | `api_search` | Search 8,693 Enfusion/Arma Reforger API classes and methods |
95
+ | `wiki_search` | Search 250+ tutorials and guides from the Enfusion engine docs and BI Community Wiki |
96
+ | `game_browse` | Browse base game files — loose files and `.pak` archives transparently |
97
+ | `game_read` | Read base game files scripts, prefabs, configs from loose files or `.pak` |
98
+ | `asset_search` | Search game assets by name across loose files and `.pak` archives |
99
+ | `project_browse` | List files in a mod project directory |
100
+ | `project_read` | Read any project file |
101
+ | `project_write` | Write or update project files |
102
+ | `mod_create` | Scaffold a complete addon with directory structure and `.gproj` |
103
+ | `script_create` | Generate Enforce Script (`.c`) files — 7 types: component, gamemode, action, entity, manager, modded, basic |
104
+ | `prefab_create` | Generate Entity Template (`.et`) prefabs 7 types: character, vehicle, weapon, spawnpoint, gamemode, interactive, generic |
105
+ | `layout_create` | Generate UI layout (`.layout`) files — 5 types: hud, menu, dialog, list, custom |
106
+ | `config_create` | Generate config files — factions, missions, entity catalogs, editor placeables |
107
+ | `server_config` | Generate dedicated server config for local testing |
108
+ | `mod_validate` | Validate project structure, scripts, prefabs, configs, and naming |
109
+ | `mod_build` | Build the addon using the Workbench CLI |
110
+
111
+ ### Live Workbench Tools
112
+
113
+ Control a running Workbench instance over TCP. Requires the handler scripts installed (see setup above).
114
+
115
+ | Tool | What it does |
116
+ |------|-------------|
117
+ | `wb_launch` | Start Workbench if not running, wait for NET API |
118
+ | `wb_connect` | Test connection to Workbench |
119
+ | `wb_state` | Full state snapshot mode, world, entity count, selection |
120
+ | `wb_play` | Switch to game mode (Play in Editor) |
121
+ | `wb_stop` | Return to edit mode |
122
+ | `wb_save` | Save the current world |
123
+ | `wb_undo_redo` | Undo or redo the last action |
124
+ | `wb_open_resource` | Open a resource in its editor |
125
+ | `wb_reload` | Reload scripts or plugins without restarting |
126
+ | `wb_execute_action` | Run any Workbench menu action by path |
127
+ | `wb_entity_create` | Create entity from prefab at a position |
128
+ | `wb_entity_delete` | Delete entity by name |
129
+ | `wb_entity_list` | List and search entities in the world |
130
+ | `wb_entity_inspect` | Get entity details — properties, components, children |
131
+ | `wb_entity_modify` | Move, rotate, rename, reparent, set/clear/get/list properties |
132
+ | `wb_entity_select` | Select, deselect, clear, get current selection |
133
+ | `wb_component` | Add, remove, list entity components |
134
+ | `wb_terrain` | Query terrain height and world bounds |
135
+ | `wb_layers` | Create, delete, rename layers, set visibility/active |
136
+ | `wb_resources` | Register resources, rebuild database |
137
+ | `wb_prefabs` | Create templates, save, GUID lookup |
138
+ | `wb_clipboard` | Copy, cut, paste, duplicate entities |
139
+ | `wb_script_editor` | Read/write lines in the open script file |
140
+ | `wb_localization` | String table CRUD for localization |
141
+ | `wb_projects` | List loaded projects, open `.gproj` files |
142
+ | `wb_validate` | Material and texture validation |
143
+
144
+ ### Mod Patterns
145
+
146
+ 10 built-in templates for `mod_create`:
147
+
148
+ `game-mode` `custom-faction` `custom-action` `spawn-system` `custom-component` `modded-behavior` `admin-tool` `custom-vehicle` `weapon-reskin` `hud-widget`
149
+
150
+ ### MCP Resources
151
+
152
+ | URI | Description |
153
+ |-----|-------------|
154
+ | `enfusion://class/{className}` | Full class docs with inheritance, methods, ancestors/descendants |
155
+ | `enfusion://pattern/{patternName}` | Mod pattern definition with all templates |
156
+ | `enfusion://group/{groupName}` | API group with class list |
157
+
158
+ ## Configuration
159
+
160
+ All optional. Sensible defaults are used when nothing is set.
161
+
162
+ | Environment Variable | Description | Default |
163
+ |---------------------|-------------|---------|
164
+ | `ENFUSION_PROJECT_PATH` | Default mod output directory | `~/Documents/My Games/ArmaReforgerWorkbench/addons` |
165
+ | `ENFUSION_WORKBENCH_PATH` | Path to Arma Reforger Tools | `C:\Program Files (x86)\Steam\steamapps\common\Arma Reforger Tools` |
166
+ | `ENFUSION_GAME_PATH` | Path to the Arma Reforger game install (used as CWD when launching Workbench so base-game addons resolve correctly) | Auto-detected from sibling of `ENFUSION_WORKBENCH_PATH` |
167
+ | `ENFUSION_WORKBENCH_HOST` | NET API host | `127.0.0.1` |
168
+ | `ENFUSION_WORKBENCH_PORT` | NET API port | `5775` |
169
+
170
+ Config can also be loaded from `~/.enfusion-mcp/config.json`. Environment variables take priority.
171
+
172
+ ## Requirements
173
+
174
+ - **Node.js 20+**
175
+ - **Arma Reforger Tools** (Steam) — needed for `mod_build` and all `wb_*` tools
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ git clone https://github.com/Articulated7/enfusion-mcp.git
181
+ cd enfusion-mcp
182
+ npm install
183
+ npm run scrape # Build API index from Workbench docs
184
+ npm run build
185
+ npm test # 187 tests
186
+ ```
187
+
188
+ ## Documentation
189
+
190
+ Full documentation is on the [GitHub Wiki](https://github.com/Articulated7/enfusion-mcp/wiki).
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,28 @@
1
+ export interface PakFileEntry {
2
+ kind: "file";
3
+ name: string;
4
+ /** Byte offset of this file's data within the DATA chunk payload */
5
+ offset: number;
6
+ compressedLen: number;
7
+ decompressedLen: number;
8
+ compressed: boolean;
9
+ }
10
+ export interface PakDirEntry {
11
+ kind: "dir";
12
+ name: string;
13
+ children: Map<string, PakDirEntry | PakFileEntry>;
14
+ }
15
+ export interface PakIndex {
16
+ root: PakDirEntry;
17
+ /** Absolute byte position in the .pak file where the DATA payload starts */
18
+ dataStart: number;
19
+ /** Path to the .pak file on disk */
20
+ pakPath: string;
21
+ }
22
+ /**
23
+ * Parse a .pak file's metadata without reading the DATA payload.
24
+ * Reads chunk headers to locate the DATA and FILE sections, then parses
25
+ * the FILE chunk's recursive entry tree into an in-memory directory structure.
26
+ */
27
+ export declare function parsePakIndex(pakPath: string): PakIndex;
28
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../src/pak/reader.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CA+DvD"}
@@ -0,0 +1,141 @@
1
+ import { openSync, readSync, closeSync, fstatSync } from "node:fs";
2
+ import { logger } from "../utils/logger.js";
3
+ // ── Chunk magic constants ────────────────────────────────────────────────────
4
+ const MAGIC_FORM = 0x464f524d; // "FORM"
5
+ const MAGIC_PAC1 = 0x50414331; // "PAC1"
6
+ const MAGIC_HEAD = 0x48454144; // "HEAD"
7
+ const MAGIC_DATA = 0x44415441; // "DATA"
8
+ const MAGIC_FILE = 0x46494c45; // "FILE"
9
+ // ── Parser ───────────────────────────────────────────────────────────────────
10
+ /**
11
+ * Parse a .pak file's metadata without reading the DATA payload.
12
+ * Reads chunk headers to locate the DATA and FILE sections, then parses
13
+ * the FILE chunk's recursive entry tree into an in-memory directory structure.
14
+ */
15
+ export function parsePakIndex(pakPath) {
16
+ const fd = openSync(pakPath, "r");
17
+ try {
18
+ const fileSize = fstatSync(fd).size;
19
+ // ── FORM chunk ───────────────────────────────────────────────────────
20
+ // Layout: "FORM" (4B) + u32BE size + "PAC1" (4B) = 12 bytes
21
+ const formBuf = readAt(fd, 0, 12);
22
+ const formMagic = formBuf.readUInt32BE(0);
23
+ if (formMagic !== MAGIC_FORM) {
24
+ throw new Error(`Not a PAK file: missing FORM header (got 0x${formMagic.toString(16)})`);
25
+ }
26
+ const pac1 = formBuf.readUInt32BE(8);
27
+ if (pac1 !== MAGIC_PAC1) {
28
+ throw new Error(`Not a PAK file: expected PAC1 identifier (got 0x${pac1.toString(16)})`);
29
+ }
30
+ // ── Walk chunks after FORM header ────────────────────────────────────
31
+ // Chunks start at offset 12 (after FORM header).
32
+ // Each chunk: magic (4B BE) + size (4B BE) + payload (size bytes).
33
+ let pos = 12;
34
+ let dataStart = -1;
35
+ let fileChunkOffset = -1;
36
+ let fileChunkLen = 0;
37
+ while (pos + 8 <= fileSize) {
38
+ const hdr = readAt(fd, pos, 8);
39
+ const magic = hdr.readUInt32BE(0);
40
+ const chunkLen = hdr.readUInt32BE(4);
41
+ if (magic === MAGIC_HEAD) {
42
+ // Skip HEAD chunk entirely
43
+ pos += 8 + chunkLen;
44
+ }
45
+ else if (magic === MAGIC_DATA) {
46
+ // Record where the DATA payload begins (right after its 8-byte header)
47
+ dataStart = pos + 8;
48
+ pos += 8 + chunkLen;
49
+ }
50
+ else if (magic === MAGIC_FILE) {
51
+ fileChunkOffset = pos + 8;
52
+ fileChunkLen = chunkLen;
53
+ break; // FILE is the last chunk we care about
54
+ }
55
+ else {
56
+ // Unknown chunk — skip it
57
+ logger.debug(`PAK unknown chunk 0x${magic.toString(16)} at offset ${pos}, skipping`);
58
+ pos += 8 + chunkLen;
59
+ }
60
+ }
61
+ if (dataStart < 0) {
62
+ throw new Error("PAK file missing DATA chunk");
63
+ }
64
+ if (fileChunkOffset < 0) {
65
+ throw new Error("PAK file missing FILE chunk");
66
+ }
67
+ // ── Parse FILE chunk ─────────────────────────────────────────────────
68
+ const fileBuf = readAt(fd, fileChunkOffset, fileChunkLen);
69
+ const root = parseFileTree(fileBuf);
70
+ return { root, dataStart, pakPath };
71
+ }
72
+ finally {
73
+ closeSync(fd);
74
+ }
75
+ }
76
+ // ── FILE tree parser ─────────────────────────────────────────────────────────
77
+ /**
78
+ * Parse the recursive file entry tree from the FILE chunk payload.
79
+ * Uses an iterative approach with an explicit stack to avoid deep recursion.
80
+ */
81
+ function parseFileTree(buf) {
82
+ const state = { offset: 0 };
83
+ // The FILE chunk contains a single root entry (always a directory)
84
+ const root = parseEntry(buf, state);
85
+ if (root.kind !== "dir") {
86
+ throw new Error("PAK FILE chunk root entry is not a directory");
87
+ }
88
+ return root;
89
+ }
90
+ function parseEntry(buf, state) {
91
+ const entryKind = buf.readUInt8(state.offset);
92
+ state.offset += 1;
93
+ const nameLen = buf.readUInt8(state.offset);
94
+ state.offset += 1;
95
+ const name = buf.toString("utf8", state.offset, state.offset + nameLen);
96
+ state.offset += nameLen;
97
+ if (entryKind === 0) {
98
+ // Directory
99
+ const childCount = buf.readUInt32LE(state.offset);
100
+ state.offset += 4;
101
+ const children = new Map();
102
+ for (let i = 0; i < childCount; i++) {
103
+ const child = parseEntry(buf, state);
104
+ children.set(child.name, child);
105
+ }
106
+ return { kind: "dir", name, children };
107
+ }
108
+ else {
109
+ // File
110
+ const offset = buf.readUInt32LE(state.offset);
111
+ state.offset += 4;
112
+ const compressedLen = buf.readUInt32LE(state.offset);
113
+ state.offset += 4;
114
+ const decompressedLen = buf.readUInt32LE(state.offset);
115
+ state.offset += 4;
116
+ // Skip unknown (u32LE) + unk2 (u16LE)
117
+ state.offset += 6;
118
+ const compressed = buf.readUInt8(state.offset) !== 0;
119
+ state.offset += 1;
120
+ // Skip compression_level (u8) + timestamp (u32LE)
121
+ state.offset += 5;
122
+ return {
123
+ kind: "file",
124
+ name,
125
+ offset,
126
+ compressedLen,
127
+ decompressedLen,
128
+ compressed,
129
+ };
130
+ }
131
+ }
132
+ // ── Helpers ──────────────────────────────────────────────────────────────────
133
+ function readAt(fd, position, length) {
134
+ const buf = Buffer.alloc(length);
135
+ const bytesRead = readSync(fd, buf, 0, length, position);
136
+ if (bytesRead < length) {
137
+ throw new Error(`Unexpected EOF: wanted ${length} bytes at offset ${position}, got ${bytesRead}`);
138
+ }
139
+ return buf;
140
+ }
141
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../../src/pak/reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export interface VfsEntry {
2
+ name: string;
3
+ isDirectory: boolean;
4
+ /** Decompressed size for files, 0 for directories */
5
+ size: number;
6
+ }
7
+ /**
8
+ * Virtual filesystem that merges all .pak files in the game's addons/ directory
9
+ * into a single unified file tree. Supports directory listing, file existence
10
+ * checks, and on-demand file reading with automatic zlib decompression.
11
+ *
12
+ * Instantiated lazily as a singleton and cached for the session lifetime.
13
+ */
14
+ export declare class PakVirtualFS {
15
+ private static instance;
16
+ private static instanceGamePath;
17
+ /** Flat lookup: normalized virtual path → file reference */
18
+ private fileIndex;
19
+ /** Merged directory tree for browsing */
20
+ private root;
21
+ /**
22
+ * Get or create the singleton VFS for the given game path.
23
+ * Returns null if no .pak files are found.
24
+ */
25
+ static get(gamePath: string): PakVirtualFS | null;
26
+ private constructor();
27
+ /**
28
+ * List entries in a virtual directory.
29
+ * Path uses forward slashes, no leading slash (e.g., "Prefabs/Weapons").
30
+ * Empty string = root.
31
+ */
32
+ listDir(virtualPath: string): VfsEntry[];
33
+ /** Check if a path exists (file or directory). */
34
+ exists(virtualPath: string): boolean;
35
+ /**
36
+ * Read a file's raw bytes from the pak archive.
37
+ * Opens the .pak, seeks to the correct offset, reads, decompresses if needed.
38
+ */
39
+ readFile(virtualPath: string): Buffer;
40
+ /** Read a file as UTF-8 text. */
41
+ readTextFile(virtualPath: string): string;
42
+ /** Get decompressed file size without reading/inflating. Returns -1 if not found. */
43
+ fileSize(virtualPath: string): number;
44
+ /** Get all file paths in the VFS (for building the asset search index). */
45
+ allFilePaths(): string[];
46
+ /** Get the number of indexed files. */
47
+ get fileCount(): number;
48
+ /**
49
+ * Merge a parsed pak tree into the unified directory tree.
50
+ * Returns the number of file entries added.
51
+ */
52
+ private mergeTree;
53
+ /** Resolve a virtual path to a directory entry, or null if not found. */
54
+ private resolveDir;
55
+ }
56
+ //# sourceMappingURL=vfs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vfs.d.ts","sourceRoot":"","sources":["../../src/pak/vfs.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;CACd;AAUD;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IACpD,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAuB;IAEtD,4DAA4D;IAC5D,OAAO,CAAC,SAAS,CAA8B;IAC/C,yCAAyC;IACzC,OAAO,CAAC,IAAI,CAA+D;IAE3E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IA0BjD,OAAO;IAwBP;;;;OAIG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,EAAE;IAexC,kDAAkD;IAClD,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAMpC;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IA8BrC,iCAAiC;IACjC,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIzC,qFAAqF;IACrF,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAMrC,2EAA2E;IAC3E,YAAY,IAAI,MAAM,EAAE;IAIxB,uCAAuC;IACvC,IAAI,SAAS,IAAI,MAAM,CAEtB;IAID;;;OAGG;IACH,OAAO,CAAC,SAAS;IAqCjB,yEAAyE;IACzE,OAAO,CAAC,UAAU;CAenB"}