Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / Research reportsForschungsberichte

The cfile Carrier: Smallest DLC to Carry a Resource

Partly solvedTeilweise gelöst Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

This report builds the smallest possible DLC package for Wolfenstein II: The New Colossus. It holds just one encrypted config file that, if the game runs it, should load a custom map. The package passes 21 of its own tests plus two independent checkers, and the file can be decrypted back out of the finished archive correctly. Whether the game mounts the package and runs the file has not been tested; the game was not started.

  • The package uses DLC ID 5 and chunk ID 26, both checked to be unused in the installation.
  • The payload decrypts from the built archive to 'map game/dlc/c02/c2v1_custom', which proves packing did not damage it.
  • An empty 32-byte texture database is enough: the game itself ships an identical empty one.
  • The installer passed 11 of 11 tests on a throwaway copy; it refuses to overwrite and only uninstalls its own folder.
  • Main open question: whether DLC archives are already mounted in the main menu, where the console command is typed.

Dieser Bericht baut das kleinstmögliche DLC-Paket für Wolfenstein II: The New Colossus. Es enthält nur eine verschlüsselte Config-Datei, die – wenn das Spiel sie ausführt – eine eigene Karte laden soll. Das Paket besteht 21 eigene Tests und zwei unabhängige Prüfer, und die Datei lässt sich korrekt aus dem fertigen Archiv wieder entschlüsseln. Ob das Spiel das Paket einbindet und die Datei ausführt, ist nicht getestet; das Spiel wurde nicht gestartet.

  • Das Paket nutzt DLC-ID 5 und Chunk-ID 26; beide sind laut Prüfung in der Installation frei.
  • Die Nutzlast entschlüsselt sich aus dem gebauten Archiv zu „map game/dlc/c02/c2v1_custom“ – das Packen hat sie also nicht beschädigt.
  • Eine leere, 32 Byte große Texturdatenbank reicht: Das Spiel liefert selbst eine identische leere Datei aus.
  • Das Installationsskript bestand 11 von 11 Tests an einer Wegwerf-Kopie; es überschreibt nichts und deinstalliert nur den eigenen Ordner.
  • Wichtigste offene Frage: ob DLC-Archive im Hauptmenü, wo der Konsolenbefehl eingegeben wird, schon eingebunden sind.

Technical detailsTechnische Details

Der technische Teil ist auf Englisch – so, wie er im Wiki steht. Unterstrichene Begriffe haben eine Erklärung auf Deutsch.

Worker w-execcarrier-a1, Task execcarrier, 2026-09-23. New: tools/build_exec_carrier.py, tools/install_exec_carrier.py, re_probes/execcarrier/dlc_5/.

Result in One Sentence

The carrier stands and passes 21 own plus 2 foreign tests, including the only one that truly counts: the payload can be extracted from the built container to map game/dlc/c02/c2v1_custom\n. Structurally valid after these tests; Mount unconfirmed — the game was not started and nothing was written to the game folder.

1. What Was Built

re_probes/execcarrier/dlc_5/
   dlcinfo.json                     19 B   {"dlcid" : 5}
   base/packagemapspec.json        233 B   chunkid 26, map game/dlc/c05/c5v1
   base/chunk_26.resources      65 632 B   1 Entry: cfile:wolfsdk_test.cfg
   base/chunk_26.texdb              32 B   empty

Entry: comp 0, csize == usize == 96, offset 65536, version 1, flags 0, 0 dependencies, +0x50 == +0x60 == 0x0e4bf70cb4ffc3e4.

None of this is newly invented. The container is laid with the existing tools/dlcmod.baue_archiv and hashed with wolfsdk/idclwrite.finalize — the writer that today has byte-identically rewritten three Retail archives. tools/build_exec_carrier.py consists essentially of tests, not build steps.

Two decisions that need a justification:

  • hash_0x60 follows +0x50. baue_archiv wants a template entry; the template is a delivered cfile from base/patch_4.resources (therefore version 1, flags 0 measured rather than guessed). Its +0x60 deviates like all Retail cfile from +0x50, and idclwrite.hash60_rule would therefore preserve it — the new entry then takes the foreign 64-bit value of default.cfg. The template is thus passed with hash_0x60 = data_hash so that finalize() follows the newly calculated +0x50. No reader calculates +0x60 (idclwriter.md §7), and dependency records do not exist here — but an explainable value is preferable to a borrowed one.
  • mapspec matches dlc_3, the simplest Retail DLC: own map name, basechunkid 6, layer -1, no refchunks key, patchlevel 4. patchlevel is the search limit for patch_N layers and base/ reports 4; a smaller value could remove map patch layers (docs/dlcmod.md §2).

