Files
UnheardCity/data/catalog
..

Sound catalog

file content
<ARCHIVE>.csv every sound of one audio archive: INT, FRA, ITA, DEU, ESN (speech WADs, one per language), SFX (music, ambience and effects streamed from the SFX WAD) and BANK (in-memory sounds stored inside soundbanks: footsteps, foley, combat, weapons, UI, efforts; docs/formats/banks.md)
BANK_locations.csv.gz every stored copy of the BANK sounds, and the bank copies of WAD speech recordings (prefetch heads, whole copies; below)
SUB_<LANG>.csv subtitles of the speech lines, one file per language (11 languages; docs/formats/subtitles.md)
SUB_locations.csv.gz every stored copy of each subtitle string (dialogue objects in the packages)
characters.csv name,character,language: verified speaker per line name (empty language: every language, the same line everywhere; a language code would label a sound of that language only - none needs it now)
index.json per archive: fingerprint of the game files the CSV was built from (ackit/game.py: wad_fingerprint, ackit/banks.py: game_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. Never edited by tools.

All CSVs are UTF-8 with a header row. They contain only names and metadata (no game audio).

<ARCHIVE>.csv columns

column meaning
archive INT, FRA, ..., SFX or BANK
target location: HASH (WAD entry with one WEM), HASH:i (i-th variation, 0-based), PACK/MEDIA (music stream pack), MEDIA (BANK: Wwise media ID)
name display name. Speech: the line's name, the same in every language (docs/decisions.md 5): its speech event (e.g. Speech_TAPE_JOKER_1_04, NAME#2 = second version an event plays) or, for lines no named event plays, the INT recording's position name. A recording stored twice keeps its position name in the second place. SFX: NAME, NAME_1..n. BANK: the Wwise event that plays it (Play_..._i) or <Bank>_<n> (<Bank>_<MEDIA> where that name is taken, e.g. by the SFX WAD's position names)
aliases other accepted names, |-separated: NAME#i = i-th WEM of the entry in this language's WAD (positions differ between languages), the entry name for single entries, <Bank>/<MEDIA> for BANK
group Speech, Music, Ambience, SFX, Unknown (docs/categories.md)
category finer category (docs/categories.md)
bank BANK: the soundbank holding the sound (first by name if several do); empty for WAD sounds
language BANK: language(s) of a localized speech line, ;-separated (INT, or ESN;FRA;INT;ITA for one recording shared by those); empty = the same sound in every language. The editor shows the sounds of the selected language; names are unique per language
line speech (WAD and BANK): the line's identity, the same in every language (smallest Wwise Sound object ID of the line; ackit/lines.py). Two rows of one archive with the same line are one recording stored twice (written together). Empty for non-speech sounds
character verified speaker(s) from characters.csv, ; -separated, empty if unknown
characters_inferred characters/roles mentioned by the name (and bank) (hint only)
seconds, rate, channels of the original sound
size size of the original WEM in bytes = maximum size of a replacement (except music packs: any size)
sha1 first 16 hex digits of the original WEM's SHA-1 (detects files changed outside the tool; empty when the copies of a BANK sound differ)
notes free text (empty; for future curation, e.g. transcripts)

BANK_locations.csv.gz columns (gzip-compressed CSV)

column meaning
media Wwise media ID (hex)
file file under BmGame/CookedPCConsole, /-separated (SFX/xxxxxxxx.bnk or Name.upk)
offset position of the WEM in the uncompressed file (hex; docs/formats/upk.md)
size bytes reserved for it there
kind m = copy of a BANK sound, p = copy of (the start of) a WAD speech recording: a prefetch head, or a whole copy with the same media ID
sound p rows: key of that speech recording (e.g. INT:2B151272:0); each language has its own recordings and heads

A replacement is written into every m copy of a BANK sound; replacing a speech line also rewrites its p copies (docs/formats/banks.md).

SUB_<LANG>.csv columns

column meaning
speech the speech event of the line (e.g. Speech_CHURCH_B1_Ch2_POST_Explosion_BM_Cash_04)
line 0-based line of a multi-line subtitle
text original subtitle text
room maximum length of a replacement in characters (it is written in place)
enc a = cp1252 string, w = UTF-16 string
sound key(s) of the linked voice recording(s), ;-separated when the event plays two versions: this language's WAD line or in-memory BANK: line (the INT ones for languages without voices); empty when no event reaches a sound

SUB_locations.csv.gz: lang, speech, line, file, pos (hex, uncompressed offset of the FString), size, enc. Menu text has no catalog: the localization files are read directly (docs/formats/text.md).

The editor uses these files directly when the installed game matches index.json; for another game build it scans the game itself and applies the same rules (no checksums then; the bank scan takes a few minutes). ackit verify --catalog cross-checks the catalog in use against the game files (--quick samples packages).

Rebuilding / adding a language

  1. Make sure the game files are original (ackit status; ackit revert --all).
  2. Optional: add listening-based lists for another language in source/<lang>/<Character>.txt (same format: wemscan names of THAT language's WAD). Each list is resolved against its own language's positions and moved to the lines, so a label then applies to the same recording in every language.
  3. Run python tools/build_catalog.py - it scans every archive and all soundbanks of the installed game (all languages present; ~5 minutes) and rewrites the CSVs, the location indexes, characters.csv and index.json. It stops before writing anything if a sound or a subtitle differs from the current catalog (a modified game); after a game update run it with --accept-changes.
  4. Run ackit verify --catalog (no issues expected). New character labels need a display name in ackit/classify.py: USER_LABELS (otherwise the file name is used).