You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1256 pool entries), and base zones (`ghosts ps3 common.ff`, 29.8 MB).
7
+
All Ghosts (IW6) version `0x22E`. No PC, Xbox 360, or Wii-U samples
8
+
have been tested; layout details below apply to PS3 only.
8
9
9
10
**Current implementation status:**
10
11
- ✅ Detect Ghosts via `IWff0100` outer magic + version `0x22E` + `IWffS100` inner magic
11
12
- ✅ Decompress and inflate to zone in one pass (`FastFileProcessor.TryDecompressGhosts`)
12
13
- Outer raw-deflate blocks (2-byte BE srcSize + payload, 64 KB out each)
13
14
- Inner per-asset zlib streams expanded inline against decoded `compressedLen` from each asset header
14
-
- 🟡 Walk the asset pool (`GhostsZoneParser`) — works for **patch FFs only**. Patches use 8-byte `[FF*4][type BE u32]` pool entries starting at `0x40`; base FFs use a different layout (a long run of pointer placeholders with type information stored elsewhere) and aren't walked correctly yet.
15
-
- ✅ Asset pool tab populates in the editor for patch FFs (type names from `GhostsAssetTypePS3`); base FFs decompress + inflate fine but the asset-pool tab stays empty.
16
-
- ❌ Per-asset content parsers (rawfile body, scriptfile body, weapon struct, etc.) — not implemented; all `Parse*` methods on `GhostsGameDefinition` return null
17
-
- ❌ Recompression / re-signing — not implemented and not viable without IW's RSA-2048 private key
15
+
- ✅ Walk the asset pool (`FastFileLib.GhostsZoneLayout`) — header-counts-driven, works for **patch FFs, DLC-updated zones, and base zones**. Reads `tagCount` @ `0x28` and `assetCount` @ `0x30` to navigate past the tag-string region; falls back to a brute scan when header counts are missing or layout is unexpected.
16
+
- ✅ Asset pool tab populates in the editor for every tested zone type, with type names from `GhostsAssetTypePS3`.
17
+
- ✅ Pair pool entries with asset bodies for **rawfile** (zlib-wrapped short header), **scriptfile** (zlib-wrapped long header), and **luafile** (flat 16-byte header). Editor surfaces names + offsets + body bytes for these in both the Asset Pool tab and the Raw Files tab. Flat-binary types (xmodel/image/sound/techset/weapon/material/…) are listed but not opened — they'd each need their own struct parser.
18
+
- ✅ Luafile bodies feed `FastFileLib.LuaBytecodeInspector`, which surfaces an extracted-strings summary in the editor's text viewer (Lua source isn't in the FF — IW6 ships compiled bytecode with custom format byte `0x0D`).
19
+
- ❌ Per-asset content parsers for other types (weapon struct, image, sound, …) — not implemented; `GhostsGameDefinition.Parse*` methods all return null.
20
+
- ❌ Recompression / re-signing — not implemented and not viable without IW's RSA-2048 private key.
18
21
19
22
## TL;DR — IW6 PS3 vs the previous IW signed-FF design
20
23
@@ -162,53 +165,71 @@ trailing data that follows the last complete block.
162
165
163
166
## Zone format (after the outer deflate, before inner-zlib inflation)
164
167
168
+
Same shape as CoD4/WaW/MW2: a fixed XFile header gives counts that drive
169
+
navigation through the tag pointer table and tag strings, then the asset
170
+
pool follows, then asset bodies. The layout is uniform across patch / DLC /
171
+
base zones — only the count values differ.
172
+
165
173
```
166
-
0x000 XFile header
167
-
0x040 Asset pool (8 bytes per entry, terminated by pattern break)
The pointer field has **four observed conventions** — any 4-byte value is
224
+
accepted by the pool walker; the strict type-word structure plus the
225
+
header's `assetCount` cap are what delimit the pool:
209
226
210
-
Base zones use a different layout (see XFile header section above) and
211
-
this 8-byte `[ptr][type]` walking doesn't apply.
227
+
| Pattern | Meaning |
228
+
|---|---|
229
+
|`FF FF FF FF`| Standard inline placeholder (most common) |
230
+
|`00 00 00 00`| NULL — seen on the first scriptfile entry of `patch_mp_prisonbreak.zone`|
231
+
|`80 XX XX XX`| High-bit-set resolved pointer (CoD4/WaW convention) |
232
+
|`40 XX XX XX`|**0x40-flagged resolved pointer** — used by IW6, e.g. `40 1F DF 85` in `patch_ui_mp.zone` (material → image references). Earlier code that only accepted the `0x80`-flag form found 87 / 1256 entries in this zone. |
212
233
213
234
### Asset type IDs (IW6 PS3)
214
235
@@ -360,8 +381,74 @@ inline as part of decompression, so a zone produced by this codebase has
360
381
no remaining `78 XX` streams (verified: 0 residual streams in all four
|`0x0B`|`00`| integral flag (floating-point lua_Number) |
430
+
431
+
The chunk body past the 12-byte header doesn't follow stock Lua 5.1 chunk
432
+
layout — IW6 uses a customized chunk format whose string-constant length
433
+
prefix is 1 byte (not `sizeof(size_t)`) at least for the type-registry
434
+
strings (`TNIL`, `TBOOLEAN`, `TLIGHTUSERDATA`, …) at the chunk start. Full
435
+
chunk layout hasn't been reverse-engineered.
436
+
437
+
`FastFileLib.LuaBytecodeInspector` works around the custom format by
438
+
**not** parsing chunks: it reads the 12-byte header for metadata and then
439
+
ASCII-scans the body for every printable null-terminated run of length
440
+
3–256. That surfaces the useful signal (menu / widget / function /
441
+
identifier names) without depending on chunk structure — e.g. for
442
+
`ui/lui/mp_menus/clandetails.lua` it produces ~803 extracted strings
443
+
including `OnCreate`, `UpdateClanDetails`, `clan_details_main`,
444
+
`MenuBuilder`, etc., letting a reader understand what the menu does.
445
+
363
446
## How the editor handles Ghosts
364
447
448
+
All Ghosts pool-layout, header-scan, pairing, and Lua-summary logic lives in
449
+
`FastFileLib`. The editor classes are thin shims that adapt library DTOs to
450
+
the editor's model types — same pattern as `RawFileScanner` ↔ `RawFileParser`.
451
+
365
452
| Component | Behaviour |
366
453
|---|---|
367
454
|`FastFileInfo.DetectGameVersion`| Recognises version `0x22E` as Ghosts; reports platform as PS3 (signed magic with this version is not Xbox 360) |
@@ -370,27 +457,54 @@ samples after `TryDecompressGhosts` completes).
370
457
|`FastFileProcessor.Decompress`| Short-circuits to `TryDecompressGhosts` before the shared CoD4/WaW/MW2 dispatch |
371
458
|`FastFileProcessor.TryDecompressGhosts`| Two passes: outer raw-deflate blocks → raw zone, then `InflateGhostsZoneAssets` expands inner zlib streams inline |
372
459
|`FastFileProcessor.TryReadGhostsAssetHeader`| Recognises both "long" (8 trailing FFs) and "short" (4 trailing FFs) header shapes; reads `compLen` from the first u32 after the leading-FF block |
460
+
|`FastFileLib.GhostsZoneLayout`| Library home for pool location (`LocatePool` + `WalkPool`), wrapped-asset header scan (`LocateAllHeaders`), luafile scan (`LocateAllLuaFiles`), and positional pool↔header pairing (`PairPoolWithHeaders`). Header-counts-driven, handles patch + DLC + base zones. |
461
+
|`FastFileLib.LuaBytecodeInspector`| Parses Lua 5.1 header + ASCII-scans body for null-terminated printable strings. Format-agnostic to handle IW6's custom format byte `0x0D`. |
373
462
|`ZoneFile.Load`| For Ghosts, skips `ReadHeaderFields` / `StructureBasedZoneParser` (those depend on PS3-MW2 header offsets) and runs `GhostsZoneParser` instead |
374
-
|`GhostsZoneParser`| Searches first `0x200` bytes for the longest run of valid `[FFFFFFFF][type ≤ 0x36 BE u32]` entries, then walks until the pattern breaks |
375
-
|`GhostsGameDefinition`| Maps the 54 IW6 PS3 asset type IDs to names. All `Parse*` content methods return null — pool listing only |
376
-
|`MainWindowForm.OpenFastFile`| Skips the asset-selection dialog for Ghosts (no content parsers, nothing to select); otherwise runs the normal flow so the asset-pool tab populates |
377
-
|`ZoneHexViewForm`| Uses `GhostsGameDefinition.GetAssetTypeName` for the type column; per-asset content panels stay empty |
463
+
|`Editor: GhostsZoneParser`| Thin shim over `GhostsZoneLayout.ParsePool` — translates `GhostsPoolEntry` DTOs to `ZoneAssetRecord` and writes them onto the `ZoneFile`. |
464
+
|`Editor: GhostsAssetWalker`| Thin shim over `GhostsZoneLayout.LocateAllHeaders` + `LocateAllLuaFiles` + `PairPoolWithHeaders`. Mutates each `ZoneAssetRecord` with resolved offsets/names; emits `RawFileNode`s for rawfile + luafile entries (the luafile's `RawFileContent` is the `LuaBytecodeInspector` summary). |
465
+
|`GhostsGameDefinition`| Maps the 54 IW6 PS3 asset type IDs to names. All `Parse*` content methods return null — content is sourced via the walker instead. |
466
+
|`MainWindowForm.OpenFastFile`| Skips the asset-selection dialog for Ghosts (most types still have no content parsers); otherwise runs the normal flow so the asset-pool tab populates. |
467
+
|`UIManager.UpdateLoadedFileNameStatusStrip`| Includes `IsGhostsFile` in the `gameString` branch so the status bar shows `Ghosts: <name>` for IW6 files. |
468
+
|`ZoneHexViewForm`| Uses `GhostsGameDefinition.GetAssetTypeName` for the type column; per-asset content panels stay empty for non-wrapped/non-lua types |
378
469
379
470
## Known unknowns
380
471
381
-
-**XFile header structure.** Block-size fields, `AssetCount` location, and any
382
-
additional metadata fields between the XFile header and the asset pool are
383
-
not mapped. The pool walker works around this by pattern-matching pool entries.
472
+
-**XFile header field semantics.** Counts at `0x28` and `0x30` are mapped
473
+
(tagCount + assetCount); other u32s in `0x00..0x27` are zone size + block
474
+
size slots but exact roles unconfirmed. The pool walker doesn't need them.
475
+
-**Trailing field between tag strings and pool.** DLC zones have 4 bytes
476
+
between the last tag string's null and pool[0] (`00 00 00 30` in
0 commit comments