Version 3.1.0: voice lines linked across all languages from the game data

This commit is contained in:
2026-10-10 14:10:03 +02:00
parent ea3e44000a
commit fbde809370
56 changed files with 74728 additions and 73360 deletions

View File

@@ -7,10 +7,12 @@ Survey of the installed game (buildid 3718300), 2026-10-09.
## 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 |
| where | banks | unique in-memory WEMs |
|-------|-------|-----------------------|
| loose `SFX\*.bnk` (Batman/Catwoman footsteps, 32 surfaces each, + `TST_Prep_Media`) | 65 | 1,117 |
| banks embedded in `.upk` packages (in 1,432 of the 2,650 packages) | 3,340 | 15,641 (~276 MB) |
All of them are in the catalog (archive `BANK`).
The two sets share 119 media IDs: 15,800 unique media IDs in 96,011 places (bank copies). 396 of them are
**prefetch heads** of streamed WAD sounds (below), so there are **15,404 in-memory sounds**. All copies of a
@@ -52,11 +54,37 @@ WAD sounds have such heads (2,101 copies); replacing one of them must rewrite th
## Localized banks
Speech banks are localized inside the (language-neutral) packages: one `AkBank` export starts with a language
list (FStrings `INT FRA ITA DEU ESN`) and then holds one complete bank per language, in that order, all with
the same bank ID; each language's copy has its own media IDs (checked on 215 banks against the WAD languages
of their streamed lines). ackit labels every in-memory media with its language (`BANK.csv` column `language`):
8,098 language-neutral sounds, ~1,460 per voice language. A catalog shows the neutral sounds plus the loaded
language's lines; names are unique per language.
list (FStrings `INT FRA ITA DEU ESN`) and then holds one block per language, in that order: the BKPK table(s)
of the speech WAD entry for that language (`wad.md`) and a complete bank, with the same bank ID in every
language. ackit gives each bank and each BKPK table the language of its position in that list (an export
whose block count differs from its language count would be reported; none does). Each in-memory media gets
the languages of the banks that hold it (`BANK.csv` column `language`): 8,098 sounds are language-neutral,
~1,460 belong to one voice language, 6 to several (a DLC intro line shared by INT/FRA/ITA/ESN while DEU has
its own); a media stored in the banks of every language is listed as `''` (the same recording everywhere).
A catalog shows the neutral sounds plus the loaded language's lines; names are unique per language.
## Lines: one recording per language
Implemented in `ackit/lines.py`, checked by `ackit verify --catalog`. A voice line exists once per language,
and the game ties the recordings together itself:
- Every WEM of a speech WAD has exactly one row in its own language's BKPK table (media ID, offset, size):
10,816 of 10,816 in each of the 5 languages, no row without a WEM. 657 media IDs are used by several
languages (cinematics, emotes, Clayface), each at another position in each language's WAD, so the tables
are kept per language and never merged.
- Every recording, streamed, prefetched or in memory, is the source of a Wwise Sound object; Sound objects,
events, actions and containers have the same IDs in every language, only the media differ. A *line* = the
recordings that are sources of the same Sound objects: 12,434 lines (WAD and in-memory), each with exactly
one recording in each of the 5 voice languages (61,510 recordings), no Sound object with two recordings in
one language. Line identity (`line` column) = the smallest Sound object ID.
- Prefetched sources count too: the 396 lines with a prefetch head (mostly the game-over taunts) are linked
only through them.
- 5 recordings per language are stored twice in that language's WAD (same media ID in two entries, byte-
identical, e.g. `Speech_STEEL_A1_CH2_Thug_behinddoormuffled` and `DIA_SteelA1Muffled`): the second place
is a copy of the line (same `line`, keeps its position name) and is written and reverted with it, because
the game plays whichever place the loaded package's table names.
- Independent checks (no BKPK involved): every WAD recording is named as a source by a Sound object of a bank
of its own language (54,055 of 54,055); every stored prefetch head equals the start of the recording its
line gives it (2,101 of 2,101); a line's recordings have similar lengths across languages (11,761 of 12,434
within a factor 1.5; the others are different edits, e.g. a 0.55 s ESN line vs 5.46 s elsewhere).
## Banks inside packages
- The packages are LZO-compressed UE3 packages (`upk.md`); ackit decompresses them itself and searches the