Edit subtitles and menu text
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
| [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/subtitles.md](formats/subtitles.md) | subtitles: dialogue objects in the packages, link to the speech sounds |
|
||||
| [formats/text.md](formats/text.md) | menu and UI text: the localization files |
|
||||
| [formats/upk.md](formats/upk.md) | UE3 packages: summary, LZO chunks, how ackit reads and patches them |
|
||||
| [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 |
|
||||
|
||||
@@ -79,6 +79,19 @@ tables): high risk for little gain, not done.
|
||||
packages' name tables (FNV-1 of the name = event ID), following event -> action -> containers -> sounds
|
||||
(6,416 sounds); the rest are numbered per bank. Categories use the bank name.
|
||||
|
||||
## 4c. Subtitles and menu text
|
||||
- **Subtitles** are written in place into every copy of the dialogue object, NUL-padded inside the original
|
||||
string (the game stops at the first NUL), so nothing in the package moves; the limit is the original
|
||||
string's length ("room"). Moving the object to the end of the package (new export offset/size and an extra
|
||||
chunk) would lift the limit, but no dialogue is loaded on the title screen, so the result could not be
|
||||
checked automatically in the game: not done.
|
||||
- **Links** subtitle -> speech sound come from the soundbanks (event -> ... -> per-language media -> WAD), not
|
||||
from names: the `_NN` suffixes do not follow the WEM order inside WAD entries.
|
||||
- **Menu text** files are plain UTF-16 text: no size limit; only the edited values change.
|
||||
- Both use the originals store like sounds: texts are recorded as strings (`texts` in store.json) and every
|
||||
file is rebuilt from its original copy + the applied edits (sound and text edits combine in one package).
|
||||
- Keys (`SUB:<LANG>:<speech>:<line>`, `MENU:<LANG>:<File>:<Section>:<Key>`) are stable and used in mod packages.
|
||||
|
||||
## 5. Catalog
|
||||
- Shipped CSVs per archive (names, categories, characters, original size + checksum) make loading instant and
|
||||
enable change detection; any other build is scanned at runtime (header reads only, cached).
|
||||
|
||||
46
docs/formats/subtitles.md
Normal file
46
docs/formats/subtitles.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Subtitles (`RDialogueEvent` objects in the packages)
|
||||
|
||||
There are no subtitle files: every speech line has a dialogue object (class `RDialogueEvent`, Rocksteady) inside
|
||||
the UE3 packages (`upk.md`), holding the subtitle text of all 11 languages. ackit reads them with
|
||||
`ackit/subtitles.py` and writes them through `Workspace.texts` (`ackit/texts.py`).
|
||||
|
||||
## Inventory (buildid 3718300)
|
||||
- 14,743 dialogue objects in the packages for 9,016 speech lines (a line's object is copied into every package
|
||||
that uses it; all copies are identical).
|
||||
- 11 languages: INT, FRA, ITA, DEU, ESN, ESM, POL, POR, RUS, JPN, KOR (the speech WADs exist only for INT, FRA,
|
||||
ITA, DEU, ESN; the other languages play the INT voices with their own subtitles).
|
||||
- ~9,400 subtitle strings per language: 94 % of the subtitles have one line, the rest up to 5 lines.
|
||||
- Strings are stored as cp1252 (positive FString length) or UTF-16 (negative length): RUS, POL, JPN, KOR
|
||||
and some INT/ESN/FRA/DEU strings are UTF-16.
|
||||
|
||||
## Object layout (compact tagged properties)
|
||||
The export data is a list of properties `u16 type, u16 field offset, value`, ended by `u16 0`. Types seen:
|
||||
`0x10` object reference (i32 export/import index), `0x0D` FString, `0x09` struct/array (name i32+i32 = struct
|
||||
or property name from the name table, i32 byte size, i32 array index, then `size` bytes), `0x04` 4-byte value,
|
||||
`0x03` bool, `0x01` enum. Relevant properties:
|
||||
|
||||
| field | content |
|
||||
|-------|---------|
|
||||
| `0x10` @ `0x2C` | the speech event this line plays, e.g. export `Speech_CHURCH_B1_Ch2_POST_Explosion_BM_Cash_04` |
|
||||
| `0x0D` @ `0x34` | speaker, e.g. `Batman` |
|
||||
| `0x09 LocalizedSubtitles` | u32 count, then per language: `0D 00 00 00` FString language code, `09 00 0C 00 Subtitles` struct, `00 00` |
|
||||
| `Subtitles` | u32 line count, then per line: `0D 00 00 00` FString text, `04 00 0C 00` 4-byte value, `00 00` |
|
||||
|
||||
The sizes add up exactly in all 14,743 objects (checked by the parser: `SubtitleError` otherwise).
|
||||
|
||||
## Link to the speech sound
|
||||
The speech event name is hashed (FNV-1) to the Wwise event ID, followed through the soundbanks (event ->
|
||||
action -> containers -> Sound) to the streamed media, and that media ID is looked up in the per-language
|
||||
BKPK tables (`banks.md`) to find the WAD entry and variation of each language. This is exact; the `_NN`
|
||||
suffix of the event name does **not** match the variation order inside the WAD entry (WEMs are stored in
|
||||
media-ID order). Linked: ~92 % of the strings (e.g. INT 8,637 / 9,379); the rest have no event that reaches a
|
||||
streamed media (unused or placeholder lines such as `..._TEMP_29`) and stay editable without a sound link.
|
||||
Event names with a number (`Speech_X_04`) exist only as export names, not in the name table.
|
||||
|
||||
## How ackit writes them
|
||||
- In place, into **every copy** (`data/catalog/SUB_locations.csv.gz`): the new text replaces the FString's
|
||||
bytes and is padded with NUL characters, keeping the length field. The game stops at the first NUL, so no
|
||||
size field, export or package layout changes. Limit: the new text must fit the original string's bytes
|
||||
(cp1252: one byte per character; UTF-16: two) - shown as "room" in the CLI and the editor.
|
||||
- A cp1252 string can only hold cp1252 characters; UTF-16 strings can hold any character.
|
||||
- Packages are rebuilt from the store's original copy + all applied sound and text edits (`docs/decisions.md`).
|
||||
19
docs/formats/text.md
Normal file
19
docs/formats/text.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Menu and UI text (`BmGame\Localization\<LANG>\*.<lang>`)
|
||||
|
||||
The menus, HUD, character bios, riddles, credits and other UI strings are UE3 localization files, one folder
|
||||
per language (INT, ITA, FRA, DEU, ESN, ESM, POL, POR, RUS, JPN, KOR), ~37 files and ~7,000 strings each
|
||||
(`FRONTEND`, `GFxUI`, `Bios`, `Credits`, `PC`, `RIDDLER`, ...). ackit reads and writes them with
|
||||
`ackit/loctext.py` through `Workspace.texts`.
|
||||
|
||||
## Format
|
||||
- UTF-16LE with a BOM (`FF FE`), CRLF line ends; the first line is empty. (`INT\Version.int` and
|
||||
`VersionData.int` are plain ASCII build IDs, not UI text: ackit ignores them.)
|
||||
- `[Section]` lines, `Key=Value` lines, `;` comments. A few values are in double quotes (`Quit="Quit game"`).
|
||||
- File extension = language code (`FRONTEND.int`, `FRONTEND.ITA`); case varies, lookups are case-insensitive.
|
||||
- Markers inside values (e.g. `\n`, button tags) are the game's own; ackit keeps them as plain text.
|
||||
|
||||
## Keys and writing
|
||||
- Key: `MENU:<LANG>:<File>:<Section>:<Key>` (`#n` for a repeated key; none in the shipped files).
|
||||
- Writing replaces only the value part of the edited lines (quotes kept); BOM, encoding, other lines and line
|
||||
ends stay byte-identical. Values cannot contain line breaks; characters must fit the file's encoding.
|
||||
- The files are rebuilt from the store's original copy + the applied edits, so reverting restores them exactly.
|
||||
Reference in New Issue
Block a user