Repository navigation
File Formats
All of this information comes from the reverse engineering of the retail files for Wolfenstein: The New Order (Steam, WolfNewOrder_x64.exe, 26,345,040 bytes). Every statement has been verified against all 14 archives and 203,843 assets unless explicitly marked as unverified.
The root: a list of archive pairs.
u8[4] Magic 03 53 45 52 ("\x03SER")
BE u32 Number of pairs (Retail: 14)
repeated 2 x Number:
LE u32 Length
char[] Name, ASCII, null-terminated
The names alternate: chunk0.index, chunk0.resources, chunk1.index, … The file is 520 bytes in the retail version, which fits exactly: 8 bytes header + 10 x 36 + 4 x 38.
Mixed Endianness. The count is in Big Endian, while string lengths are in Little Endian. This is not a reading error; it applies consistently across all
.indexfiles.
Table of contents for chunkN.resources.
u8[4] Magic 03 53 45 52
BE u32 Size of payload data (file size minus 32)
u8[24] Null bytes
BE u32 Counter A (chunk0: 12220)
BE u32 Counter B (chunk0: 15069, approximately equal to entry count)
BE u32 0
--- entries begin at offset 0x2C ---
An entry:
LE u32 + char[] Type e.g. "weapon", "image", "material"
LE u32 + char[] Name e.g. "weapon/shotgun_base"
LE u32 + char[] Path e.g. "generated/decls/weapon/weapon/shotgun_base.decl"
BE u32 Offset into the associated .resources file
BE u32 usize Uncompressed size
BE u32 csize Size on disk
u8[?] Rest variable, mostly null bytes
The trailing data at the end of an entry is not fully understood. For most types, it is 13 bytes (9 null bytes plus a sequential BE-u32 ID); for audio types like sample and sound, it is larger. Precisely because of this, this SDK serializes the index never: it keeps the original buffer and overwrites only the 12 bytes of offset/usize/csize during patching. All unknown data survives unchanged, verified by the fact that an unmodified index is written byte-for-byte back.
To determine entry boundaries, the parser uses a plausibility check rather than fixed step size: three printable ASCII strings, followed by 0 < csize <= usize and offset + csize <= file_size. This maintains synchronization across the variable trailing data.
There is no checksum field. This is why patching in-place is safe and undetectable by the game.
Raw data blob, addressed via the index.
u8[4] Magic 03 53 45 52
u8[12] Null bytes
--- payload follows at offset 16, tightly packed ---
Per entry:
-
csize == usize→ the bytes are stored uncompressed. - otherwise → raw DEFLATE (
zlib.decompressobj(-15), no zlib header).
The game itself stores verbatim whenever compression yields nothing; this SDK does the same.
New data is written into the slot already occupied by the entry. Offset and csize remain unchanged; only usize moves. If the new deflate stream is shorter than csize, the input is padded with a Decl comment until the stream is exactly csize bytes long (resources.deflate_exact()); raw entries (csize == usize) accept only an exact replacement of equal length.
Appending is incorrect. An earlier version of the patcher appended new data to the end of the file and moved the index there. This passed every byte test but still caused the game to crash on startup:
FATAL ERROR: idBackgroundLoader::BeginBackgroundLoads: FileTable is out of order. The engine requires ascending offsets. Similarly,csizemust not shrink — otherwise subsequent entries shift (No address, error: 487). Both are measured; see limits.md §4.
If a replacement does not fit into its slot, the entire archive is rewritten in original order (Archive.rebuild()). Only then do offsets change — and they all change together, preserving the order.
Reversion therefore occurs in three stages, in this order (patch.revert()):
- Replay fully copied
.resourcesfiles frombase/_wolfsdk_backup/— and delete the copy afterward. - Rewrite individually overwritten slots from the Journal (the original bytes are base64-encoded in
journal.json). - Restore the backed-up
.indexfiles.
A .resources file is never truncated: its length does not change due to patching at all.
Text format, available in two dialects that coexist within the same file tree:
// weapon, entityDef, damage, inventoryItem
{
edit = {
maxDamage = 25;
validAmmoClips = { num = 1; item[0] = { clipSize = 20; } }
}
}
// material, md6Def, skins
{
diffusemap "models/weapons/Handgun/Handgun"
virtualmapping { 0.015625, 0.015625, 0.078125, 0.031250 }
offset ( 0 0 0 )
prop "_info" { tag "muzzle" { trans ( 0 0 0 ) } }
}
Features covered by the parser:
-
=and;are optional - Values: String, Word, Block
{…}, Tuple(…) - Comments
//and/* */ - Qualified blocks:
prop "_info" { … }→ pathprop:_info - Indexed fields:
item[0]remains asitem[0]part of the path -
inherit = "…"refers to a base Decl (inheritance)
Paths are dot-separated:
edit.damageParms.maxDamage
edit.validAmmoClips.item[0].clipSize
props.prop:_info.tag:muzzle.trans
Duplicate keys on the same level receive a suffix #1, #2, … starting from the second occurrence.
python -m wolfsdk paths <type:name> lists all paths of a Decl.
The editor remembers the byte span for each value and replaces only this excerpt when writing. Nothing is reformatted or re-serialized. Verified against 60,233 Decls: parsing and unchanged reversion yields exactly the input.
Counting Basis. 60,233 are index entries in text types, counting multiple occurrences of the same asset in several chunks. When reduced to unique
(Type, Name)pairs, there are 25,887, of which 25,559 are valid UTF-8 and parse correctly; the 328 outliers are 327 embedded binary files underfile:and onerenderProgwith non-UTF-8 bytes (TNO-Modding-Surface.mdheader). Both numbers match; they count different things — "0 parse errors" and "328 outliers" are not contradictory.
LE u32 Hash / ID (Purpose unconfirmed)
u8[4] Magic 09 4D 49 42 ("BIM" + Version 9, byte-swapped)
BE u32 x 13 Header fields
--- then per Mip level: ---
BE u32 Width
BE u32 Height
BE u32 Byte count
u8[] Data
Known header fields (index starting at 0 after Magic):
| # | Meaning |
|---|---|
| 0 | Texture type (0 = 2D, 2 = Cubemap face) |
| 1 | Width |
| 2 | Height |
| 3 | Depth |
| 4 | Mip count |
| 6 | Format code |
| 11, 12 | Base width, base height |
Format codes, derived from bytes per pixel across all 2,566 images — each one matches exactly:
| Code | Format | Bytes |
|---|---|---|
| 3 | RGBA8 | 4 / Pixel |
| 5 | A8 / Distance Field | 1 / Pixel |
| 10 | BC1 / DXT1 | 8 / 4x4 block |
| 11 | BC3 / DXT5 | 16 / 4x4 block |
python -m wolfsdk image-info <name> provides all this information for an image.
wolfsdk/bim.py models the header, wolfsdk/image.py the pixels — in both directions:
| Direction | Function | Status |
|---|---|---|
| BIM → RGBA |
decode(), decode_preview()
|
all four format codes, measured against 2,566 images |
| RGBA → PNG | to_png() |
Standard library, zlib
|
| PNG → RGBA | from_png() |
including filter removal |
| RGBA → BIM |
to_bim(), png_to_bim()
|
only format code 3 (RGBA8), header taken from the replaced image |
About the command line: wolfsdk image-export and wolfsdk image-import.
Two limitations remain: There is no BC1/BC3 compressor, so a substitute is typically larger than the original and forces an archive rebuild; and whether a substituted image looks correct in-game is not verified — it is only documented that the engine successfully loaded four self-built BIMs without error messages (limits.md §4). For weapon and character surfaces, this is not the lever: they reside in the Megatexture, see limits.md §5.
streamed.resources and english.streamed are concatenated Ogg Vorbis streams (OggS-Magic immediately at the file start). Both containers are not in master.index.
The .index entries for types sample, sound, and voicetrack contain no audio data themselves — they are descriptors pointing to it: 20 bytes after the offset/usize/csize triple stands as a BE u32 representing the byte offset into the container. Verified across all 61,318 sample entries by traversing the Ogg page chain up to the EOS page and comparing against the length in the descriptor: 0 deviations. The slots lie on a 16384-byte grid, non-overlapping.
Anyone holding sample for the audio bytes is searching in the wrong container — and overlooking that streamed.resources and english.streamed are not in the backup. Details in audio.md §3 and §6.
virtualtextures/*.pages, 39 files, totaling 35.6 GB (33.2 GiB; measured on 21.09.2026). Materials with usevirtualmapping 1 derive their pixels from there, not from image assets. The field virtualmapping { u, v, du, dv } in the material represents the coordinates within the pages.
The container is decrypted and verified — file header (32 bytes, all seven fields), offset scale, set header (40 bytes), page payload, quadtree derivation, self-test over seven fully traversed files with no unassigned bytes. The complete description is in virtualtextures.md.
The only unresolved issue is the pixel data (a cropped JPEG-XR stream, TNO-Virtual-Textures.md §8). Therefore, it remains: Virtual texture pixels are not replaceable.
The engine accepts Cvar assignments directly on the command line:
WolfNewOrder_x64.exe +g_fov 105 +bind F1 "toggle g_permaGodMode 0 1"
This confirms that the game-specific base/build.cfg uses exactly this syntax:
+feature_hq 1 +feature_lowgore 0 +sys_region "ww" +g_EnableGore 1 ...
The Retail EXE contains around 2,000 Cvar names, including preserved developer cvars (g_permaGodMode, g_permaInfiniteAmmo, com_allowConsole, devgui) and the commands teleport and devmap. noclip, god, notarget, give and spawn do not exist in The New Order (the earlier string-based list was wrong); the registry has 3,194 cvars, see TNO-CVar-Catalog.
The strings lie in the .rdata pool roughly as Description \0 Default \0 Name \0, but the linker frequently deduplicates common values like "0" and "1", making it impossible to reliably assign Defaults. The names, however, are fine. There is no static registration table — MSVC passes the pointers RIP-relative in the code.
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