2. The IDs — What Is Proven and Why 5 / 26

python tools/build_exec_carrier.py --ids reads the installation from two sources (file names and all packagemapspec.json, including basechunkid and refchunks), because an ID can exist in a Spec without the file existing, and vice versa:

proven chunk-IDs: [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 22, 24]
proven dlc-IDs  : [1, 2, 3, 4]
selected        : dlcid=5  chunkid=26
free            : yes

dlcid 5, because 1–3 are Retail and 4 carries the Map Alias. chunkid 26, because it is free and continues the Retail pattern (DLC chunks are 20, 22, 24). Both are checked against the installation on every run, not just asserted once.

Note without restriction: the permission layer of the Exe knows only ENTITLEMENT_DLC1/2/3 (docs/dlcmod.md §4). dlc_4 mounts anyway, so dlcid 5 is no worse placed there — but it is not proven for 5.

3. Acceptance

3.1 The Own Tests — 21/21

python tools/build_exec_carrier.py

ok exactly 1 entry ok Type 'cfile' ok Name exactly 'wolfsdk_test.cfg' ok compression == 0 ok csize == usize == PayloadLength 96 -- no padding bytes, otherwise Length()-32-Tell() fails ok Payload byte-identical to wolfsdk_test.cfg.cfile ok decrypted from container: b'map game/dlc/c02/c2v1_custom\n' ok declen 0xFFFFFFFF (unpacked) ok Metadata ends on the IDCL marker ok Header hash +0x20 (FarmHash64 over the metadata) ok Header hash +0x08 ok no legacy entry hash at +0x50

Validation checks (must fail) ok case mismatch fails through ok 'base/' prefix fails through ok 16 appended padding bytes break the tag

TexDB ok Magic 4FA5C2292EF3C761 -- the only thing MountDatabase checks ok 8 Magic + 16 Build-ID + u64 entry count 0 = 32 bytes ok byte-identical to delivered patch_4_chunkbase_6.texdb

JSON ok mapspec reports chunkid 26 ok chunkid 26 is free in the installation ok dlcid 5 is free in the installation

21/21 tests passed

The seventh line is the end-to-end test requested by the assignment: idcl.Archive.read_raw() on the built container, then probe_cfilekey.decrypt(). It verifies the HMAC tag and PKCS7 over exactly the bytes left behind by the archive construction — container checks alone do not see a shifted or padded payload.

The three validation checks are not decorative: they are precisely the three ways this carrier could be built incorrectly (renamed name, 'base/' prefix, padding bytes after the payload), and all three fail at the tag.

3.2 The Foreign Validators

python tools/probe_datahash.py re_probes/execcarrier/dlc_5/base/chunk_26.resources
   chunk_26.resources   1/1   (skipped 0 of 1 entries, mode 4)
   +0x50  1/1                                              exit 0

python tools/probe_idclhash.py re_probes/execcarrier/dlc_5/base/chunk_26.resources
   v12  meta=212  tail=IDCL
   +0x08 e7256786c21d5c2f/e7256786c21d5c2f OK
   +0x20 c0fa759caf99cebe/c0fa759caf99cebe OK               exit 0

Both validators can fail on this file — measured on disposable copies in the scratchpad, not claimed:

Manipulation Validator Result
one payload byte flipped probe_datahash BAD cfile:wolfsdk_test.cfg, 0/1, exit 1
header byte at 0x18 flipped probe_idclhash +0x08 …/b2d79b78b1a31c43 BAD, 0/1, exit 1

3.3 The TexDB Claim — Proven, Not Assumed

The assignment requires testing the claim "a minimal TexDB suffices" against the header format. It is more strongly proven than: the game delivers an empty TexDB itself. base/patch_4_chunkbase_6.texdb is 32 bytes large — 8 bytes Magic 4FA5C2292EF3C761, 16 bytes Build-ID, u64 entry count 0 — and belongs to a 401-KB container mounted in the retail game. Our chunk_26.texdb is byte-identical to this file (verified, not reconstructed). This matches the code: MountDatabase (0x792E00) checks exclusively the Magic and the two read sizes according to texdbkey.md §2, and the 16 bytes Build-ID are copied but never compared. The container contains no image anyway, so no key will ever be looked up.

3.4 The Installer — 11/11 on a Disposable Copy

