Route cfileindlc4, 2026-09-23. New: tools/build_dlc4_with_cfile.py, re_probes/dlc4_cfile/chunk_22.resources. Nothing installed, the game was not started, nothing written under <Steam library> or Saved Games (only read there).
Result in two sentences
The container is built and passes 21/21 checks. However, the idea behind it does not hold: The Exe selects the DLC directory via the map name, not via the chunk ID. When loading the retail game state game/dlc/c02/c2v1, it mounts ../dlc//dlc_2/base/chunk_22.resources. This also applies with today's dlc_4 spec (18:35), which lists c2v1 as well, because dlc_2 appears before dlc_4 in the DLC list. Our chunk_22 is only mounted when loading c2v1_custom. This finding stems from static analysis of the running Exe and was not measured within the game itself.
1. The Container
| Source | re_probes/entitypipe/base/chunk_22.resources, 7128 entries, sha256 a8da9e4e… (= installed dlc_4) |
| Target | re_probes/dlc4_cfile/chunk_22.resources, 7129 entries, 151 703 648 bytes, sha256 cb3d8bac6fa574173212cc681f75c3043d1a8bc7d925497c4db93f493c807f03 |
| New Entry | Last table position, cfile:wolfsdk_test.cfg, comp 0, csize = usize = 96, 0 dependencies, payload byte-identical to re_probes/cfilekey/wolfsdk_test.cfg.cfile |
Nothing new was written for the build. It is appended using tools/build_tnc_map_alias_v3.write_archive, the function that previously attached 143 alias entries to this container. The line template is the entry from the exe carrier. Afterwards, wolfsdk/idclwrite.finalize runs. Nothing needed recalculation (data_hash 0, hash_0x60 0 changed), so the hashes of write_archive were already correct. The string table grows at the back by cfile and wolfsdk_test.cfg, and the string index pool by 2 entries. All old indices remain valid; therefore, nothing changes in any old entry except the offset.
Running python tools/build_dlc4_with_cfile.py --pruefen yields 21/21:
- 7129 entries on re-read with
wolfsdk/idcl.py. - 7128 old table lines are byte-identical except for
+0x38(offset). The check compared all 144 bytes, including+0x50/+0x60, versions, flags, and run indices. - 7128 payloads are byte-identical. Dependency records (1943) and Dep-index pool are byte-identical. The old string index pool, the old 8821 strings, and their raw bytes are each prefixes of the new ones.
- No payload overlaps another, and the last one ends at the file end. The 5 empty
cmentries share an offset like in Retail. My first, stricter check for unique offsets therefore failed and was corrected. - The new cfile decrypts from the built container to
b'map game/dlc/c02/c2v1_custom\n'. Withbase/names or 16 appended filler bytes, the tag fails. probe_datahash:+0x50 7128/7128(1 entry with compression mode 4 skipped, same as in source), rc 0.probe_idclhash:+0x08 1/1 +0x20 1/1, rc 0.
The control runs with --negativ on a disposable copy in the Temp directory. There, a ciphertext byte and the header byte 0x18 are flipped. Afterwards, probe_datahash, probe_idclhash, and pruefe() must fail. Additionally, zeilen_diff must see changes at +0x50, string_index, and compression, and report nothing on the offset. See §4 for the result.
2. Which chunk_22 is mounted
The check runs with python tools/build_dlc4_with_cfile.py --vorrang (35/35). It checks 29 instructions and 4 strings at fixed RVAs in re_probes/runtime_87400/module.bin. The control run with addresses shifted by about 1 byte hits none of them.
- LoadNamedResourceBlock (
0xac1730):r13holds the map name, populated by the format argument ofchunk missing in LoadNamedResourceBlock for map %s. The execution flow is as follows:0xabf440(MapName)retrieves the Spec entry; the chunk ID resides at[r15+0x30].0xabf500(MapName)retrieves the directory prefix.0xabd940(chunkID)constructschunk_22.resourceswithout a directory path.0x1211e70combines the prefix and name, then mounts at0xacc6b0.
The same logic applies to .texdb. The RefChunks (chunk_9, chunk_13) and chunkbase_6 are mounted without a prefix from base/.
* 0xabf500 and 0xabf440: Both first search in base/packagemapspec.json (this+8), then in the DLC list (this+0x30, 160 bytes per entry, MapSpec at +0x138). The first match wins (jne exits the loop). The prefix is "%s/%s" % (DLC-Path, "base/"). The chunk ID is never compared across DLCs. Therefore, the question "dlc_2 or dlc_4 with the same chunk ID 22" is not posed to the engine at all.
* Order of the DLC List: SearchDLC (0xabdcf0) skips an already existing dlcid (the first wins), otherwise it hangs (0xabe2c6) and logs immediately after found dlc id %d at %s (0xabe503). Thus, the log order corresponds to the list order. Measured in qconsole.loadsave.log: dlc_1, dlc_2, dlc_3, dlc_4.
* Simulated against installed Specs (read-only):
c2v1 -> ../dlc//dlc_2/base/chunk_22.resources,
c2v1_custom -> ../dlc//dlc_4/base/chunk_22.resources.
* Consistent with today's measurement at 18:27: The startup routine 0xabdaa0 mounts only gameresources and Patches. DLC Chunks come exclusively via LoadNamedResourceBlock. Therefore, resourceExec finds nothing in the main menu.
* packagechunklist.json is read at 0xb76330 (chunks/chunkid/packagechunks). This lies in the neighborhood of idStreamingInstallManager, so the file is UNLOCKED as a Streaming-Install Manifest and not a Mount List. The warning is irrelevant for this question.
Summary: dlc_primary.md and map_selection.md held precedence for equal names as "statically unproven". For the directory path, it is now statically proven: first match in order base, then DLC list. Only what a runtime measurement would show remains open. A contradiction in the game would imply this interpretation is false.
3. Implications
- The planned path (Loading Retail-Save c2v1, then
resourceExec wolfsdk_test.cfg) again predictscan't generate in productionbecausedlc_2/chunk_22is mounted. This is a cheap falsification test of this analysis. - Our container is mounted when
c2v1_customloads. Then the cfile is superfluous because one is already in the map. It only proves that a custom cfile in the game is resolvable. - Leverage without Retail write access (not tested): Rename the folder
dlc/dlc_4todlc/dlc_0. The dlcid 4 remains, and the Spec already listsc2v1. If the directory enumeration is alphabetical (the measured order 1–4 fits this, but it has not been proven), thendlc_0precedesdlc_2. Then for the Retail-Save, ourchunk_22will be mounted. It contains all 6985 c2v1 retail payloads byte-identically. Risks:- The same prefix goes to a virtual call at
0xac1a47..0xac1a8d. It is SUSPECTED that this is the DLC Sound Mount, anddlc_4has nosound/folder. - An entitlement check for DLC 2 for the Save cannot be ruled out.
- The same prefix goes to a virtual call at
Log evidence that the lever works: found dlc id 4 at ../dlc//dlc_0 appears before found dlc id 1.
4. Counter-controls (--negativ)
7/7, rc 0:
| Manipulation | Result |
|---|---|
zeilen_diff: one bit in +0x50, string_index, and compression |
all three detected |
zeilen_diff: a bit in the offset |
ignored, as intended |
Ciphertext byte and header byte 0x18 flipped (copy) |
probe_datahash 7127/7128 rc 1, probe_idclhash +0x08 0/1 rc 1 |
pruefe() on the same copy |
17/21: Payload, Decryption (HMAC-Tag incorrect), and both checks fail |
Limitation: The equality checks of the old entries were only tested against manipulation (zeilen_diff) in memory, not with a second damaged 150-MB copy.
Reproduction
python tools/build_dlc4_with_cfile.py # build + 21 checks (~2.5 min)
python tools/build_dlc4_with_cfile.py --pruefen # check only
python tools/build_dlc4_with_cfile.py --negativ # negative controls, copy in temp directory
python tools/build_dlc4_with_cfile.py --vorrang # Mount rule on module.bin, 35 checks