# WEM format accepted by Arkham City (Wwise 2011.1, Vorbis) Derived from the game's WEMs; `ackit verify` / `tests/test_realgame.py` rebuild them byte-for-byte from these rules alone. Checked on every WEM of the 5 speech WADs: 54,079 of 54,080 are identical; the exception (INT `FE9963E8:74`) differs only in the informational avg-bytes/s field (one lower than the formula below). In SFX 626 of 651 are identical; the others are 14 music tracks whose seek table follows another placement (one entry per 1,024-1,103 samples), 5 music tracks whose sample count exceeds what their packets hold, and 6 4-channel ambience WEMs with a `smpl` (loop) chunk. These rules are not needed to write sounds the game plays (replaced music, built with the rules here, is heard in game: `ackit verify --ingame`), only to reproduce those originals. ## RIFF layout `RIFF` size `WAVE`, then `fmt ` (0x42 bytes), optional `LIST` + `cue ` (659 WEMs), `LIST` + `cue ` + `smpl` (1 WEM), `cue ` (18 WEMs), `cue ` + `smpl` (3 WEMs), `smpl` (41 WEMs), `data`. Odd-sized `data` chunks are **not** padded. ### `fmt ` (0x42) | off | type | value | |-----|------|-------| | 0x00 | u16 | 0xFFFF (Wwise Vorbis) | | 0x02 | u16 | channels (1,2,3,4,5,6 seen) | | 0x04 | u32 | sample rate | | 0x08 | u32 | avg bytes/s = `(data_len - seek_size) * rate // total_frames` | | 0x0C | u16,u16 | 0, 0 | | 0x10 | u16 | cbSize 0x30 | | 0x12 | u16 | valid bits (16 or 24 = source bit depth, informational) | | 0x14 | u32 | channel mask (mono 0x4, stereo 0x3, quad 0x33, 5.0 0x37, 5.1 0x3F) | | 0x18 | 42 B | AkVorbisInfo (below) | ### AkVorbisInfo (fmt+0x18) | off | type | field | rule | |-----|------|-------|------| | 0x00 | u32 | dwTotalPCMFrames | sample count | | 0x04 | u32 | dwLoopStartPacketOffset | `2 + setup_len` (relative to end of seek table) | | 0x08 | u32 | dwLoopEndPacketOffset | `data_len - seek_size` | | 0x0C | u16 | uLoopBeginExtra | 0 | | 0x0E | u16 | uLoopEndExtra | = uLastGranuleExtra | | 0x10 | u32 | dwSeekTableSize | bytes (4 per entry) | | 0x14 | u32 | dwVorbisDataOffset | `seek_size + 2 + setup_len` | | 0x18 | u16 | uMaxPacketSize | max audio packet payload | | 0x1A | u16 | uLastGranuleExtra | `sum(packet samples) - total_frames` (< blocksize) | | 0x1C | u32 | dwDecodeAllocSize | see "decode alloc" — **must be right** (too small = NULL write crash) | | 0x20 | u32 | dwDecodeX64AllocSize | `alloc + 8*nbooks + 40 + 52*nfloors` (fit; unused by 32-bit game) | | 0x24 | u32 | uHashCodebook | **FNV-1 32** of the stripped setup bytes; codec-setup cache key | | 0x28 | u8,u8 | uBlockSizes | log2 short, log2 long | ## `data` chunk ``` [seek table: N x {u16 granule delta, u16 byte delta}] [u16 setup_len][stripped setup packet] [u16 len][audio packet] ... ``` - Seek points: walk packets; point at the first packet whose *end* granule >= previous point + interval (8192 for SFX/speech; music uses 1024 or other per-sound values). Byte offsets are relative to the setup start (end of seek table). - No identification/comment headers (rebuilt from fmt). ## Stripped setup ("external codebooks") Standard Vorbis setup with: no `\x05vorbis` header; codebook count (8b), then each codebook as a **10-bit ID** into the built-in library; no time-domain section; floor type field removed (always 1); residue type 2 bits; mapping type removed; mode window/transform type removed. See `ackit/wwvorbis.py:transcode_setup` (bidirectional). Codebook library: the game's table (runtime `0x0158E748`, 598 books) is byte-identical to ww2ogg's standard `packed_codebooks.bin` (`ackit/data/game_codebooks.bin`). **Wwise 2011.2.x uses the aoTuV 6.03 library instead** — same IDs, different books — which is why files from the newer encoder decode to garbage/silence in this game. Stock libvorbis (1.3.7, in ffmpeg and in the libsndfile wheels ackit uses) setups map onto this library for all tested rates with 1/2/4/5 channels, and for 6 channels up to 32 kHz (its 5.1 books at 44.1/48 kHz are newer than the game). At 24 kHz q>=5 needs a book not in the library. The encoder probes and picks a compatible quality. ## Audio packets - Blocksizes differ (e.g. 256/2048): "modified" packets — packet-type bit and the long-window prev/next flags are removed (mode number stays first). - Equal blocksizes (512/512, 1024/1024): standard Vorbis packets (type bit kept). - 2-byte size header, no granule. ## Decode alloc (dwDecodeAllocSize) `0x30 + parser allocations + 4*nbooks + 8`, every allocation rounded up to 4 bytes. Parser allocations (Tremor-lowmem derived, `ackit/allocsize.py`): - codebooks: `nbooks*0x3C` + per book: decode table (`used<2` or nodeb==4: `used*8+4`, else `align4((used*(leafw+1)-2)*nodeb)`) + for dec_type 2 the quantized values (`align4(quantvals)` or `align4(2*quantvals)`). - floors: `nfloors*0x24` + per floor `a4(parts) + a4(11*(maxclass+1)) + a4(2*(posts+2)) + a4(posts+2) + 2*a4(posts)`. - residues: `nres*0x1C` + per residue `a4(ncls) + 8*ncls`. - mappings: `nmaps*0x14` + `a4(2*coupling_steps)` + `a4(channels)` if submaps>1 + `a4(2*submaps)`. - modes: `a4(2*nmodes)`. Exact on all 24 setups the game ships; in-game test E10 used a new setup with computed values.