Repository navigation
Research Report Assetviewer
Status 2026-09-24, Worker w-assetviewer-a1. Continues an abandoned attempt: wolfsdk/tncview.py, with the changes to wolfsdk/browser.py and tools/verify_browser_tnc.py already in place; this report was missing. Verified, hardened three locations (see "Changed in this Pass"), and ran everything again against the actual installations.
Second Pass (same day): The seven findings from the auditors are fixed, see "Auditor Findings". Each finding first received a check in tools/verify_browser_tnc.py, which failed on the old code (20 ERRORS), and now runs successfully.
wolfsdk/gui.py is not touched: The GUI already passes a tncpatch.Installation to the browser for Part 2; that suffices.
After "Load", there are 29 Archives with 218,311 Entries in the list:
| Location | Archives | Entries |
|---|---|---|
base/ |
24 | 178,077 |
dlc/dlc_0/base/ (Test DLC, not retail) |
1 | 7,128 |
dlc/dlc_1/base/ |
1 | 13,366 |
dlc/dlc_2/base/ |
1 | 6,985 |
dlc/dlc_3/base/ |
1 | 12,754 |
dlc/dlc_5/base/ (Exec-Carrier, not retail) |
1 | 1 |
The columns remain unchanged. The archive appears in the info line; for DLCs with additions, e.g., Archive dlc_1/chunk_20.resources (DLC, read-only).
| Type (Count) | Text Tab | Image Tab | Export... | Save as Mod / Import |
|---|---|---|---|---|
image (37,910) |
Hex of the Payload | Preview ≤ 512 px; below that a line with size, format, Mip number, displayed Mip, source (TexDB/embedded), and hints |
.png = highest decodable Mip in full size (Status names the Mip); otherwise raw data |
Like Part 1, only via Import (raw data) |
cfile (68) |
decrypted; also unpacked if declen > 0; info line decrypted or decrypted and unpacked
|
– | Plaintext, or .raw = Payload as in Archive |
Text edit → re-encrypted under the same Resource Name before being read back |
compfile (171) |
unpacked; info line unpacked
|
– | Unpacked content or .raw
|
Text edit → Kraken repacked (like retail), read back |
| everything else (180,162) | Text if tncview.as_text preserves it losslessly, otherwise Hex; Path Tab for Decls like Part 1 |
– | Raw data | Text edit → byte-exact return (Decl query like Part 1); binary → "Text Only"; Import takes raw data |
New for "everything else": Kraken-compressed entries (Archive Mode 2 and 4) are now unpacked. Previously, selection reported only a read error for them.
Saving writes only to mods/<folder>/, and <folder> must be a simple name directly under mods/. A new mod gets "spiel": "tnc", an existing folder for Part 1 is rejected, and an existing, broken mod.json remains untouched. Saving and importing from a DLC archive are rejected with a reason because tncpatch only writes to base/.
Lazy loading: "Load" reads only the tables (0.6 s). Oodle is not loaded and no .texdb table is read until an entry is selected; this has been verified.
-
Images, Sample: 400 random
imageentries from all 29 archives (Seed 2026), via the same path as the tab (tncview.payload→tncview.present).- 379 with preview (94.75 %). 21 instead show a reason, and all 21 are light samples: 18 BC6H, 2 BIM v13, 1 BIM v12.
- Notes for the 379: 142 × "Tile chain skipped" (the preview falls to a smaller Mip), 21 × "TexDB without block" (fallback to the next Mip), 15 × "scaled down".
- Time per selection for reading and decoding, warm cache: Median 0.03 s, p90 0.08 s, Maximum 0.48 s.
-
In the real widget:
- BC7 2048² (
woodboard_02, Preview Mip 2 512²): 0.64 s - DLC texture
gunslinger_shirt(dlc_1): 0.17 s -
strings/english.lang(2,049,240 bytes plaintext): 0.09 s -
c2v1.entities(DLC, 8,329,537 characters): 0.17 s
- BC7 2048² (
- All 239 cfile/compfile (68 + 171) in all 29 archives run the path unpack → Text → Tk-Text → back and come out byte-for-byte identical. 8 of them are not valid UTF-8 and are shown as latin-1, 67 end with NUL. Tk rejects NUL, so the end is retained and appended again when saving. 0 are binary, 0 errors.
-
Other TNC entries that look like text (
looks_textual, all 29 archives without image/cfile/compfile): 49,086. Of these,as_textrejects 138 as binary; they no longer pass through the Text widget:- 93
destructionmodeldata, 28json, 12hknavmesh, 3staticShadowGeom: Binary formats containing text with NUL. Tk cuts off at the first NUL. An unchanged "Save as Mod" wrote a 0-byte file for the json entries (Finding 2). - 2
particlewith each one single control byte (\x01or\x06):zerstorer_backpack_thrusters_explosion,ubergewehr/projectile_sphere. These now only go through Import.
- 93
python -m wolfsdk gui
Select "Wolfenstein II" at the top, then open the Assets tab and press Load.
- Type
image, searchwoodboard_02→ Tab Image - Type
cfile, searchenglish.lang→ Tab Text; the info line says "decrypted and unpacked" - Type
compfile, searchc2v1.entities→ DLC entry, text starts withVersion 5 - Change text → Save as Mod → enter folder name → activate and apply in the Mods tab
python tools/verify_browser_tnc.py takes about 10 s with warm disk cache, up to 45 s cold; with --alle the run over all 239 cfile/compfile is added (+36 s). Exit 0 with "All good." (second run: 62 tests, with --alle 63, 0 ERRORS). The tool builds the real Browser with a hidden Tk window and clicks via tree selection.
-
TNC positive:
- The loader lists 24 + 5 archives and decodes nothing.
- BC7 and BC1 are converted into a single PhotoImage with neighbor correlation of 0.92 and 0.97, respectively, indicating image data rather than noise.
- DLC image (dlc_1): Correlation 0.96.
-
english.lang: Text begins with BOM +// string tableand is byte-identical to the decryption output. - Unprocessed storage → "Nothing changed."
- Processed → The mod file is encrypted (
ff ff ff ff…), opens under the same name as editor text, andmod.build_payloadscontains it. -
.cfgwith trailing NUL: Round-Trip successful. -
c2v1.entities:Version 5. - compfile processed → repacked, unpacked to editor text.
- Export: Plaintext,
.raw, and full-size PNG.
-
Negative controls:
- Stored payload under a different name → HMAC error.
- Intentional encryption under a false name → the browser rejects saving; nothing is written.
- BC6H and BIM v13 display a reason instead of crashing.
- 3-byte payload → "Image data corrupted (IndexError …)".
- Random pixels are recognized as noise.
- Tk truly rejects NUL.
- Saving from DLC rejected; nothing written.
-
Part 1 (TNO), old vs new: Identical clicks on the current
browser.pyand the one fromgit HEAD(loaded viagit show) leave identical widget states and identical files. Compared were info lines, text, paths, image pixels, status, "Save as Mod" (bytes frommod.jsonand Asset), and PNG bytes. Verified across 6 entries (3 images, weapon, damage, anim). -
Mutation samples (in a scratch copy, not in the repo):
- Removal of read-back check in
tncview.wrap→ The exact check "Negative: packaged under false name → saving rejected" fails. - Adding a space to the Part 1 info line → All 6 checks "Part 1 … identical with HEAD" fail.
- Removal of read-back check in
tools/verify_gui.py before/after:
- Before: Scratch mirror of the working state with
browser.pyfrom HEAD. - After: The repo.
Both end with Exit 0 and "All good.", and the outputs are character-identical (diff empty). Among other things, they contain: "All five work areas built", "Mods read in 19", "Title change loads another catalog 31 -> 2231 Cvars", "Mod list follows title 7 Mod(s) for tnc". Running the second pass again before and after corrections yields Exit 0 and character-identical output. config.json was backed up before and restored afterwards.
Each check is located in check_findings or at the end of check_tno. The "before" column shows the output on the code prior to correction.
| # | Finding | Issue | Previous Behavior | Correction |
|---|---|---|---|---|
| 1 | Importing to cfile/compfile blindly repacks everything that wasn't already unpacked |
.raw from english.lang to default_sp.cfg; custom packed .raw from english.lang without Oodle (simulated) |
Both FAILED: Mod written (Garbage or double-encrypted) |
tncview.for_import distinguishes: saved form exactly this entry → kept; readable text → gets repacked; everything else (foreign resource name, missing Oodle/AES, other binary data) → "Import failed" with reason |
| 2 | Unpacked TNC entries ran through looks_textual and the Text widget |
all 30 json in base/ saved unprocessed |
FAILED, 16 of them wrote a Mod (0 Bytes) | All TNC entries saved via _save_tnc: only what as_text accepts and Tk holds exactly unchanged → "Nothing changed.", binary → "Only text … Importing" |
| 3 | Folder name unchecked when attached to mods_dir
|
absolute path, ..\x, ../x, a/b, CON, nul.txt, vb.
|
all 7 FAILED, written outside of mods/ or mods\CON, mods\vb
|
only a simple name (no separators, no drive letter, no .., no dot/space at end, no device name); additionally Path.resolve(): target must lie directly under mods/
|
| 4 | Filename without type, two assets shared a file | material and skins models/animals/bessie/bessie into one Mod |
FAILED, both keys → assets/models_animals_bessie_bessie
|
The first file keeps its old name. If another asset of the same mod uses the file already, it gets ~2, ~3 … (Comparison like Windows: case insensitive). The same key takes its file back on re-save. Existing mods remain valid. |
| 5 | Traceback in dialog | broken JSON, JSON list, assets as list, filename > 255 |
4 × FAILED with "Traceback (most recent call last)" |
mod.json is read before every write. Broken → "mod.json not readable", it remains unchanged, nothing is written. Write errors → "Save failed" with reason and file, for > 255 characters with exactly this hint. |
| 6 | OSError/MemoryError from Oodle compressor landed in Tk |
Oodle.compress raises OSError or MemoryError, during save and during import |
3 × FAILED "… escapes" | both paths catch TncCryptError, OSError, MemoryError → dialog "… Nothing saved."; the packing happens before the first write attempt |
| 7 | "Load" also reset selection for Part 1 | TNO: Selection, Load, then Export and Import against HEAD | FAILED "Export False, Import 0/2 files" | Selection is only discarded on game switch, then with preview (own check "Game switch on load"). Within a game it remains as in HEAD. |
Additionally new, as protection against regression: Import of own .raw remains byte-identical; imported plaintext is encrypted under default_sp.cfg; a TNC-Decl (projectile_ent/bottle_throw) is recognized and processed unchanged byte-for-byte saved; no message of the whole run contains "Traceback".
Behavioral changes in Part 1 (TNO) – only in _write_mod, which both parts use (Findings 3–5), everything else like HEAD:
- A folder name that is not a simple name is rejected. HEAD wrote there, also outside of
mods/. - If another asset of the same mod already uses the flat filename, the new file gets
…~2. HEAD silently overwrote the file. Without such collisions, files andmod.jsonare byte-identical to HEAD, which is verified. - An existing
mod.jsonthat is not a JSON object with anassetsobject is reported before anything is written. HEAD wrote the asset first and then showed a Traceback. - Write errors show a German message with reason and file instead of a Traceback.
Not changed, although related (Part 1 should remain like HEAD):
-
Part 1 continues with
looks_textual. In TNO, 60,411 non-image entries are considered textual; 15 of them (e.g.,model, such asmaps/game/wolf/c01/c01p1/_combo/_world.bmodel) contain a NUL byte in the middle of their content. Tk truncates there. Saving "As Mod" on top of that would, like finding 2, only write the beginning. -
Placing a TNO asset into a TNC mod folder is assumed as in HEAD. Only Part 2 checks
spiel.
-
tncview.wrapreads every newly wrapped cfile/compfile back withunwrapand outputs it only if exactly the editor text comes out. The reason: The moduleassetcryptis considered untrustworthy in finding 2 (7/8). Therefore, an error there cannot create a mod that the game does not open. Import runs via the same path. - Non-
TncImageErrors fromtncimagenow read as "Image data corrupted (Type: …)" instead of just with the bare Python type. - PNG Export: The status line names the exported Mip. A tile chain can push the export below the original size, and that should be visible.
- Light probes without image: BC6H as well as BIM v10/v12/v13 show a reason. In the sample set, these were 5.25% of all images.
- Tile chains (Codec 2, those ≥ 512-px Mips of most BC4/BC5 Maps) are not decoded. Preview and PNG Export take the next Mip; a 2048 Normal Map comes out as 256² in this way.
-
"TexDB without Block": There are textures whose large Mips have no
.texdb. They fall back to a smaller Mip, in the extreme case 32², and that is displayed. -
No PNG Import: Importing takes raw data (
.bimage), as in Part 1. - "Export all hits" writes the payload behind the archive compression, i.e., cfile/compfile in their stored form, not decrypted. This fits what importing expects.
-
Not tested in game (the game was never started):
- A saved cfile is always unpacked (
declen = 0xFFFFFFFF). Thejlein the reader after that is allowed; loading a file like this the game has not done yet. - If a payload does not fit into its slot,
tncpatchpacks the entry at archive level with Kraken (compression 0 → 2).
- A saved cfile is always unpacked (
- DLC only read: Assets that lie only in a DLC cannot be modded via the tab.
-
Windows only:
tnccryptusesbcrypt.dlland finds Oodle viaGame.find(title='tnc'). If that fails, it shows the info line "not readable: …" and the text tab Hex; it does not crash. - Corrupted compfiles do not fall back: Kraken frames have no CRC. Retail does not affect this, but a broken user DLC can show nonsense as text.
-
Foreign exceptions from
tncimage:-
IndexErroratb"BIM" -
ValueErrorat a.texdboffset behind the file end -
KeyErrorindecodeat an unknown format
-
tncview.present catches all of them. The actual correction belongs in tncimage.py. The first two I could fix in the second pass; it is not needed because present already catches them. Therefore I did not touch tncimage.py.
- Text that Tk does not losslessly hold blocks saving. In the 239 cfile/compfiles, this never happened.
- Binary entries and text with control characters (see "Numbers", 138 entries) can only be replaced via importing.
- Import on cfile/compfile takes only the stored form of exactly this entry or text without control characters. The unpacked content of a binary cfile/compfile would not work; retail has none.
-
Asset names whose flat filename is longer than 255 characters (338
animEventin TNC) cannot be saved into a mod. A message comes that says so. -
No atomic writing: If writing fails halfway through a file (disk full), the asset file can be half-written.
mod.jsonis written last. The behavior is the same as in HEAD.
-
tools/verify_gui.pywritesconfig.json. The title change test calls_resolve_game→Game.remember()→save_config; only thecheck_playstub is executed. After execution,game_pathpointed to TNO instead of TNC. I restoredconfig.jsonfrom HEAD; that was the state at session start. The correction belongs to the owner ofverify_gui.py. Until then, secureconfig.jsonbefore execution. -
The installation now has
dlc_0anddlc_5. Both are test DLCs from other agents;dlc_4no longer exists. Both are also listed as read-only. The check requires only thatdlc_1..3be present. -
tools/probe_tno_tex.pycontains NUL bytes. The file originates from another, aborted workflow and has not been touched. Thecompileallinverify_allfails on it, independently ofbrowser.py.python -m compileall -q wolfsdk tools/verify_browser_tnc.pyruns cleanly.
-
wolfsdk/tncview.py(new):-
dlc_archives,read_only,where– Archives -
payload– Archive compression including mode 4, ported fromtools/extract_tnc.py -
unwrap/wrap/for_import– the second wrapper -
as_text/from_text– text without loss -
present– everything for a selection
-
-
wolfsdk/browser.py: Part-2 branch in Load, Select, Export, All-export, Import, and Save-as-mod. The Part-1 code runs unchanged, except the shared_write_mod(Findings 3–5, see "Behavioral Changes in Part 1"). -
tools/verify_browser_tnc.py(new; in the second pass,check_findingsand checks after reload were added)
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