168 lines
16 KiB
Markdown
168 lines
16 KiB
Markdown
# NOTES — Arkham City audio toolkit & editor
|
|
|
|
## Current status / Next steps (rewrite as you go)
|
|
- Mission 1 (2026-10-07): DONE. Format reversed, encoder + injector, verified in-game (no exe patch).
|
|
- Mission 2 Part 1 (2026-10-08): repository cleanup DONE — tag `pre-cleanup` holds the old state.
|
|
New layout (README "Where things are"); `ackit` refactored: portable encoder (libsndfile, no ffmpeg/vgmstream),
|
|
`Game` (Steam/Linux/Proton discovery), catalog from `data/catalog/*.csv` (user's character lists + rules),
|
|
originals store + `Workspace` (apply/revert/conflicts), mod packages, CLI. 63 tests pass; `ackit verify` PASS;
|
|
`ackit verify --ingame` PASS (E14: ncc 1.0, revert byte-identical).
|
|
- Part 2 (2026-10-08): Unheard City DONE (app/, PySide6): browser (search + group/category/character/
|
|
archive/tag/modified/favorites filters, sortable, multi-select, notes/tags/favorites), editor (original/installed/
|
|
replacement waveforms, A/B, loop, volume, file or recording, trim/gain/normalize/match loudness, apply/revert,
|
|
conflict + changed-underneath handling), mod export/import, settings. 72 tests incl. offscreen GUI tests.
|
|
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).
|
|
- Mission 3 (2026-10-09, brief: `docs/history/mission3_audio_text_prompt.md`): full-game audio, then subtitle
|
|
and menu text editing. Plan (one commit per stage; a stage = code + tests + docs + this status):
|
|
1. [x] `ackit/lzo.py`, `ackit/upk.py` (UE3 package summary, compressed chunks, block-level read/patch),
|
|
`ackit/bnk.py` (bank parsing: DIDX/DATA/HIRC sources). Docs: formats/upk.md, banks.md. Findings: pure-Python
|
|
LZO ~47 MB/s (== decompress.exe on every block); 15,800 media IDs in 96,011 copies, 396 are prefetch heads
|
|
of WAD sounds (2,101 copies) -> 15,404 in-memory sounds; copies identical except 1C898731.
|
|
2. [x] Catalog archive `BANK` (15,404 sounds, 6,416 named by events, now 6,456) + `BANK_locations.csv.gz` (93,910 copies,
|
|
2,101 prefetch heads of 396 speech lines); `ackit verify --catalog` (full: catalog OK; fixed 2 duplicate
|
|
names per language: variation shown as NAME#i); categories for bank prefixes.
|
|
3. [x] Workspace writes BANK sounds into every copy and keeps speech-line prefetch heads in sync (bank files
|
|
rebuilt from a mandatory full original copy); editor shows bank + copies. `ackit verify` PASS (300/300
|
|
BANK WEMs rebuilt identically); `ackit verify --ingame --banks` PASS (loaded, no crash, revert identical;
|
|
audible check not possible without input: findings 18).
|
|
4. [x] Subtitles = RDialogueEvent objects in packages (9,016 lines x 11 languages, 168k copies; parser
|
|
`ackit/subtitles.py`, catalog SUB_<LANG>.csv + SUB_locations.csv.gz, 92 % linked to speech sounds via
|
|
events; now ~99 %); menu text = Localization/<LANG>/*.int (`ackit/loctext.py`, ~7,000 strings/language).
|
|
`Workspace.texts` (ackit/texts.py): states, apply/revert, conflicts, mod packages; CLI `ackit text`.
|
|
Subtitles in place (NUL-padded, limit = original length); decision + reasons in decisions.md 4c.
|
|
5. [x] Editor: tabs Sounds / Subtitles / Menu text (app/textpanel.py: search, filters, edit with room
|
|
counter, apply/revert/revert all, find & replace on the filtered rows, go to sound); the sound editor
|
|
shows and edits the sound's subtitle (SubtitleBox); texts in mod export/import and the changed-outside
|
|
banner; smoke test loads texts. 15 GUI tests.
|
|
6. [x] Final pass 2026-10-09: `ackit verify` PASS, `--catalog` OK, `--ingame` PASS (ncc 1.0), `--ingame
|
|
--banks` PASS, `--ingame --texts` PASS (12/12 menu markers in memory), tools/e2e_editor.py PASS; game
|
|
original after. Version 3.0.0; dist/UnheardCity-3.0.0-windows-amd64.zip (86 MB) smoke + GUI start PASS
|
|
with PATH = C:\Windows. 121 tests. REPORT.md rewritten (unresolved items).
|
|
- 2026-10-09 (after user test): in-memory speech is localized (one AkBank export = language list + one bank
|
|
per language; ~1,460 lines per voice language): BANK.csv has a `language` column, the catalog shows shared
|
|
sounds + the loaded language, names unique per language, subtitles of in-memory lines link to them (links
|
|
92 % -> 99 %). Editor: one Language setting at the top (always starts at the game's language; languages
|
|
without voices use INT voices), text tabs follow it.
|
|
- 2026-10-10 (user asked "is it really all in order?"): variation positions differ per language (75 % of lines)
|
|
-> speech sounds named after the game's line (speech event), curated labels mapped through it (characters.csv
|
|
gets a language column), verified 955/955 against dialogue speaker fields; RSubtitle + RSubtitleLookup
|
|
(key = FNV-1 of the line name without Speech_) added; editor redesigned: Listen | Replace | Subtitle columns,
|
|
editor spans the full window width (no scrolling at 1000x700 and up).
|
|
- 2026-10-10 final check: build_catalog refuses to run on a modified game (`--accept-changes` after an update);
|
|
every test tool reverts in `finally`; mods from another language are not matched to position-named lines;
|
|
`tools/e2e_features.py` (8 real-game round trips) PASS; `--ingame --texts` also finds 23/23 Startup.upk
|
|
subtitle markers in memory; `--ingame`, `--banks`, e2e_editor PASS; `verify --catalog` OK; 124 tests; game
|
|
original after. Docs brought up to date.
|
|
- 2026-10-10 (user: "make it complete for every language, fact check everything"): voice lines are identified
|
|
by the game data alone (`ackit/lines.py`, catalog column `line`): per-language BKPK tables (they were merged
|
|
by media ID, and 657 shared IDs let ESN overwrite the others), prefetch sources in the Sound-object map,
|
|
recordings stored twice written together, multi-language in-memory media, subtitle objects naming events.
|
|
12,434 lines, each with exactly one recording in all 5 voice languages; 3,366 speaker labels in every language;
|
|
subtitles link every version of a line; mods move between languages by line identity. `ackit verify
|
|
--catalog` re-derives all relations and prints facts (independent checks: bank language of every source,
|
|
prefetch heads = start of their recording, copies identical, speaker field of the dialogue objects, menu keys
|
|
per language). `tools/e2e_features.py --lang all` 78/78; `--ingame`, `--banks`, `--texts`, e2e_editor PASS;
|
|
127 tests; game original after. Speaker labels vs the dialogue speaker field: 2,766 agree, 19 differ (listed in
|
|
categories.md, for the user to check by listening; the source lists are never edited). The user's guide files
|
|
(`ArkhamCityModding/Audio/AbdAshes' ...`) hold no relation data: the hashlist equals ackit/data/hashlist.txt,
|
|
the scripts only unpack (wemscan = position names). `data/catalog/source/ita/Checkpoint.txt` was found deleted
|
|
(no code touches source/; cause unknown) and restored from git. Version 3.1.0 (minor):
|
|
dist/UnheardCity-3.1.0-windows-amd64.zip smoke PASS. e2e_features now deletes its temporary stores (GBs of
|
|
full copies) when everything reverted. NEXT: UI fixes the user will name.
|
|
- NEXT (needs a person): play a mission with a replaced footstep/foley sound and an edited subtitle (REPORT.md).
|
|
- OPEN: channel order for >2 channels unverified; speech and in-memory sounds *heard* in-game (need gameplay;
|
|
posted keys do not drive the menu).
|
|
|
|
## Environment facts
|
|
- Game: Steam AppID 200260 "Batman: Arkham City GOTY", `C:\Program Files (x86)\Steam\steamapps\common\Batman Arkham
|
|
City GOTY`, exe `Binaries\Win32\BatmanAC.exe` (32-bit, SteamStub). buildid 3718300. Game language: Italian
|
|
(`Documents\WB Games\...\UserEngine.ini` is UTF-16, `Language=Ita`).
|
|
- Steam must be running and logged in for the in-game test (`steam.exe -silent`, wait for
|
|
HKCU\Software\Valve\Steam\ActiveProcess ActiveUser != 0). appmanifest AutoUpdateBehavior=0 (no updates expected).
|
|
- History: the ITA WAD in the game folder was once the user's modded copy; the clean original came from
|
|
`%USERPROFILE%\Desktop\ArkhamCityModding\Audio\49D84887_LOC_ITA\WAD\` (sha1 1238172C...). The game is clean now
|
|
(fingerprints match the old `_backup/`, deleted on 2026-10-08).
|
|
- Dev tools: `.venv` (Python 3.14.6, PySide6 6.10.3, soundfile 0.14.0 / libsndfile 1.2.2, soxr 1.1.0, numpy,
|
|
pytest, pyflakes, PyInstaller 6.22.3, soundcard 0.4.6). git at `C:\Program Files\Git\cmd`. WinDbg Store (path changes on
|
|
update; `ackit/verify/debugger.py: find_cdb`). No WSL distro, no Docker.
|
|
- User data (this PC): `%APPDATA%\UnheardCity\` (settings.json, stores\<id>\), cache `%LOCALAPPDATA%\UnheardCity\cache`.
|
|
- The user's own material: `%USERPROFILE%\Desktop\ArkhamCityModding` (old mod work, Ghidra project, dump);
|
|
their third-party downloads were moved from `tools/`, `repositories/` to `external/` (git-ignored).
|
|
- Ghidra project BAC: `.text` Ghidra address = runtime + 0xC00 (docs use runtime addresses).
|
|
|
|
## Where to look
|
|
- Findings, failed hypotheses, in-game experiment log E0-E14: `docs/reverse-engineering/findings.md`.
|
|
- Formats: `docs/formats/`. Decoder addresses: `docs/reverse-engineering/decoder.md`.
|
|
- Decisions (stack, deps, licenses, originals store, size rule, catalog, verification): `docs/decisions.md`.
|
|
- Voice lines across languages (one recording per language, from the game data): `docs/formats/banks.md`
|
|
(*Lines*), `ackit/lines.py`; checked by `ackit verify --catalog` (facts).
|
|
|
|
## Session log
|
|
- 2026-10-08: user added `my_catalog_for_ita/` (per-character lists, wemscan names) -> moved to
|
|
`data/catalog/source/ita/`; 3364 names labelled (2 wemscan name collisions label both candidates; one line had a
|
|
`.wem.wav` typo). Built catalogs for ITA/INT/FRA/DEU/ESN/SFX (identical names and counts across languages).
|
|
Assumption: speakers are the same in every language, so ITA labels apply to all. (Wrong for multi-variation
|
|
entries, whose WEM order differs per language: since 2026-10-10 labels go through the game's line names.)
|
|
- 2026-10-08: libsndfile's Vorbis writer crashed (C stack overflow) on a 30 s x 6 ch single write -> block writes.
|
|
- 2026-10-08: wad_fingerprint made structural (IDs + non-pack sizes) so music edits don't invalidate the catalog;
|
|
scan mode never borrows checksums from the shipped CSV (other builds / synthetic tests).
|
|
- 2026-10-08: editor async loading looked stuck: Job objects were garbage-collected before their queued result
|
|
signal arrived -> Runner keeps references until done. Qt 6.10 deprecates invalidateFilter/invalidateRowsFilter ->
|
|
SoundFilter.refilter uses begin/endFilterChange when available.
|
|
- 2026-10-08: search haystack must not contain the variation index (":2" matched "2"); IDs are matched separately
|
|
(words of >= 6 chars against the target id).
|
|
- Verification commands: `pytest -q`; `ackit verify`; `ackit verify --catalog`; `python tools/e2e_features.py
|
|
[--lang all]`;
|
|
`python tools/e2e_editor.py`; `ackit verify --ingame [--banks | --texts]` (needs Steam logged in); packaged:
|
|
`dist/UnheardCity/UnheardCity.exe --smoke-test --report f.txt`.
|
|
- 2026-10-08: UI restyle on request: vector icon set (app/icons.py, no emoji), "Arkham" theme (app/theme.py: QSS +
|
|
palette, translucent panels over a wallpaper backdrop).
|
|
- 2026-10-08: user rejected the procedural icon/backdrop and supplied images (~/Downloads/Arkham City:
|
|
bac_logo_full.png, wallpaper.jpg). tools/make_assets.py builds git-ignored app/assets/ (icon = logo bat over a
|
|
red waveform; wallpaper copied); app/art.py, make_icon.py and the old icon files removed. Settings ->
|
|
Background toggle (background_enabled). Character filter is now a plain combobox like the others.
|
|
- 2026-10-08: theme selector and custom background image removed (one look); the wallpaper is painted only in the
|
|
filter panel (theme.Sidebar: scaled to panel height, left part shown).
|
|
- 2026-10-08: build simplified to one offline command: build_app.py runs make_assets when app/assets is missing
|
|
(--art FOLDER), and license texts live in tools/licenses/ (www.gnu.org was down; no more downloads).
|
|
- 2026-10-08: neutral dark theme (no blue), editor header icon removed, new icon: flat BAC bat with echolocation
|
|
arcs. build_app.py also regenerates app/assets when tools/make_assets.py is newer than the icon.
|
|
- 2026-10-08: background strength default 10 %; obsolete settings keys (theme, background_image) dropped on load.
|
|
Spinners (app/spinner.py): waveform overlay while loading, editor action row, status bar for global jobs.
|
|
Replacement files now decode in a background job.
|
|
- 2026-10-08: accent is now dark red (#b0242c; icons.ACCENT / theme.ACCENT, was AMBER). Icon: white rounded tile,
|
|
red waveform bars, black logo bat on top. Filter panel opens at its maximum width (320 px, can only shrink).
|
|
Sidebar veil alpha follows the background strength (at 10 % the wallpaper stays visible).
|
|
- 2026-10-09: editor renamed "Unheard City" (exe/zip UnheardCity, launcher unheard-city, user data %APPDATA%\UnheardCity).
|
|
Icon: 9 wider/taller bars (5 at <=24 px), bat 78% of the tile. Panel background #0c0c0c.
|
|
- 2026-10-09: editor panel opens fitted to its contents (MainWindow.fit_editor; refits on layout changes until
|
|
the divider is dragged). Windows installer: tools/installer (Install.cmd -> install.ps1/uninstall.ps1, per-user,
|
|
HKCU uninstall entry), copied into the build by build_app.py; install/upgrade/uninstall cycle verified.
|
|
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.
|
|
- 2026-10-09 (mission 3): BANK = one row per media ID, written into every copy in place (assumption: the game
|
|
may load any copy; a partial write would be inconsistent). Package writes store changed blocks as LZO literal
|
|
runs (no compressor needed). Bank-file conflicts use the whole-file SHA-1 (the cheap guid+chunk-table
|
|
fingerprint missed same-size edits in tests). Name collisions in the WAD catalogs (2 per language) are shown as
|
|
NAME#i. `soundcard` comes from the `verify` extra (was missing from the venv).
|
|
- 2026-10-09 (mission 3, stage 4): subtitle links by name were wrong for most multi-variation entries (WEMs are
|
|
stored in media-ID order, not _NN order): links come only from events. Numbered event names (Speech_X_04)
|
|
live in export tables, not in name tables: the bank scan now collects export names (+40 named BANK sounds,
|
|
links 71 % -> 92 %). Version*.int (build IDs) are not treated as UI text.
|
|
- 2026-10-10 INCIDENT: a crashed e2e_features run (exception before its revert) left ITA:8108E77E:0 modified,
|
|
and a catalog rebuild then recorded the modified bytes. Recovered: reverted from the run's temporary store,
|
|
ITA WAD sha1 1238172C... again, every other full copy in that store equal to the game, ITA.csv/SUB_ITA.csv
|
|
restored from git and an identical rebuild. Prevention: build_catalog stops when a sound differs from the
|
|
catalog (`changed_since_catalog`), all test tools revert in `finally`, e2e_features writes only ORIGINAL sounds.
|
|
- 2026-10-10: source lists of another language (`source/<lang>/`) are resolved through THAT language's WAD
|
|
positions; ~1,270 speech sounds per language have no game line name (efforts 519, radio/TV 139, cinematics
|
|
134) and keep position names (`Sound.is_position_name`): their labels stay per language and mod packages from
|
|
another language are not matched to them.
|