Document soundbank findings

This commit is contained in:
2026-10-09 01:46:44 +02:00
parent f2bc986a82
commit 67bc541b1e
17 changed files with 102 additions and 17 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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/<lang>/*.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/<lang>/*.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.

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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/<ARCHIVE>.csv -- shipped catalog (instant) when the WAD fingerprint matches;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -2,7 +2,7 @@
| file | content |
|------|---------|
| `<ARCHIVE>.csv` | every sound of one audio archive: `ITA`, `INT`, `FRA`, `DEU`, `ESN` (speech, one per language) and `SFX` (music, ambience, effects) |
| `<ARCHIVE>.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/<lang>/*.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. |

View File

@@ -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) |

65
docs/formats/banks.md Normal file
View File

@@ -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`).

View File

@@ -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.

View File

@@ -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` ->

View File

@@ -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)