用 Qwen-TTS 给《逆转裁判123》做了一个日语全语音 MOD
大部分工作是AI自己完成的,这里讲几个人类做的事情:
运行时框架
MOD 使用MelonLoader + Harmony。
MelonLoader 负责把 M OD DLL 加载进 Unity 游戏;Harmony 负责在不修改游戏原始 DLL 的前提下,对游戏方法做运行时 Hook。简单来说:
游戏启动 → MelonLoader 加载 MOD → Harmony Hook 游戏对白逻辑 → MOD 根据当前对白找到对应音频 → 播放本地 WAV
MDT 剧本解密
游戏的剧情文字存放在 MDT 文件中,例如:
PWAAT_Data\StreamingAssets\GS1\scenario
MDT 是加密后的二进制剧情脚本,里面同时包含台词、换行、翻页、角色名牌、跳转和自动推进等控制信息。
给 Code Agent 挂上 IDA Pro MCP,让它逆向Assembly-CSharp.dll后,大致还原出了游戏自己的解密流程:
固定口令 + 固定 salt
→ PBKDF2-HMAC-SHA1(1000 次)
→ 得到 32 字节结果
├─ 前 16 字节:AES-128 密钥
└─ 后 16 字节:CBC IV
→ AES-CBC 解密
→ PKCS#7 去填充
→ 解析 MDT 二进制脚本
这些参数只和游戏版本有关,与系统指纹和 Steam 账号无关。
对白和语音如何对应
主要 Hook 点是:
MessageSystem.SetCharacter
这里处在游戏逐字显示对白的过程中,上下文里可以拿到当前作品、章节脚本、消息编号、页号和角色 ID。
于是每页对白可以被编码成一个稳定的 cue,例如:
gs1.ep01.sc0.m0001.p001
含义大致是:
GS1 / 第一章 / sc0 剧本 / 第 1 条消息 / 第 1 页
离线提取 MDT 时,也按同一套规则给每页日文台词生成 cue。运行时 MOD 根据游戏状态计算 cue,在索引中查找对应音频:
gs1.ep01.sc0.m0001.p001 → audio/_assets/0d/0dc338….wav
这里 cue 是剧情位置,音频文件则按内容哈希保存。相同的文本、音色和模型参数只合成一次,多个剧情位置可以复用同一个 WAV。
TTS、音色和读音
语音合成使用 Qwen-TTS,非软广,主要原因是预制音色足够多,音色列表见:
Qwen-TTS 音色列表 (https://help.aliyun.com/zh/model-studio/qwen-tts-voice-list)
生成前做了不少打样,至少保证主要角色不那么 OOC。排除了Bella和Dylan这两个说日语效果非常爆炸的音色。生成语音总费用大约是 200 元人民币。
我去,不早说。
另一个问题是,游戏数据里的人名往往只有汉字。日语里同一个写法可能有音读、训读、特殊读法,直接让 TTS 猜会导致同一个人前后读法不一致。
所以在请求 TTS 前,会把人工核对过的人名、别名统一替换为片假名读音:
原文日语台词 → 人名/别名读音归一化 → 片假名 TTS 文本 → Qwen-TTS → WAV + 元数据 + 内容哈希