Repository navigation
Research Report R2 Newassets
Every existing writer in this project replaces the payload of an entry that already exists in an archive's entry table; none of them change the entry count, string table, or dependency table — a previously-flagged open gap. This note asks whether adding a genuinely new (type, name) entry is a hard engine limitation or just a missing tool, and shows structurally that it is the latter. A new entry — reusing an existing type string, or introducing a brand-new type string to a container — was hand-built and round-tripped end to end, re-read correctly by the project's own reader, and passed every independent hash check with real negative controls. Critically, adding a new name sidesteps the still-unresolved question of which of two containers wins when they both claim the same name — a brand-new name has nothing to collide with. Everything here is a structural proof against the file format and the project's own readers; whether the live game engine accepts an appended container at mount time is the one remaining untested gap.
| Layer | Must change for an add? |
|---|---|
| Entry table | Yes — one new 144-byte row, entry count +1 |
| String table | Yes — +1 row for a new name, or +2 if the type string is also new to the container |
| String-index pool | Yes — +2 index values for the new entry's (type, name) pair |
| Entry hashes | Yes, computed from the already-solved formulas (not copied) |
| Header hashes | Yes, computed in order — metadata hash first, then the header hash that covers it |
| Dependency table | No, if nothing yet references the new entry |
| Map preload list | Optional — an unlisted resource is created and loaded on first reference; a preload line is retail convention (100% coverage) and a performance pre-cache, not a requirement |
| Container identity |
No restriction found — a new entry can live in any mounted container, including a small, purpose-built add-on, exactly like over a thousand real retail (type, name) pairs that exist only in a patch archive |
Two cases were built and verified from the same unmodified source archive: (1) an existing type string, new name; (2) a brand-new type string the archive had never carried at all. Both:
- left every original entry byte-identical (same hash, same offset, same payload);
- used uncompressed payloads for the new entry (no compression dependency);
- fit inside the source archive's existing metadata slack, so no existing payload offset moved. (The code path for when metadata growth would not fit that slack — shifting the data-start address and every later payload offset — is the same relocation math the project's existing archive-rebuild function already performs for a replace; it was not separately implemented here, the tool raises rather than guess.)
Checks: the project's reader finds the new entry by (type, name) with exactly 1 hit; an exhaustive per-entry data-hash check passes on all entries including the new one; both header hashes pass; the dependency-staleness check correctly reports nothing stale (nothing references the new entry yet).
Negative controls, each caught:
- Flipping one payload byte of the new entry → the data-hash check flags exactly that entry.
- Corrupting the header's entry count → does not crash the reader. The string/dependency table addresses are independent header fields, so the file parses fine and the new entry is simply hidden (a useful correction: a wrong count silently omits, it does not corrupt).
- Pointing the new entry's string-index at an existing entry's name instead of its own → no crash either; it silently creates a duplicate-name alias, the same hazard the project's own reader already documents for several hundred genuine duplicate-name pairs that exist in retail data.
- Corrupting only the metadata hash → both the metadata hash and the header hash are correctly flagged as bad (the header hash covers the range containing the metadata hash, a documented build-order dependency).
Name resolution for a decl/resource is not a fixed, compiled-in list. The resource-load function searches by name and, if not already known, creates and loads it fresh — across every currently-mounted container. This is literally how an unlisted resource still loads on first reference, and the same mechanism that lets patch- and DLC-only assets exist at all in retail data.
Fresh disassembly this pass: a registry address previously named by other work belongs to a function with 9 call sites across 6 functions, one of them inside the already-known inherit-parser for weapon/ammo-type decls, called immediately after loading the parent decl. This reads as runtime dependency/hot-reload bookkeeping (recording what the parent decl pointer is), not the name→resource binding itself — inferred from call-site shape, not a full decompile.
Important distinction: inventing a brand-new decl type (not just a new name within an existing type) is a much bigger undertaking — each type is a compiled-in parser class reached by RTTI/vtable dispatch, and nothing in the traced data-driven path creates a new one. Not attempted; no evidence here supports it being possible without binary-patching the executable.
- Metadata growth beyond a given archive's existing slack — a known, bounded extension of the existing rebuild math, just not separately implemented in this proof; check large archives individually before relying on in-place growth.
- Whether the live engine's mount-time check accepts an appended container beyond what the already-measured hash checks require.
In a custom-map container, append a new ammo decl under a name that does not exist in retail, with a distinctive clip size. Retarget the already-proven pistol-decl-edit technique to point the pistol at that new ammo name instead of the real one. Install, load the custom map, pick up the pistol, and read the HUD magazine count: the distinctive new value appearing confirms the newly-added, retail-absent decl name was found and loaded by the live engine — the one gap that cannot be proven at a desk.
English Wiki generated from the measured research notes in docs/. Evidence grades and uncertainty are preserved intentionally.
- WolfSDK Loader · DE
- WolfSDK Studio · DE
- FAQ · DE
- Mod format · DE
- Modding Guide
- Modding Surface
- CVar Catalog
- CVar Validation (old scrape)
- Texture Database
- Developer Mode
- Dev Menu
- Custom-Map Status