python tools/install_exec_carrier.py --selbsttest builds a dummy game folder with dlc/dlc_1..4 in the temp directory and runs both paths:

  ok   install places dlc_5
  ok   all four files present
  ok   Container byte-identical copy
  ok   second install aborts without overwriting
  ok   no remainder of a temporary folder
  ok   uninstall refuses foreign content
  ok   foreign content still present (nothing deleted)
  ok   --dry-run does not delete
  ok   uninstall removes dlc_5
  ok   uninstall without installation aborts
  ok   dlc_1..4 untouched

The properties required by the task and how they are enforced:

  • Prints before touching. Every run lists the four target paths with size before anything happens. --dry-run prints only.
  • Never overwrites. If dlc/dlc_5 exists, it is an abort.
  • No-op on failure. Copying goes to dlc_5.installing; only then does os.replace create dlc_5. Any exception removes the intermediate folder; a half-written DLC cannot exist.
  • uninstall cannot hit a Retail DLC. Only a folder containing exactly those four files and whose dlcinfo.json reports dlcid 5 is deleted. A wrong --game-root or a foreign file in the folder lead to an abort, not deletion.

Against the actual game folder, only install --dry-run ran: the plan was printed, and dlc/ contained unchanged dlc_1..4. dlc_4 was only read.

4. What remains open — and it is the most important thing

The mount time point of DLC chunks is not documented, and this determines whether the console line works.

What was measured (docs/dlcmod.md §3.1/§3.3): gameresources and chunkbase_6 are mounted for every map, so they do not double-count a DLC; registered refchunks are mounted, unregistered chunks must be doubled (15 against 1,495 copies, six out of six cells). The Exe explicitly logs this: GetIsChunkForMapInstalled: chunk %d for map %s is not installed.

All of this describes mounting during a map loading process. Whether a DLC chunk is already mounted in the main menu — the moment the console line is typed — is undecidable from the files and was not followed up via disassembly here; that would be a separate task.

From this, a testable prediction and its two outcomes:

  • The map loads → the chunk was mounted, the chain is complete.
  • Again cfile:wolfsdk_test.cfg:NONE can't generate in production → the name was correctly resolved but not found in any mounted container. Then it is not the Traeger that is wrong (§3 excludes this), but the chunk was not mounted at this time point. The next step would then be to put the same payload into a container that is demonstrably mounted in the main menu — not by screwing on this Traeger.
  • Any third message → belongs in the report, not in a repair.

Cross-check for the log if it fails: qconsole.log contains SearchDLC: found %d dlcs and found dlc id %d at %s. If there is no dlc id 5 there, the package was not even recognized (then dlcinfo.json or the permission layer is the cause); if it is there but the resource is still missing, it is exactly the mount time point above.

Smaller open points:

  • resourceExec knows about a -s according to its Usage string (USAGE: resourceExec <filename> -s [define]), and the Retail start commands use it (resourceExec default.cfg -s). The current runtime attempt without -s has already resolved the name to cfile:wolfsdk_test.cfg, so the measured form is in the Runbook. If it stays silent, resourceExec wolfsdk_test.cfg -s is the second attempt.
  • RS_ValidateContainers is one of the 34 unlocked commands and could provide information about mounted containers (Validating %d containers:), but requires filenames as arguments and is not suitable for a five-minute window on a 40-GB installation.
  • The Traeger is 65,632 bytes in size; 65,536 bytes of alignment padding precede the data area — this is the 64-KiB alignment of addr_data, which Retail also uses, and it is not waste worth optimizing.

5. Reproduction

python tools/build_exec_carrier.py # build + 21 tests python tools/build_exec_carrier.py --pruefen # test only python tools/build_exec_carrier.py --ids # show populated IDs python tools/probe_datahash.py re_probes/execcarrier/dlc_5/base/chunk_26.resources python tools/probe_idclhash.py re_probes/execcarrier/dlc_5/base/chunk_26.resources python tools/install_exec_carrier.py --selbsttest python tools/install_exec_carrier.py install --dry-run


Runbook

  1. Install: python tools/install_exec_carrier.py install
  2. Start: Launch the game with +com_enableDeveloperMode 1, click Continue in the Steam dialog, and open the console in the main menu.
  3. Type: resourceExec wolfsdk_test.cfg
  4. Success: The map game/dlc/c02/c2v1_custom loads. If instead you receive can't generate in production, the chunk was not mounted — see Section 4, do not unscrew at Traeger.
  5. Uninstall: python tools/install_exec_carrier.py uninstall