Where .wem/.bnk sound media live in The New Colossus, whether the engine checks sizes or hashes on them, and how a bigger replacement would be appended. TNC uses Wwise audio banks; TNO does not use Wwise at all (an exhaustive file search across the whole TNO install finds zero .wem/.bnk/Wwise-named files of any kind — its audio is raw Ogg Vorbis in two monolithic containers, a separate, already-documented system). Every TNC sound pack turns out to be an ordinary archive container, the exact same format already used for textures and decls, so the project's existing generic archive writer applies unmodified — no sound-specific writer had to be built. A full structural round trip (replacing a loose sound and a bank-embedded sound, both forced through a full archive rebuild, plus two negative controls) succeeds end to end and re-reads correctly through the project's own unmodified readers. Everything here was built and torn down on disposable temporary copies; nothing was run in a live game.
Storage — PROVEN
Every sound pack (base/sound/soundbanks/pc/*.pack, and per-DLC equivalents) is an ordinary archive container — same format as the main game archives, entry type "file", compression 0 (stored throughout). The generic archive writer this project already uses for textures/decls/entity definitions applies to sound packs unmodified.
Two kinds of entry inside a pack:
- a loose top-level
<id>.wementry — the payload is the whole RIFF/WAVE file; - a
.bnkentry — itself a small container:BKHD(header),DIDX(index:u32 media id, u32 offset, u32 size, 12 bytes/slot),DATA(the WEM slices back to back),HIRC(objects: events, actions, Sound nodes).
No language-id field exists in DIDX itself — language is the pack/folder, not a per-row field.
The load-bearing duplicate-size invariant
Every HIRC Sound object carries its own duplicate u32 size field (byte offset 9 of the object body). Across all 131,236 Sound objects in the full retail+DLC install, wherever the referenced media also has a local DIDX row in the same bank, that row's size and the Sound object's own size field are equal — 0 mismatches. A replacement must patch both the DIDX row's size and the matching HIRC Sound object's own size field, or it reintroduces exactly the inconsistency that never occurs in 131k shipped rows (plausible rejection risk; the actual runtime check itself was not disassembled, no disassembler was available in this research environment).
Container-level checks — PROVEN (pre-existing project findings, re-applied here)
The function every pack/archive file goes through to be mounted unconditionally checks magic, version, the header hash, and the metadata hash on every open. Both hashes are FarmHash64-derived and fully recomputable with the project's existing hash-finalizer — already the production write path for every other asset type this project patches. An optional, off-by-default per-entry payload hash is the same mechanism, nothing Wwise-specific layered on top.
Growth/append rule (PROVEN, round-tripped)
A stored (uncompressed) entry has exactly three outcomes when its payload length changes (measured against a real entry with a real trailing gap):
| New length | Result |
|---|---|
| same as old | in-place rewrite, nothing else moves |
| bigger, fits the trailing gap | in-place (same mechanics as the already-proven texture-database append trick) |
| bigger, doesn't fit the gap | full archive rebuild — every later entry's offset shifts |
| smaller, by even 1 byte | full rebuild — a stored entry's in-place path requires an exact length match |
What a replacement must be — PROVEN by elimination
Two codecs occur in retail: Wwise's packed Vorbis, and PCM. The project can decode packed Wwise Vorbis (strip the custom bit-packing back to stock Ogg Vorbis, using a codebook library extracted from the game executable) but has no encoder — producing a new, correctly packed Vorbis stream would mean writing a full Vorbis encoder constrained to reuse the shipped codebook library bit-for-bit, out of scope for this project. The practical replacement codec is 16-bit PCM: the project's own acceptance rule is a dozen ordinary WAVE-format fields (16-bit, block align = 2×channels, 1–8 channels, 1000–192000 Hz) — a plain RIFF/WAVE file any standard library can build from scratch.
Loose-file loading — does not exist for shipped data
Zero .wem files exist outside a pack anywhere in the install. The decompiled string constants support the same conclusion: the entry-name format strings read as archive-internal names (<lang>/<id>.<ext>), not filesystem paths, and the only related error string is specifically about failing to open a pack file.
Full structural round trip — PROVEN
Both a loose top-level .wem replacement and a bank-embedded (DIDX+HIRC) replacement were built, forced through the expensive rebuild path (both replacements were larger than any available in-place gap), and re-read successfully with the project's own unmodified readers — the re-reader was not told in advance where the new bytes were, finding them the same way a fresh game process would. Two negative controls both correctly fired: leaving the HIRC duplicate-size field stale is caught by the size-mismatch check; flipping one header-hash byte is caught by independent hash re-derivation.
Recipe
- Open the pack, find the target entry (
<id>.wemdirectly, or the owning.bnk+ that id'sDIDXrow). - Build the replacement as a 16-bit PCM WAVE file — sidesteps the missing Vorbis encoder entirely.
- Loose entry: rewrite its bytes in place through the archive writer. Bank entry: split the
.bnkinto its four sections, append the new WEM toDATA, rewrite that id'sDIDXrow, patch the matchingHIRCSound object's own size field, rejoin, and write the rewritten bank back. - Always finish with the project's header-hash finalizer — the same step the existing write path already performs for every other asset type.
- Re-verify every slot in the touched bank (not just the changed one) before touching the real install — the regression control that would catch an off-by-one section-length mistake.
Open questions
- Whether the engine actually enforces the HIRC/DIDX size match at load time, or it just always happens to hold in shipped data — needs either a disassembler or an in-game test.
- Whether the sound-pack open function goes through the same container-mount hash check as every other archive type — consistent with the identical file format measured, not confirmed by disassembly.
- TNO's own audio append path (appending a replacement past its current slot in the flat, offset-addressed container) was not round-tripped this pass — structurally analogous to the growth-to-rebuild path proven here, but not yet built or tested.
In-game test (not yet run)
Replace one unobtrusive menu sound effect with a 16-bit PCM replacement built as above (keep the original backed up). Start the game, trigger the sound, and confirm the new audio plays with no "corrupt header" message and no crash. Restore the original and re-trigger to confirm only that one file was affected.