Wolfenstein Modloader Buy me a coffeeKaffee spendieren

In plain wordsIn einfachen Worten

This is the assignment given to a research worker, not a result. A carefully edited save game for Wolfenstein II: The New Colossus was still rejected as 'File Corrupt', while its 40-byte .verify files were left unchanged. The worker must read the decoded game code to find what writes a .verify, with which algorithm and key, and whether loading actually checks it. The most important question is whether .verify or the save structure is the real cause of the rejection.

  • Three questions: what writes a .verify, does loading check it, and is it the reason for 'File Corrupt'?
  • Each .verify is 40 bytes: 8 bytes that look like a random value plus 32 more bytes.
  • Proof required: recreate an existing .verify byte for byte for at least three files.
  • Negative control: the recreation must fail on a deliberately altered file.
  • If the key depends on runtime or hardware values, the worker must say so plainly.

Dies ist der Auftrag an einen Forschungs-Worker, kein Ergebnis. Ein sorgfältig bearbeiteter Spielstand für Wolfenstein II: The New Colossus wurde trotzdem als 'File Corrupt' abgelehnt, während seine 40-Byte-.verify-Dateien unverändert blieben. Der Worker soll im entschlüsselten Programmcode finden, was eine .verify schreibt, mit welchem Verfahren und Schlüssel, und ob das Laden sie wirklich prüft. Die wichtigste Frage ist, ob .verify oder die Struktur des Spielstands die echte Ursache der Ablehnung ist.

  • Drei Fragen: Was schreibt eine .verify, prüft das Laden sie, und ist sie der Grund für 'File Corrupt'?
  • Jede .verify ist 40 Byte groß: 8 Byte, die wie ein Zufallswert aussehen, plus 32 weitere Bytes.
  • Verlangter Beweis: eine vorhandene .verify für mindestens drei Dateien byte-genau nachbauen.
  • Gegenprobe: Der Nachbau muss bei einer absichtlich veränderten Datei scheitern.
  • Hängt der Schlüssel von Laufzeit- oder Hardwarewerten ab, soll der Worker das klar sagen.

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-saveverify-a1; task saveverify; research (read-only RE). Read .agent-cache/WORKER.md. Bus: python "%USERPROFILE%/.claude/skills/agent-cache-orchestrator/scripts/acbus.py" Parallel workers: astra-save-v3 works the STRUCTURAL side of the same problem (save serialization, length prefixes, dictionary checksum) on another host. You work the CRYPTO/CODE side only. Do not touch its files. Its findings and yours must meet in the middle; publish facts rather than duplicating its work.

Objective

These are the user's own single-player save files on their own machine. The question is a file-format one: how is a .verify sidecar produced, and does it actually decide whether a save loads?

Measured and unexplained: a candidate save whose three data files were rewritten consistently (map path in checkpoint.dat incl. its length prefix, checkpoint_mapstart.dat, game.details, all DAT MD5-fold headers recomputed) is still rejected at runtime as "File Corrupt", with all six .verify files left at their retail values. Each .verify is exactly 40 bytes: 8 bytes that look like a per-write nonce followed by 32 bytes of ciphertext.

Answer, from the code in the runtime image:

  1. What writes a .verify, over what input, with which algorithm and key/derivation?
  2. Does the LOAD path verify it? If yes, at which point and what happens on mismatch - is "File Corrupt" that path, or a different one?
  3. Therefore: is .verify the reason the candidate is rejected, or is the structural side (astra-save-v3's lane) the real cause? This is the single most valuable thing you can establish, in either direction.

Sources / starting points

  • re_probes/runtime_87400/module.bin - read-only capture of the RUNNING, SteamStub-unpacked NewColossus_x64vk.exe. Layout "virtual RVA": file offset == RVA. Sections and image base in re_probes/runtime_87400/manifest.json. Decisive: the on-disk EXE is packed, this image is not.
  • .pdata is a RUNTIME_FUNCTION table - use it for function boundaries instead of sweeping 31 MB of .text.
  • Entry points that tend to work: the literal string ".verify" and the save file names ("checkpoint.dat", "game.details", "checkpoint_mapstart.dat") in .rdata, then xrefs into .text. From the writer, the algorithm is usually two or three calls away. Modern x86 AES shows up as AESENC/AESENCLAST/AESKEYGENASSIST instructions - scanning .text for those opcodes localizes the crypto fast, and their call sites lead to the key schedule.
  • capstone 5.0.7 vendored: PYTHONPATH=re_probes/python_deps python ...
  • Corpus, READ ONLY: re_probes/save_backups/SLOT0_before_c2custom and SLOT2_before_c2custom (14 files each, originals), the quarantined set re_probes/verify_quarantine_SLOT0_FC2, and the live tree under "%USERPROFILE%/Saved Games/MachineGames/Wolfenstein II The New Colossus" - READ ONLY, never write there.
  • Prior work, read-only: re_probes/agent_reports/custom_save_registration.md, docs/spielstaende.md, tools/savehash.py (the DAT MD5-fold header).

Ownership

tools/probe_saveverify.py (new), re_probes/saveverify/ (new dir), re_probes/agent_reports/saveverify.md (new).

Forbidden / do not touch

Never write to "%USERPROFILE%/Saved Games" or the Steam install. No game launch, no process attach. Do not touch files owned by others: tools/build_custom_save_candidate.py, tools/probe_details_checksum.py, tools/verify_custom_save_candidate.py, re_probes/custom_save_candidates/, tools/probe_cmdtable.py, re_probes/cmdtable/, tools/probe_idclhash.py, re_probes/idclhash/, tools/vtpixel.py, docs/, wolfsdk/, config.json. No pip install.

Hard constraints

Python 3.11, stdlib plus vendored capstone. Reproducible from the command line. No running game required.

Acceptance criteria

  • The writer and, if it exists, the reader identified by function RVA with the xref chain that leads there and the disassembly excerpt that shows the input.
  • A clear verdict on question 3 with the evidence behind it.
  • If the derivation is recovered: reproduce an EXISTING retail .verify byte-exactly from its data file. That is the only acceptable proof. Do it for at least three different files across both backup slots. Anything less is a hypothesis and must be labelled as one.
  • Negative control: your reproduction must FAIL on a deliberately altered data file. A routine that emits the expected 40 bytes regardless of input proves nothing.
  • If the key is not statically recoverable (hardware-bound, Steam-derived, or generated at runtime), say so plainly and name what it depends on.

Verification expectations

Every important assurance needs a check that fails if the assurance is false. "An AES string exists in the binary" is not evidence of anything - the prior report already warns against exactly that inference. Distinguish: crypto code present / called on save write / verified on save load / rejection observable as "File Corrupt".

Failure honesty

A well-evidenced "the load path never reads .verify, so the structural side is the real cause" is an EXCELLENT result - it would redirect the other worker immediately. So is "key is runtime-derived, offline forging is impossible". Report failed hypotheses. Do not pad a negative into a maybe.

Coordination / deliverable

Checkpoint at milestones. Publish a bus fact the moment question 3 has an answer - astra-save-v3 is blocked behind exactly this. Concise German report in re_probes/agent_reports/saveverify.md. Finish the bus task when done.