Edit subtitles and menu text
This commit is contained in:
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