PC-88 简体补丁制作流程记录
好像全网都没人发过相关内容,也许是独一份(
仅用作个人制作记录。以下为正文内容。
最初,好 homi 推荐了几个 PC-88 的老游戏。我看了一下,感觉还挺有意思,就打算尝试制作。本来以为流程和 PC-98 差不多,结果还是比想象中复杂了一点。(musume_08)
具体思路和现在的 Galgame 补丁制作差不多:
解包 -> 提文本 -> 文本注回 -> 封包
到了 PC-98/88,则变成:
拆碟 -> 提文本 -> 文本注回 -> 封包
除此之外,还需要额外处理字库。
1. 拆碟
一般来说,只要游戏厂商没有自己整一些妙妙东西,使用的都是标准的 D88、NFD、FDI 等格式,自己找工具拆就可以了。
另外,还有一些比较高端的厂商会做出自己的改动,使用自定义轨道布局。这种情况就需要自行分析,elf 的《FOX》就是一个例子。
具体程序相关的内容不在这里展开,只说明整体思路。
2. 文本提取与注回
这里有两个思路。
一种是老老实实地逆文本 OP,计算偏移,然后进行提取和注回。
另一种就是爆提截断。
截断是什么意思?顾名思义,就是原来的文本占用多少字节,注入回去的文本依旧占用多少字节。这样一来,对于源文件而言,就只是替换了内容,并没有改变任何原本的布局。
当然,缺点是有时译文句子过长,最后可能导致话说到一半就没了。
总之,爆提截断就是处理陌生文本格式的最优方案(大雾)。
3. 字库
这是最关键的部分。毕竟前面两步做完以后,最终还是要让游戏能够正常显示文本,所以字库重建是不可缺少的一环。
如果玩过 PC-88 的游戏,应该对 FONT.ROM 和 KANJI1.ROM 比较熟悉。作为模拟器中常见的字库文件,它们承载了许多游戏的文字显示内容。
简单来说,FONT.ROM 是 PC-88 前期一些纯片假名游戏使用的字库,每个槽位大小为 8×8,这也是为什么看上去比较费眼睛。
下图是渲染后的样子:

KANJI1.ROM 则是包含平假名、片假名和汉字的字库,渲染如下:

3.1 KANJI1.ROM 的基本结构
先说相对简单的 KANJI1.ROM 修改。
要修改字库,自然不可能人工用 PS 一个字一个字地改,肯定要交给程序处理。在此之前,先说明几个相关术语。
-
glyph:ROM 文件中的连续字模槽编号。
-
ROM address:PC-8801 字库硬件使用的地址。
-
file byte offset:
KANJI1.ROM文件中的实际位置。
有了这些概念之后,自然就需要对应的计算公式。
先不考虑 JIS 映射,只看一个字在 ROM 中是如何保存的。
一个汉字的单行宽度为 16 像素,即 16 bits。因为 1 Byte = 8 bits,所以每行占用:
16 / 8 = 2 Bytes
在 16×16 点阵中,单字行高为 16,因此单个字模的总字节数为:
16 × 2 = 32 Bytes
32(十进制)= 0x20(十六进制)
字库中的字模是连续存储的。glyph 从 0 开始递增,每个字占用固定的 0x20 字节空间。
假设 y 从 0 开始计数,那么第 y 行之前已经有 y 行,每行占用 2 Bytes,因此:
row_offset = y × 2
再看横坐标 x:
-
0 <= x <= 7:位于当前行的左半边。 -
8 <= x <= 15:位于当前行的右半边。
因此可以得到:
side = floor(x / 8)
= x >> 3
将这些关系组合起来:
offset = glyph × 0x20 + y × 2 + (x >> 3)
复杂的部分位于后续的映射:
JIS code -> ROM address / glyph
上述内容只是在说明:已经知道 glyph 时,如何找到 16×16 字模中的某一个像素。
完整的定位公式为:
base = glyph × 0x20
byte_offset = base + y × 2 + (x >> 3)
mask = 0x80 >> (x & 7)
约束条件为:
0 <= x < 16
0 <= y < 16
3.2 ROM address、glyph 与文件偏移
下面进入映射设计环节。
从给出的 KANJI1.ROM 渲染图中,大致可以划分出两个区域:
-
非汉字区域:标点、数字、拉丁字母等。
-
汉字区域:常用汉字。
我们要修改的就是汉字区域中的一部分。
一个 ROM address 对应一行 16-bit 点阵数据,也就是文件中的两个字节。一个字有 16 行,因此一个完整字模占用连续 16 个 ROM address。
ROM address 和 glyph 的关系为:
ROM address = glyph << 4
glyph = ROM address >> 4
左移四位相当于乘以 16,因为每个字模占用 16 个行地址。
file byte offset 是打开 KANJI1.ROM 后,真正用于数组索引或者文件定位的字节位置。由于一个 ROM address 对应两个文件字节:
file byte offset = ROM address × 2
= ROM address << 1
结合前面的关系:
file byte offset = (glyph << 4) << 1
= glyph << 5
= glyph × 0x20
由此可以得到完整的换算关系:
ROM address = glyph << 4
glyph = ROM address >> 4
file offset = ROM address << 1
file offset = glyph << 5
file offset = glyph × 0x20
3.3 JIS 区点码
JIS X 0208 使用“区、点”描述字符位置:
区号 = JIS 高字节 - 0x20
点号 = JIS 低字节 - 0x20
JIS = ((区号 + 0x20) << 8) | (点号 + 0x20)
例如:
JIS 0x3021
区号 = 0x30 - 0x20 = 16
点号 = 0x21 - 0x20 = 1
所以,0x3021 就是 JIS 第 16 区第 1 点,对应第一水准汉字“亜”。
第一水准汉字位于 JIS 第 16~47 区:
第 16 区:0x3021~0x307E
第 17 区:0x3121~0x317E
...
第 46 区:0x4E21~0x4E7E
第 47 区:0x4F21~0x4F53
前 31 区每区最多有 94 个字符,最后一区有 51 个字符:
31 × 94 + 51 = 2965
第一水准汉字使用以下公式:
rom_address =
((jis & 0x0060) << 9)
| ((jis & 0x1F00) << 1)
| ((jis & 0x001F) << 4)
使用 JIS 0x3021 代入第一水准地址公式,可以得到:
ROM address = 0x6010
换算成 glyph:
glyph = 0x6010 >> 4
= 0x0601
= 1537
换算成文件字节偏移:
file byte offset = 0x6010 << 1
= 0xC020
因此,该字模占用:
ROM address:0x6010~0x601F
glyph index:0x0601
文件范围:0xC020~0xC03F
如果已经知道字模基准 ROM address = A,那么像素 (x, y) 所在的文件字节为:
byte_offset = A × 2 + y × 2 + (x >> 3)
bit_mask = 0x80 >> (x & 7)
例如:
A = 0x6010
x = 12
y = 5
计算字模起点:
字模起点 = 0x6010 × 2
= 0xC020
计算行内偏移:
行内偏移 = 5 × 2
= 0x0A
计算左右半边:
左右半边 = 12 >> 3
= 1
得到文件字节位置:
byte_offset = 0xC020 + 0x0A + 1
= 0xC02B
计算像素对应的 bit:
bit_mask = 0x80 >> (12 & 7)
= 0x80 >> 4
= 0x08
所以,该像素位于文件字节 0xC02B 的 0x08 位。
3.4 动态映射
了解这些计算之后,就可以正式进入动态映射的设计。
区别于现在常用的日繁固定映射表,PC-88 更多时候可以选择动态映射。
何为动态?顾名思义,就是不硬性规定一个固定映射关系,例如“你 -> 凛”,而是选择借用一个游戏能够显示、但当前文本没有使用的日文编码槽。然后,将这个槽中的日文字模替换成中文字模,同时把译文中的中文字符编码成该槽原来的 CP932 双字节编码。
例如,假设中文“简”无法用 CP932 表示,但找到了一个当前文本未使用的日文载体字符“鰯”。
首先进行以下换算:
载体字符“鰯”
-> CP932
-> JIS code
-> ROM address
文本注入时,游戏得到的是“鰯”对应的 CP932 编码。游戏随后按照正常流程计算“鰯”对应的 ROM address,并从 KANJI1.ROM 中读取该槽位的字形。
但是,由于我们已经将这个槽位中的字模重绘成了“简”,所以游戏最终显示出来的就是“简”。
对于那种纯片假地狱的 PC-88 游戏,处理起来就比较麻烦了。因为那些游戏制作时还没有 KANJI1.ROM 镜像,程序自然也不会有主动读取 KANJI1.ROM 的对应逻辑。此外,还涉及从原本的 8×8 点阵片假名转换成 16×16 汉字,相当麻烦,所以这里不做细致展开。具体分析做起来和新做一个游戏没什么区别,感觉不如直接移植()。
示例
PC-88《トリロジー 九鬼妖華真伝》



