diff --git a/NOTES.md b/NOTES.md index c06c85d..683f38d 100644 --- a/NOTES.md +++ b/NOTES.md @@ -14,6 +14,8 @@ Packaging: `python tools/build_app.py` -> dist/UnheardCity (self-contained, licenses); packaged exe passes `--smoke-test` with PATH = C:\Windows only. Real e2e through the editor code path (tools/e2e_editor.py): PASS (music heard in game ncc 1.0, reverts byte-identical, speech line apply/revert byte-identical). +- NEXT: editing of the ~15,800 in-memory sounds in soundbanks (loose `SFX\*.bnk` footsteps first, then banks + embedded in `.upk` packages); not listed or editable yet, survey in `docs/formats/banks.md`. - NEXT (optional): verify >2-channel channel order in game; speech heard in-game (needs gameplay). - OPEN: channel order for >2 channels unverified. @@ -80,3 +82,7 @@ User chose no new software (no Inno Setup). - 2026-10-09: app/assets is committed (user: public artwork and generated content, not Warner Bros. pictures). build_app.py regenerates it only when missing or with --art; after changing make_assets.py run it by hand. +- 2026-10-09: SFX coverage check (read-only): the catalog covers every WAD sound, but ~15,800 in-memory WEMs in + soundbanks are missing (docs/formats/banks.md). Packages were unpacked with Gildor's decompress.exe from + `%USERPROFILE%\Desktop\ArkhamCityModding\Audio\decompress\` (outside the repo). User: document now, implement + editing later. diff --git a/README.md b/README.md index 26ba0eb..881ab18 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Replace any voice line, music track or ambience in **Batman: Arkham City (PC, St by name, from a file or a recording, with one-click revert. No Wwise, no game patch, no external programs. - **Unheard City** (`app/`, [user guide](docs/user-guide.md)): the desktop editor for Windows and Linux. Browse and - search all ~11,500 sounds, filter by category and character, listen, replace or record, apply and revert. + search the ~11,500 sounds of the game's audio archives, filter by category and character, listen, replace or + record, apply and revert. Most short effects (footsteps, foley, weapons, UI) live inside soundbanks and are + not listed yet ([docs/formats/banks.md](docs/formats/banks.md)). - **`ackit`** (`ackit/`): the Python library and command-line tool behind it. How it works in one paragraph: the game's Wwise 2011.1 decoder only knows the standard Vorbis codebooks, so ackit diff --git a/REPORT.md b/REPORT.md index c0801f7..540d3c3 100644 --- a/REPORT.md +++ b/REPORT.md @@ -21,6 +21,9 @@ Contains a per-user installer (`Install.cmd`): install, upgrade and uninstall (Settings > Apps) verified. ## What is not verified / limitations +- **Coverage**: only sounds in the WAD archives are listed and editable. About 15,800 short effects (footsteps, + foley, weapons, UI, grunts) are stored inside soundbanks (loose `.bnk` and banks embedded in `.upk` + packages) and are not covered yet (`docs/formats/banks.md`). - Speech lines being *heard* in the game needs gameplay; verified indirectly (same decoder/WAD path as the music test, byte-level apply/revert, boot test with modified speech WAD). - Speech/effects replacements must fit the original's size (index lives in compressed `.upk` packages). @@ -29,5 +32,7 @@ - Character labels outside the user's lists are inferred from names (marked as such), not verified speakers. ## Next steps -1. Extend the curated speaker lists (`data/catalog/source//*.txt`) and rebuild with `tools/build_catalog.py`. -2. Optional: rewrite the BKPK indexes inside `.upk` packages (LZO) to lift the size limit for speech. +1. Support the in-memory sounds in soundbanks: the loose footstep banks first, then the banks embedded in + `.upk` packages (`docs/formats/banks.md`). +2. Extend the curated speaker lists (`data/catalog/source//*.txt`) and rebuild with `tools/build_catalog.py`. +3. Optional: rewrite the BKPK indexes inside `.upk` packages (LZO) to lift the size limit for speech. diff --git a/ackit/allocsize.py b/ackit/allocsize.py index f211a07..42993d3 100644 --- a/ackit/allocsize.py +++ b/ackit/allocsize.py @@ -1,6 +1,6 @@ """Model of the game's codec-setup bump-allocator usage (dwDecodeAllocSize). -Reversed from the dumped BatmanAC.exe (see docs/decoder_re.md): +Reversed from the dumped BatmanAC.exe (see docs/reverse-engineering/decoder.md): WwiseVorbisParseSetupHeader (runtime 0x010B4D10) and its callees are a Tremor-lowmem derivative; every pool allocation is rounded up to 4 bytes. """ diff --git a/ackit/builder.py b/ackit/builder.py index d88288d..1812774 100644 --- a/ackit/builder.py +++ b/ackit/builder.py @@ -1,7 +1,7 @@ """Build an Arkham City compatible WEM from a standard Ogg Vorbis stream. -Field rules were derived from all 2467 WEMs of the ITA WAD and are validated by -rebuilding original files byte-for-byte (scripts/validate_builder.py). +Field rules were derived from the WEMs of the ITA WAD and are validated by +rebuilding original files byte-for-byte (`ackit verify`, tests/test_realgame.py). """ import struct diff --git a/ackit/catalog.py b/ackit/catalog.py index 55139a3..f939535 100644 --- a/ackit/catalog.py +++ b/ackit/catalog.py @@ -1,4 +1,5 @@ -"""Index of every sound in the game, with names, categories and characters. +"""Index of every sound in the game's WAD archives, with names, categories and characters +(sounds stored inside soundbanks are not indexed yet: docs/formats/banks.md). Sources, in order: 1. data/catalog/.csv -- shipped catalog (instant) when the WAD fingerprint matches; diff --git a/ackit/codebooks.py b/ackit/codebooks.py index db61d01..d2b4066 100644 --- a/ackit/codebooks.py +++ b/ackit/codebooks.py @@ -1,7 +1,7 @@ """Wwise packed codebook library (ww2ogg 'packed_codebooks.bin' format). The game's built-in table (runtime 0x0158E748, 598 entries) is byte-identical to -ww2ogg's standard packed_codebooks.bin; see docs/wem_format.md. +ww2ogg's standard packed_codebooks.bin; see docs/formats/wem.md. """ import os import struct diff --git a/ackit/pack.py b/ackit/pack.py index b35f860..4edaa23 100644 --- a/ackit/pack.py +++ b/ackit/pack.py @@ -9,7 +9,7 @@ BKPK header (u32 LE): 0x2C end of table (0x48 + 12N) 0x38 embedded bank size 0x3C embedded bank offset table: N x {u32 media id, u32 size, u32 offset-in-WAD-entry}, sorted by id; each media starts at the next 0x800-aligned offset after the previous one. -See docs/wad_format.md. +See docs/formats/wad.md. """ import struct diff --git a/ackit/verify/debugger.py b/ackit/verify/debugger.py index 74c3de2..b7539a5 100644 --- a/ackit/verify/debugger.py +++ b/ackit/verify/debugger.py @@ -31,7 +31,7 @@ def find_cdb(): return p raise FileNotFoundError("x86 cdb.exe not found (install WinDbg or set ACKIT_CDB)") -# Runtime addresses (image base 0x400000; ASLR off for this exe). See docs/decoder_re.md. +# Runtime addresses (image base 0x400000; ASLR off for this exe). See docs/reverse-engineering/decoder.md. ADDR = dict( get_or_create_setup=0x010B3400, # __thiscall(cache, AkSrcInfo*, ...) ; info+0x9c hash parse_setup=0x010B4D10, # WwiseVorbisParseSetupHeader(info, channels, reader, pool) -> 0 ok diff --git a/ackit/wad.py b/ackit/wad.py index 0ae4151..9a1dd73 100644 --- a/ackit/wad.py +++ b/ackit/wad.py @@ -11,7 +11,7 @@ Layout (little endian): 0x1C N x {u32 hash, u32 size, u32 offset} sorted by hash as *signed* int32 Entries are stored in table order, each at a 0x1000-aligned offset; 'size' is the entry payload size padded to 0x800. Padding bytes are zero; the file ends 0x1000-aligned. -See docs/wad_format.md. +See docs/formats/wad.md. """ import struct from dataclasses import dataclass diff --git a/ackit/wwvorbis.py b/ackit/wwvorbis.py index 1bed468..3c60d48 100644 --- a/ackit/wwvorbis.py +++ b/ackit/wwvorbis.py @@ -2,7 +2,7 @@ Wwise stores a stripped setup header (codebooks referenced by 10-bit IDs into the built-in library, narrowed fields) and "modified" audio packets (no packet-type bit, -no prev/next window flags). See docs/wem_format.md. +no prev/next window flags). See docs/formats/wem.md. """ from .bits import BitReader, BitWriter, ilog from .codebooks import CodebookLibrary, wwise_book_to_vorbis, skip_vorbis_book, book_bits_key diff --git a/data/catalog/README.md b/data/catalog/README.md index 92e917f..ca22014 100644 --- a/data/catalog/README.md +++ b/data/catalog/README.md @@ -2,7 +2,7 @@ | file | content | |------|---------| -| `.csv` | every sound of one audio archive: `ITA`, `INT`, `FRA`, `DEU`, `ESN` (speech, one per language) and `SFX` (music, ambience, effects) | +| `.csv` | every sound of one audio archive: `ITA`, `INT`, `FRA`, `DEU`, `ESN` (speech, one per language) and `SFX` (music, ambience, effects of the SFX WAD; sounds stored inside soundbanks are not listed, see `docs/formats/banks.md`) | | `characters.csv` | `name,character`: verified speaker per sound name (language independent) | | `index.json` | fingerprint of the game WAD each CSV was built from (`ackit/game.py: wad_fingerprint`) and counts | | `source//*.txt` | **the curated original lists** (one file per character, one wemscan-style file name per line, e.g. `Speech_TAPE_JOKER_1_4.wem`). `Files.txt` = all files, `Stats.txt` = counts, `Checkpoint.txt` = progress marker. Never edited by tools. | diff --git a/docs/README.md b/docs/README.md index 858700d..1289c71 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ | [categories.md](categories.md) | group/category scheme and character fields | | [building.md](building.md) | building the editor and its installer (Windows, Linux) | | [formats/wad.md](formats/wad.md) | WWAD archives, grouped entries, BKPK stream packs | +| [formats/banks.md](formats/banks.md) | soundbanks and the in-memory sounds not covered yet (footsteps, foley, effects) | | [formats/wem.md](formats/wem.md) | WEM layout the game accepts, setup stripping, hash and decode-alloc formulas | | [reverse-engineering/decoder.md](reverse-engineering/decoder.md) | BatmanAC.exe decoder functions and addresses | | [reverse-engineering/findings.md](reverse-engineering/findings.md) | root cause, findings, in-game experiment log (incl. failed hypotheses) | diff --git a/docs/formats/banks.md b/docs/formats/banks.md new file mode 100644 index 0000000..0ff8cfa --- /dev/null +++ b/docs/formats/banks.md @@ -0,0 +1,65 @@ +# Soundbanks and in-memory sounds + +Most short sound effects (footsteps, foley, combat, weapons, objects, UI, grunts) are **not in the WADs**: they +are WEMs stored inside Wwise soundbanks, either loose in `SFX\*.bnk` or embedded in the UE3 packages (`.upk`). +The catalog and the editor only cover the WADs (`wad.md`), so none of these sounds are listed or editable yet. +Survey of the installed game (buildid 3718300) on 2026-10-09, read-only. + +## Inventory + +| where | banks | unique in-memory WEMs | in the catalog | +|-------|-------|-----------------------|----------------| +| loose `SFX\*.bnk` (Batman/Catwoman footsteps, 32 surfaces each, + `TST_Prep_Media`) | 65 | 1,117 | 0 | +| banks embedded in `.upk` packages (in 1,432 of the 2,650 packages) | 3,340 | 15,641 (~276 MB) | 0 | + +The two sets share 119 media IDs: about **15,800 unique sounds** in total. The WADs, by comparison, are fully +covered: the BKPK tables in the packages reference 2,543 WAD entries, all of them in the catalog. + +Embedded WEMs by bank name prefix: `Speech` 7,872, `CHR` (character foley, combat, bosses) 3,896, `PSO` 781, +`WPN` 664, `NPC` 475, `OBJ` 439, `SEQ` 265, `GUI` 208, `PSA` 203, `MTN` 196, `PX` 195, `DVC` 100, `GEN` 80, +`PHY` 73, `AMB` 65, `SBJ` 50, others < 30. The `Speech_` banks (e.g. `Speech_EMOTE_Batman`, `..._GRUNT`) are +probably efforts/grunts rather than dialogue (there are no per-language packages); not verified by listening. +All WEMs seen are Vorbis (format tag 0xFFFF), the same codec as the WADs: the footsteps are mono (4 stereo), +the embedded ones mono or stereo (~22 % stereo in a 2,533-WEM sample). + +## Names +- The community hash list names none of these media IDs; only the bank names are known. +- `CookedPCConsole\CookedBanks.txt` lists all 3,345 cooked bank names. Bank ID = FNV-1 32 of the lowercased + name (the same hash Wwise uses for every name). 3,338 of them are embedded in packages, 4 are loose `.bnk` + files (`Init`, `Global`, `Music`, `Music_PDLC`); `AMB_OW_A6`, `AMB_OW_C1`, `AMB_OW_E1`, + `Speech_EMOTE_Thug_Muffled_Stop` and `Speech_GFxUI_ArkhamCityStories` were found nowhere. +- Loose footstep banks are named in their STID chunk (`CHR_BatmanFootsteps_Concrete_Media`, ...). + +## Bank layout (version 56, Wwise 2011.1) +Chunks `BKHD` {u32 version 56, u32 bank id, ...}, `DIDX` N x {u32 media id, u32 offset, u32 size} (offsets +relative to the `DATA` payload, WEMs 16-byte aligned), `DATA`, `HIRC`, `STID` {u32 type, u32 count, +count x {u32 bank id, u8 len, name}}. `Init` (`50C63A23.bnk`) has `STMG`/`ENVS` instead of media. +`701C1363.bnk` holds only `BKHD`. + +HIRC Sound object (type 2) payload: u32 object id, u32 plugin id (`0x00040001` = Vorbis), u32 stream type +(0 = in memory, 1 = streamed, 2 = prefetch), u32 media id, u32 bank id of the media; for in-memory sources +then u32 offset, u32 size, where offset = `DATA` payload position (from the start of the bank) + DIDX offset +and size = DIDX size (checked on every in-memory source of the footstep banks and of `BmGame.upk`). +A few sources use media of another bank. Every streamed media ID found is in a BKPK table, i.e. a WAD sound +(`wad.md`). + +## Banks inside packages +- The packages are fully LZO-compressed (package version 805, licensee 101). For the survey they were unpacked + with Gildor's `decompress.exe` (gildor.org; used from the user's old modding folder, not part of this + repository) and searched for `BKHD` (banks) and `BKPK` (WAD indexes). `UnrealX.upk` (512 bytes) is not a + readable package (wrong tag, probably encrypted). +- Bank blobs in packages: 41,798 for 3,340 bank IDs. Many banks are copied into several packages (e.g. + `CHR_CatwomanFoleySuit` 31 times, `CHR_BatmanFoleyBelt` 26). 302 bank IDs occur as several blobs with + *different* media subsets (often one WEM each, several per package), so a bank's media set is the union + of all its blobs. +- Largest: `CHR_RobinFoleyCape` 300 WEMs, `CHR_BatmanFoleyCape` 282, `CHR_Clayface` 263, + `CHR_NightwingFoley` 242, `CHR_CatwomanFoleySuit` 225, `CHR_MrFreeze` 176, `CHR_Combat` 166. + +## Consequences for editing (not implemented) +- **Loose banks**: a replacement means rebuilding `DATA`/`DIDX` and the in-memory offsets/sizes in the HIRC + Sound objects of that bank (and in any other bank whose sources use its media). Whether the game accepts a + bank that changed size is untested. +- **Embedded banks**: the package must be rewritten (decompress, patch every blob of the bank, recompress + LZO, fix the package's chunk table and export offsets/sizes). A sound must be replaced in **every** package + that carries a copy, otherwise the game may load the original from another package. +- Either way: unverified in game so far; the same WEM rules apply (`wem.md`). diff --git a/docs/formats/wad.md b/docs/formats/wad.md index 23bec2b..09f7be5 100644 --- a/docs/formats/wad.md +++ b/docs/formats/wad.md @@ -63,8 +63,7 @@ original bytes where nothing moved). Replacing a pack media (`ackit` target `EDF036D6/393BC53A`) rewrites the pack table (`.bnk`), the WAD entry payload, and the WAD table/offsets. Verified in-game (NOTES.md, E2). -## Regular soundbanks (`SFX\*.bnk`, bank version 56) +## Regular soundbanks -BKHD / DIDX {id, offset, size} / DATA / HIRC / STID. In-memory WEMs (DATA) are 16-byte aligned. -`ackit/bnk.py` can read/rebuild them, but most front-end sounds come from banks embedded in UE3 -packages (`.upk`), which this toolkit does not modify. +The other `SFX\*.bnk` files and the banks embedded in `.upk` packages hold their WEMs in memory (not in a +WAD); see `banks.md`. The toolkit does not read or modify them yet. diff --git a/docs/reverse-engineering/findings.md b/docs/reverse-engineering/findings.md index 35062e2..9591fa0 100644 --- a/docs/reverse-engineering/findings.md +++ b/docs/reverse-engineering/findings.md @@ -68,6 +68,10 @@ Conclusion: approach A works end-to-end in-game. Use >= 30 s probes for the intr 6 channels work at <= 32 kHz (uncoupled, like Wwise). The encoder falls back to 32 kHz automatically. 15. Channel order for > 2 channels: the bitstream order equals the array order given to libsndfile; whether the game expects WAVEFORMATEX order (FL FR FC LFE BL BR) is unverified (identical-channel probes were used). +16. (2026-10-09) The WADs hold only streamed audio. About 15,800 more sounds (footsteps, foley, combat, weapons, + UI, grunts) are in-memory WEMs inside soundbanks: 65 loose `SFX\*.bnk` (1,117) and 3,340 banks embedded in + the `.upk` packages (15,641; many banks copied into several packages). None are in the catalog; the WAD + side is complete. Inventory, layout and editing consequences: `docs/formats/banks.md`. ## Old path -> new path `scripts/harness.py` -> `ackit verify --ingame` (ackit/verify/ingame.py); `scripts/validate_builder.py` -> diff --git a/docs/user-guide.md b/docs/user-guide.md index 6eae042..a525b77 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -26,6 +26,8 @@ back with one click. sound's name (useful hints for the many unlabelled lines); tick *Include characters mentioned in names* to filter on them too. - Click a column header to sort (length, size, status, ...). Ctrl/Shift-click selects several rows. +- Most short effects (footsteps, foley, weapons, menu sounds) are stored inside soundbanks and are not listed + yet; the list covers voice lines, music, ambience and the effects of the SFX archive. - **Language** (top left) switches the speech language. Speech is read from the language the game is set to. ![Browsing by category](img/browse.png)