Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / Research reportsForschungsberichte

packagechunklist.json in TNC-Retail: Clear Negative

SolvedGelöst Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

This report checks whether the file packagechunklist.json can make Wolfenstein II (TNC) load extra archive chunks early, before the main menu. The game does read the file, but only into a streaming-install table that is inactive on PC. No code loads a chunk because of it, so this route for mods is closed.

  • The file is read once at startup, before any archive is mounted.
  • The install manager's active switch is never turned on in the PC version, and its install check always answers yes.
  • 10 of 10 code tests pass.
  • A negative chunk number in the file could corrupt memory or crash the game.
  • An optional in-game counter-check is described, but not needed.

Der Bericht prüft, ob die Datei packagechunklist.json Wolfenstein II (TNC) dazu bringen kann, zusätzliche Archiv-Chunks früh zu laden, schon vor dem Hauptmenü. Das Spiel liest die Datei zwar, aber nur in eine Streaming-Installations-Tabelle, die auf dem PC inaktiv ist. Kein Code lädt deswegen einen Chunk, dieser Weg für Mods ist also zu.

  • Die Datei wird einmal beim Start gelesen, bevor irgendein Archiv eingebunden ist.
  • Der Aktiv-Schalter des Installationsmanagers wird in der PC-Version nie eingeschaltet, und seine Installationsprüfung antwortet immer mit ja.
  • 10 von 10 Code-Tests bestehen.
  • Eine negative Chunk-Nummer in der Datei könnte den Speicher beschädigen oder das Spiel abstürzen lassen.
  • Eine optionale Gegenprobe im Spiel ist beschrieben, aber nicht nötig.

Technical detailsTechnische Details

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

Short Answer: Retail reads the file correctly; therefore, it is not merely a dev path. However, its contents end up only in the idStreamingInstallManager table. This table holds console metadata for streaming install, following the pattern chunkid → dependent packagechunks. On PC, this manager sleeps: its active flag is never set anywhere in the code, and IsChunkInstalled always returns true. No code mounts a chunk because of this file. dlc_5 cannot be mounted via it before the main menu. Nothing installs.

Tools: python tools/probe_chunklist.py yields 10 PASS results and "ALL TESTS OK". Details are in re_probes/chunklist/result.json. The installed EXE has the same SHA-256 as runtime_87400/manifest.json (68e6c21a…18c3). Thus, this analysis applies to the installed game.

Findings (RVA in module.bin)

# Finding Evidence
1 The string appears only once (0x26e63a8). There is exactly one Xref, 0xb763aa, in the reader 0xb76330. c_string_and_xref
2 Call chain: idCommonLocal::Init (0xa638b0) → Log "time 20" → fileSystem->Init (vtable+8) → 0xb765f0 (call at 0xa64a2e) → "Executing build.cfg". This occurs before SearchDLC and before idResourceManagerLocal::Init2. At this point, no container has been mounted yet. c_call_chain_and_time, Log Z. 3–23
3 Switch: 0xb765f0 reads only if sys_installToHDD ≠ 0 (Default "1", Help text "if 1, allow streaming install to HDD"). Live value: 1. c_gate
4 Location: ReadJsonFile (0x1217d10) calls fileSystem vtable+0xd0. This is idFileSystemLocal::ReadFile (0x1248630), called with the naked name packagechunklist.json, without a path prefix. For comparison: packagemapspec.json receives a DLC prefix at 0xac5910. The name is resolved against the search path from the log: 1. %USERPROFILE%/Saved Games/MachineGames/Wolfenstein II The New Colossus/base/, 2. <Steam library>/steamapps/common/Wolfenstein.II.The.New.Colossus/base/. The order comes from the log. I did not trace OpenFileReadFlags (0x124d880). c_readjson_is_filesystem, c_log
5 The warning appears twice, but the reader runs only once. The second line merely repeats the first in the block "Warnings during … initialization" ("1 warnings"). c_log

Schema (from the reader 0xb76330)

