Repository navigation
Streamed Audio Containers
These are the two files that have been on the blacklist since the project began. They are now read — exclusively with "rb", without a single write operation in the game folder. Everything here is measured, not explored; where something remains uncertain, it is marked as conjecture.
All numbers come from tools/streamed.py (Standard Library, mmap).
Verifiable via:
py -3.11 tools/streamed.py inventory
py -3.11 tools/streamed.py walk <file> --crc all
py -3.11 tools/streamed.py verify
py -3.11 tools/streamed.py prove "music/" <output_folder>
py -3.11 tools/streamed.py control --scratch <output_folder>
py -3.11 tools/streamed.py --selftest
They are Ogg Vorbis containers. Both, complete, with no remainder.
| Part 1 (TNO) | Part 2 (TNC) | |
|---|---|---|
*.streamed / streamed.resources
|
2 files, 1,572,314,960 B | none |
| Content | 23,565 Ogg Vorbis streams | — (Wwise .pack, 2.88 GB) |
| Playtime | 24.6 hours of audio | — |
| Coverage by the format described here | 100.000% of streams, 100.000% of page bytes | — |
| Unexplained bytes | 0 | — |
For mods, this means: the audio bytes of Part 1 are addressable, exactly length-defined, and exportable. Replacement is possible but dangerous — §7.
A recursive search was performed across both installations for *.streamed, streamed.resources, and anything carrying the name streamed.
| Game | File | Bytes | GiB |
|---|---|---|---|
| TNO | base\english.streamed |
415,137,488 | 0.387 |
| TNO | base\streamed.resources |
1,157,177,472 | 1.078 |
| TNC | — | 0 | — |
Part 2 has not a single streamed file. The audio lies there in base\sound\soundbanks\pc\ as Wwise packages:
sound.pack 1,722,766,110 B
english(us).pack 758,900,091 B
patch_3_sound.pack 193,042,687 B
patch_1_sound.pack 110,345,075 B
patch_2_sound.pack 47,419,238 B
patch_1/2/3_english(us).pack 47,619,261 B combined
patch_4_sound.pack / patch_4_english(us).pack each 132 B
wwise_project.bin 4,493,695 B
prodspec.json 163,237 B
------------------
Sum 2,884,749,658 B (2.69 GiB)
This is a different world (id Tech 6 + Wwise) and was not the subject of this investigation; docs/audio.md §0 had already correctly placed it.
Per language: in this installation there is exactly one language file, english.streamed. The bsnf descriptors in the archive lead to four languages (english, french, italian, spanish), but no french.streamed or similar exists. Conjecture: the other languages are separate Steam Depots and simply not installed. Not verified — this would require changing the game language in Steam, which was outside the scope of this task.
Neither wolfsdk/resources.py (Magic 03 53 45 52) nor wolfsdk/idcl.py (Magic IDCL) applies. The files have no file header at all — Byte 0 is already the start of the first Ogg page (4F 67 67 53 = OggS). There is no table, no directory, no count, no checksum at the container level. The directory lies outside, in the chunkN.index.
The container is a sequence of blocks. Each block begins on a multiple of 16384 (16 KiB) and is padded with null bytes up to the next 16-KiB boundary.
| Blocks | free space per block: min | median | max | |
|---|---|---|---|---|
streamed.resources |
14,603 | 0 | 8,544 | 16,382 |
english.streamed |
5,091 | 1 | 8,041 | 16,380 |
A block contains either one Ogg stream or a continuous chain of two to 140 Ogg streams (this is the samplepack, §2.4). The free space never exceeds a grid step — this serves as proof that the grid is truly 16384 and not a multiple thereof.
docs/audio.md§3 states "max 3,144,537 B of free space per slot". This was an artifact of the slot-based calculation: it measured the distance to the next referencedsampleoffset and skipped over the pack blocks in between. Measured block-wise, it is never more than 16,382 B.
Regular Ogg per specification, nothing changed, nothing truncated:
streamed.resources |
english.streamed |
|
|---|---|---|
| Ogg Pages | 267,219 | 94,933 |
| Ogg Packages | 4,213,830 | 1,780,066 |
| Logical Streams | 18,474 | 5,091 |
| Page Bytes | 1,034,059,500 (89.360 %) | 373,618,721 (89.999 %) |
| Padding | 123,117,972 (10.640 %) | 41,518,767 (10.001 %) |
| Padding Pieces | 14,599 | 5,090 |
| Padding that is not pure zeros | 0 | 0 |
| Page Sequence Number Breaks | 0 | 0 |
| Balance Pages + Padding − File Size | 0 | 0 |
The Vorbis identification header of each stream, read:
| Channels | Rate | br_nominal | blocksize0/1 | Streams |
|---|---|---|---|---|
| 2 | 48000 | 128000 | 256 / 2048 | 13,721 |
| 1 | 48000 | 128000 | 256 / 2048 | 3,948 |
| 1 | 48000 | 96000 | 256 / 2048 | 4,692 + 704 |
| 2 | 48000 | 96000 | 256 / 2048 | 399 + 101 |
Comment header, identical in all 23,565 streams:
Xiph.Org libVorbis I 20120203 (Omnipresent)
This is libVorbis 1.3.3 (February 2012) — fitting for a game from 2014 and an indication that the streams were generated with the reference encoder, not with middleware. User comment fields (Title, Artist) do not exist; names are stored exclusively in the archive.
Measured bitrate over all — page bytes divided by play duration:
streamed.resources 139,732 bit/s (16.45 h audio)
english.streamed 102,422 bit/s (8.11 h audio)
This aligns with br_nominal 128000 and 96000 plus Ogg frame overhead. The bitrate is thus measured and explained, not estimated.
During the pass it became apparent: of the 18,474 streams in streamed.resources, only 14,603 lie on the 16-KiB grid. The remaining 3,871 begin mid-block. This is not stated in docs/audio.md because there only the sample descriptors were followed, and they all point to grid addresses.
The explanation is the asset type sound with path extension .samplepack, Magic 1KPS (= SPK1 byte-swapped). Layout, derived and then fully verified against all packs:
u8[4] "1KPS"
LE u16 Number of members
per member:
LE u32 Path length
char[] Path of the sample asset, e.g.
"generated/sound/weapons/ar60/ar60_ai_split/…_vorbis.bsnd"
LE u32 Offset, relative to block start
LE u32 Length in bytes
149 non-empty packs (plus 2 empty placeholders with count = 0), members min 5 / median 25 / max 140, totaling 4,020 streams and 45,113,737 bytes. 143 packs are under weapons/, 6 under enemies/.
Content-wise these are the layered weapon sounds: a pack like weapons/ar60/split_ai/ar60_ai_split_nearmid bundles 40 variants (fire_first_r001…r005, fire_split_r001…r030, fire_tail_r001…r005) which are merged together on firing. They lie one after another in one streaming block: a single read operation, not forty.
Important for Mods: the 4,020 pack members have no own index entry. They are addressable exclusively via the pack. Anyone looking only at sample assets will see 21.8 % of the streams in streamed.resources missing.
The requirement was: the structure must span the entire file, gap-free, non-overlapping, ending matching the file size. This is the finding.
tools/streamed.py verify runs both containers without any index, collects every found stream start along with its measured length, and then asks for each individual one: is it claimed by any archive descriptor?
streamed.resources Streams 18474 claimed 18474 (100,000 %) remaining 0
claimed Bytes 1.034.059.500 of 1.034.059.500 page bytes (100,000 %)
english.streamed Streams 5091 claimed 5091 (100,000 %) remaining 0
claimed Bytes 373.618.721 of 373.618.721 page bytes (100,000 %)
total 23565 Streams, 23565 claimed (100,000 %)
Conversely: no descriptor must point to nothing. The length in the descriptor must exactly match the length resulting from the Ogg page scan in the container.
unique sample-Assets 19545 hits exact 19545 failures 0
unique samplepacks 151 of which empty 2
fully consumed 149 failures 0 members 4020
"Fully consumed" means for a pack: every one of its members lands on a real stream start and the length matches exactly. A single false member causes the entire pack to fail.
19.545 sample-Assets (= all 16-KiB block starts except the packs)
+ 149 samplepack-block-starts
= 19.694 blocks = 14.603 + 5.091 ✓
4.020 pack-members − 149 block-starts = 3.871 non-grid-aligned streams ✓
19.545 + 4.020 = 23.565 streams total ✓
Each of these three equations was measured independently. No byte and no entry remains unaccounted for.
For both files, page_bytes + padding_bytes − file_size = 0. The scan assigns every single byte: either part of an Ogg page or null padding. Unexplained bytes: 0. No overlap case, no backward offset, no sequence number gap.
The chain spans two separate files and closes on the individual PCM sample exactly. Executed with the real reference decoder (ffmpeg 8.1.2, libvorbis), not with a heuristic.
Archive (chunkN.index) Offset 0x08638000 (grid-aligned)
Descriptor (bsnf in archive) Length 2.681.895 B Granule 7.938.432 Samples
Container (measured) Length 2.681.895 B Granule 7.938.432
623 Ogg pages, CRC ok 623 / incorrect 0
Vorbis-ID 2 channels, 48000 Hz, br_nom 128000
ffprobe codec_name=vorbis duration=165.384000
ffmpeg -f s16le 31.753.728 B / 2 channels = 7.938.432 PCM-Frames
7.938.432 == 7.938.432. The number in the archive and the number calculated by a foreign decoder from the container bytes are identical. 165.384 s results with 2.681.895 bytes exactly 129.7 kbit/s — the nominal bitrate of the header.
Two further proofs in the same form:
| Entry | Container | Length | Granule | ffmpeg-PCM-Frames |
|---|---|---|---|---|
sound/music/journal/karla_master.wav |
streamed.resources |
2.115.418 | 6.260.416 | 6.260.416 |
sound/vo/english/bj/efforts/hurt/eff_pl_hurtsoft_001.wav |
english.streamed |
11.991 | 33.340 | 33.340 |
The harder case, because this stream has no own index entry and is only findable via the 1KPS table — member 1 of
weapons/ar60/split_ai/ar60_ai_split_nearmid, absolute 0x003F167A, thus in the middle of the block:
Pack says: Offset relative 0x167A, Length 5.792 B
Container: 5.792 B, 3 pages, CRC ok 3 / incorrect 0, Granule 4.080
ffprobe: vorbis, 48000 Hz, 2 channels, duration=0.085000
ffmpeg: 16.320 B / 4 = 4.080 PCM-Frames
4.080 == 4.080, and 4080 / 48000 = 0.085 s. The chain Pack table → container bytes → decoder closes exactly as well.
An inspection that accepts everything checks nothing. This one checks three things separately — Magic, page arithmetic, and CRC — and each must be able to fail individually.
The Ogg checksum is CRC-32 with polynomial 0x04C11DB7, MSB first, Init 0, no Final-XOR, no reflection. This is explicitly not zlib.crc32 (same polynomial, but reflected, Init 0xFFFFFFFF, Final-XOR). For the example b"OggS-probe-0123456789": zlib.crc32 = 0x291ADAC9, ogg_crc = 0x36C40DCD.
Input Bytes Pages CRCgepr CRCbad CRCok Cover%
Random bytes 1048576 0 0 0 0 0,000
Zeros 1048576 0 0 0 0 0,000
chunk0.resources @1MiB (TNO, real) 1048576 0 0 0 0 0,000
chunk_1.resources (TNC, real) 1048576 0 0 0 0 0,000
Random bytes with "OggS" Magic 1048576 0 0 0 0 0,000
Real Ogg start (unchanged) 1048576 226 226 0 226 65,862
Real Ogg start, 1 bit flipped 1048576 226 226 1 225 65,862
What this shows:
- Real game data that is not Ogg is rejected — both archive formats, not just random. 0 pages, 0% coverage.
- Magic alone is insufficient. Random bytes preceded by "OggS" yield 0 pages: the version byte check and segment table arithmetic catch them.
- The CRC is sharp. A single flipped bit in a real stream causes exactly one page to fail — and only that one.
Plus the full run: all 362,152 Ogg pages of both files were CRC-checked, 0 errors. There is no falsely structured entity that happens to hit a 32-bit checksum 362,152 times in a row.
tools/streamed.py --selftest additionally builds a valid Ogg page by hand, checks CRC and arithmetic, and ensures that a corrupted page and false Magic fail.
After the upper scope of the sample name, across both containers combined (only the 19,545 indexed assets; the 4,020 Pack members count towards their Pack):
| Scope | Streams | Bytes |
|---|---|---|
vo (Voice output) |
5,896 | 385.8 MB |
music |
461 | 382.3 MB |
ambience |
1,331 | 249.6 MB |
anim_events |
1,810 | 106.5 MB |
weapons |
4,078 | 65.5 MB |
enemies |
2,122 | 53.7 MB |
ambient_events |
265 | 35.1 MB |
vehicles |
180 | 21.6 MB |
movements |
941 | 13.2 MB |
footsteps |
1,279 | 12.3 MB |
explosions |
164 | 9.8 MB |
env_vo |
63 | 7.5 MB |
impacts |
295 | 4.2 MB |
projectile |
255 | 3.6 MB |
| Rest (≈ 20 more) | 405 | ~9 MB |
english.streamed contains exclusively sound/vo/english/* — 5,091 streams, 373.6 MB, 8.11 hours, a single namespace, no exceptions. streamed.resources contains everything else, distributed across 404 namespaces, including non-English voice output under vo/efforts (775 streams, neutral sounds like screams).
Largest individual entries: music with 461 streams and 382 MB — so the music is almost as large as the entire English voice output. The longest stream in the container runs 357.4 s, the shortest 0.001 s.
Stream sizes: streamed.resources min 4,426 / median 12,146 / max 5,789,560 bytes; english.streamed min 6,897 / median 37,199 / max 2,273,748 bytes.
What is now safe:
-
Export. Each of the 23,565 Sounds can be individually extracted and played back with any standard player —
tools/streamed.py prove <pattern> <directory>generates ready-made.oggfiles. For modding, this is the foundational material: you hear what you will bend before you bend it. -
Bending without touching the container. The method described in
docs/audio.md§4 — makingedit.soundFiles.item[i]of asounddeclaration point to another existing sample — remains correct. It is now easier to use because candidates can be heard beforehand.
What works, but is dangerous:
A sample replaces itself by writing new Ogg bytes into its block and adjusting the length in the bsnf. Technically, there is no objection: the block has an average of 8.6 KiB slack (english.streamed: 8.0 KiB), there is no checksum at the container level, and the blocks are non-overlapping. It has not been done, and it should not be done, for a reason unrelated to the format:
streamed.resourcesandenglish.streamedlie in no backup.base/_wolfsdk_backup/contains onlychunkN.indexandchunkN.resources. A corrupted byte costs 1.5 GB of re-download via Steam's file verification.
Anyone who intends to do this must first: make a full copy of both files outside the game directory, and extend wolfsdk/patch.py to treat this copy like the archive backup. Without both, it is not modding, but a risk.
Three limitations apply, all measured:
-
Block space is tight. An average of 8,544 B slack (
english.streamed: 8,041 B) means a replacement must be roughly 8 KiB larger than the original on average, or else it will overwrite the neighboring block. There is no more than 16,382 B slack anywhere, and in at least one block it is 0. -
Packs are rigid. Changing a member of a
samplepackshifts all subsequent members — and the offsets are recorded in the1KPStable within the archive. These are two files that must match. This affects 4,020 streams, or 21.8% ofstreamed.resources. -
The descriptor contains more than just length. The
bsnfincludes Granule, Channels, Rate, and aWAVEFORMATEX. A replacement with a different channel count or sample rate requires including all of this. Hypothesis, how the engine reacts to contradictions: untested and unverifiable without starting the game.
What does not work: Part 2. There, such containers do not exist; the audio lies in Wwise Banks, which is a separate, unresolved topic.
-
The other three languages. The descriptors know
french,italian, andspanish; no installed files exist for them. Whether they lie alongside asfrench.streamedor similar is speculation. -
voicetrack(30,627 entries, 5,115 unique,.bmvtr). These demonstrably do not belong in these containers. Two independent reasons: first, after matching, every single stream is already claimed by asampleor asamplepack, leaving none for them. Second, their field at the same location wheresampleleads to the container offset (+12+20) contains the value0x706C653Ffor all 5,115 — these are the ASCII charactersple?, i.e., remnants of a string, not an offset; 0 out of 5,115 hit a stream start. The entry remainder invoicetrackis simply structured differently. What resides in their 310-byte blocks per language (docs/audio.md§2 speculates on lip-sync data) remains open — but it is not audio. This is an exclusion, not a finding. -
The two empty
samplepackplaceholders (1KPS+count = 0,inheritences/weapons/weapons_ai_dry_looping,…/weapons_ai_env_looping). Their offset field contains ASCII (0x706C653F), pointing nowhere. Speculation: inheritance nodes without their own data, analogous to theinheritences/*sound decls. - Why exactly 16384. The grid is measured and applies universally, but the number itself appears nowhere in the EXE search this task covered. It is likely the sector size of the streaming reader. Speculation.
-
The EXE section.
.textis SteamStub-encrypted; the task did not allow unpacking. Therefore, anidStreamedFileclass could not be reconciled with the finding. It was not necessary: the structure is fully populated from the data alone.
docs/audio.md §3 was correct in every point and forms the basis of this work. What is added here:
Audio.md §3 |
here | |
|---|---|---|
| Access | via sample descriptors |
additionally without any index, directly through the file |
| Streams | 14,454 + 5,091 slots | 18,474 + 5,091 streams |
| Coverage | not quantified | 100.00 %, balance 0 |
| Padding | "free space per slot" | proven pure zeros, 100% |
samplepack |
mentioned as type, content unknown | layout solved, 149 packs, 4,020 members |
| Checksum | — | all 362,152 Ogg pages CRC-checked, 0 errors |
| Decoder | — | ffmpeg, PCM frames == Granule |
| Cross-check | — | present, separates Magic / Arithmetic / CRC |
The one number that shifts: "14,454 occupied slots" were the grid-aligned blocks without packs. With the packs included, there are 14,603 blocks and 18,474 streams. Both numbers are correct; they count different things.
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