{"chunks": [{"chunkid": <int>, "packagechunks": [<int>, ...]}]}
  • chunkid is a direct index into an idList with 0x48 bytes per entry. Offset +0 is a bool "valid" (set to 1), Offset +8 is an idList<int> containing the packagechunks. Other keys are ignored.
  • If the file is missing, the table remains empty (live: ptr 0, num 0). There is no built-in default list that could replace the file.
  • chunkid is only checked upwards (cmp/jl against num). A sign check is missing (js/jns do not appear). Therefore, a negative chunkid writes before the table start, potentially corrupting the heap or causing a crash. A huge chunkid triggers a huge allocation.

What Happens to the Content

After reading, 0xb765f0 sets byte 0x2f5d371 to 1. No one reads this byte. Additionally, the function allocates a 1-byte object (0x2f5d378) that is never used except for atexit cleanup. Then it collects all packagechunks without duplicates into a list and immediately frees them again, without any intervening call. The platform hook is stripped from the PC build.

I scanned all RIP-relative accesses to 0x2f5d360..0x2f5d3a0 and decoded the hits linearly with correct width. The table is used thereafter only by these functions:

Function Role Effect
0xb767e0 IsChunkInstalled Predicate Always returns 1. The table serves only for the "invalid chunk" warning, and only when the Manager is active.
0xb76820 / 0xb76cd0 PrioritizeChunk(s) Change order, printf Called only when the Manager is active
0xabf6a0 GetIsChunkForMapInstalled Predicate during map loading Table used only for the "invalid chunk" warning when the Manager is active; result comes from IsChunkInstalled (= 1)
0x1e2c3f0 atexit Free memory
  • Active flag 0x2f5d370: 4 read accesses, all cmp, and 0 writers. Even a wider write access from the neighbor CVar sys_debugGameInstall (up to 0x2f5d36f) does not reach into the flag. The only place holding its address is a reloc'ted .rdata pointer at 0x20abf80, and no code reads it. Live value: 0.
  • All direct calls from these functions go only to JSON-, list-, heap-, and log helpers, or to the Manager functions themselves. Indirect calls are only Free via the allocator 0x4124cc0 and the destructor of the JSON value. No call leads into the Resource Manager or to mounting.

Why this cannot damage Retail Chunks (in case someone uploads the file)

A file with valid, non-negative IDs fills only the sleeping table. No standard list is replaced, no container is touched. Therefore, Retail Chunks do not need to be listed. The only danger is a negative or absurdly large chunkid (see above).

Implication for the Master

The path via packagechunklist.json is closed. DLC chunks continue to be mounted only when loading a map from the DLC (see dlc_primary.md, map_selection.md). For resourceExec in the main menu, the carrier must reside in a container that is already mounted there. I have not investigated this here; see docs/patchvorrang.md.

Optional runtime counter-check (not necessary, refutes this report if it yields differently)

File: re_probes/chunklist/optional_gegenprobe/packagechunklist.json = {"chunks":[{"chunkid":26,"packagechunks":[26]}]}. These are valid IDs, so harmless according to the code. Expectation:

  1. The line ReadJsonFile: could not read file: packagechunklist.json is missing. This confirms the found location.
  2. resourceExec wolfsdk_test.cfg in the main menu continues to fail with can't generate in production. This confirms that nothing is mounted.

If 1 does not occur, the found location is incorrect. If 2 works, the analysis is incorrect.

Tests (each FAILs if the statement is false)

python tools/probe_chunklist.py:

  • c_exe_matches: installed EXE = Snapshot
  • c_string_and_xref: String exists only once, single xref in 0xb76330
  • c_schema: Keys in order chunks/chunkid/packagechunks, Name goes directly to ReadJsonFile, entry 0x48, no sign check
  • c_readjson_is_filesystem: vtable+0xd0 = idFileSystemLocal::ReadFile
  • c_call_chain_and_time: exactly one caller, between FS-init and build.cfg
  • c_gate: Switch sys_installToHDD, live 1
  • c_consumers: complete list of references with positive control, no writer on the active flag, live 0
  • c_is_installed_constant: every return path runs via mov al, 1
  • c_no_mount_calls: List of allowed callees
  • c_log: 1 read access plus 1 repetition in the summary, errors from resourceExec (Log copy re_probes/chunklist/condump_wolfsdk_test3.txt)

Mutation test (manual): Setting ACTIVE to 0x2f5d371, IsChunkInstalled to another function, GATE incorrect, or allocator incorrect will each fail the corresponding test.