为什么这个补丁器反人类
0. 这个程序实际做的事
安装器的工作分两段:先校验游戏目录,再把 7 个补丁文件覆盖进去。业务代码集中在 4 个函数里:
WinMain(流程)
sub_401460(消息表)
sub_401780(目录校验)
sub_401BA0(安装)
补丁数据拖在 exe 尾部,从 0x60000 开始,没有压缩、加密或差分
难用的原因都在产品逻辑上:目录要靠手选、安装不可重入、失败不写日志、错误文案与真实原因脱节。下面按影响排序逐条给证据。
1. 问题清单
| # | 问题 | 严重度 | 关键证据 |
|---|---|---|---|
| F1 | 从不自动找游戏目录,浏览框从 C:\ 起步 |
P0 | sub_401780;.c:837 起;初始路径 L"C:\\" |
| F2 | “已打过”就拒绝重跑,无备份无回滚 | P0 | sub_401780 魔数 2 分支;WinMain 静默退出;sub_401BA0 只写不备份 |
| F3 | 错误提示与真实原因无关 | P0 | 摘要不匹配也报 “is not installed in the selected folder”;安装失败只剩 “for some reason” |
| F4 | 版本兼容只查 7 个文件里的 1 个 | P1 | sub_401780 只碰 data01100.arc;安装循环不读旧文件 |
| F5 | 界面文案锁死英文,多语言表没用上 | P1 | .c:220、.c:1640-1641、.c:741-742 |
| F6 | 用互斥体猜进程状态,双开时无提示 | P1 | WinMain 两个 CreateMutex;asm:0x402A38 |
| F7 | 选目录时点“取消”还要再确认一次 | P2 | sub_401780 取消分支;asm:0x401A3D-0x401A4A |
| F8 | 首次安装问 “Do you want to update?”,默认按钮是 No | P2 | WinMain;.c:1673;按钮组合 0x124 |
| F9 | 数据偏移写死 0x60000,找不到就报“安装程序可能损坏” | P2 | .c:1116-1118;文案 0x4114B0 |
| F10 | 677 MB 全量整包覆盖,无差分 | P2 | sub_401BA0 索引循环;文件表见 patch_report.md |
| F11 | 错误无错误码、无日志,只有 OK 按钮 | P2 | .c:768/774(0x40/0x30 图标) |
2. 逐条展开
F1 找目录靠手选,起点还是 C:\
sub_401780(.c:837)对目录的“识别”只有三步:弹 SHBrowseForFolderW,拼 <所选目录>\data01100.arc,检查文件存在并比对摘要。初始路径写死为 C:\:
sub_402CF0(v17, L"C:\\");
导入表里没有任何 Reg* API,也没有 Steam libraryfolders.vdf 相关读取,shell 相关只有 SHBrowseForFolderW 和 SHGetPathFromIDListW。
manifest 还要求 requireAdministrator。于是用户先过 UAC,再从 C:\ 逐级点到 Steam\steamapps\common\...,点错版本还会收到 F3 那条误导文案。
F2 单向状态机:打完就锁死
sub_401780 只认两个摘要:
| 摘要 | 含义 | 结果 |
|---|---|---|
44971AEE617DFCB0 |
原版 data01100.arc |
放行,随后弹 “Do you want to update?” |
84AD2F9C3C3EC4F2 |
补丁后 | “The patch has already been applied.”,返回 0x80000009 |
装完后 data01100.arc 的摘要变成魔数 2,这个 exe 不会再处理任何文件。安装中断后重跑会被拒,残留的 .tmp 和部分覆盖没有入口修复;想装到第二份拷贝或 Steam 换盘后的新路径同样被拒。sub_401BA0 全程只写 .tmp 再 MoveFileExW 覆盖,不产生任何备份文件。
Steam 若把文件校验还原回原版,摘要回到魔数 1,安装器又允许再打。能走的只有“原版到补丁版”这一条路,失败态没有恢复入口。
F3 报错与实际原因对不上
sub_401780 对两种失败给同一句文案:
- 文件不存在,返回
0x80000003,报"'Amairo Chocolate' is not installed in the selected folder."。 - 文件存在但摘要是第三种,比如日文版或别的版本,返回
0x80000008,报同样一句。
版本不对被说成“没装”,用户会反复重选目录。
安装阶段(sub_401BA0)的错误文案更少:.rdata 0x4113F0 的 "Installation cannot be performed for some reason." 和 0x4114B0 的 "The installation program may be corrupted."。没有错误码、没有指出哪个文件、没有日志。
F4 七个文件只校验第一个
安装器覆盖 7 个文件,安装前只检查 data01100.arc 的摘要。sub_401BA0 的安装循环从头到尾不读旧文件内容,写完后用 BGI.hvl 清单核对的摘要来自刚写进去的新内容。
这套校验能发现拷贝损坏,发现不了目标目录是否配套版本。其余 6 个 arc 版本不匹配时,安装器照样覆盖并弹“安装完成”。
F5 文案锁死英文,多语言表形同虚设
消息表确实存在:sub_401460 每个消息 ID 存 4 个指针,按 v5[dword_415018] 取值(.c:741-742),.rdata 0x411AC8、0x411AF0、0x411B18 分别有繁体、简体、英文标题。
dword_415018 在 .c:220 初始化为 2,WinMain(.c:1640-1641)又写了一遍:
GetSystemDefaultLangID(); // 返回值被丢弃
dword_415018 = 2; // 始终选英文槽位
全程序只有这两处赋值,运行时始终为 2。中文用户看不到界面语言切换入口。
F6 用互斥体猜进程状态,双开时无提示
WinMain 创建两个互斥体:
"Updater for AmairoChocolate is executing." // 防自己双开
"Buriko General Interpreter for AmairoChocolate is executing." // 假定游戏在跑时会创建同名互斥体
游戏检测依赖 BGI 引擎使用同名互斥体。引擎改版、名字变化、兼容层启动都会让检测失效;反过来,同名互斥体被无关进程占用时,会误报 "The update cannot be performed because the game or installer is running.",文案自己也分不清是游戏还是安装器。
第一把互斥体创建失败(已有安装器在跑)时,WinMain 直接跳到清理退出(asm:0x402A38 的 jne 0x402C47),连提示框都不弹。第二次双击后程序闪现即退,用户得不到任何反馈。
F7 取消选目录后还要再确认一次
选目录对话框点“取消”,sub_401780 的取消分支(asm:0x401A3D-0x401A4A)先弹一次确认:
v15 = sub_401460(4); // "Cancel the patch installation."
sub_401650(v15);
return -2147483642;
用户已经表达过取消意图,程序再用一个 OK 框要求确认,没有给出任何后续选择。
F8 首次安装问“是否更新”,默认按钮是 No
WinMain(.c:1673)用 0x124(MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2)弹 “Do you want to update?”(0x4119CC)。首次安装的用户还没装过任何版本就要回答“要不要更新”,且默认焦点在 No 上,需要先看懂再手动选 Yes。
F9 数据偏移写死,找不到就甩锅给安装包
sub_401BA0(.c:1116-1118)先要求文件大小不小于 0x60000,再直接 SetFilePointer(393216),不搜索魔数。任何重打包或尾部追加了其它数据的变体都会走到 "The installation program may be corrupted.",用户无法从这行字里知道该检查什么。
F10 全量 677 MB 整包覆盖
7 个文件整体替换,最大的是 data02251.arc,420,991,779 字节。文件按索引顺序处理,前 4 个合计不到 1 MB,瞬间完成,然后停在 421 MB 的文件上。界面有两条进度条:一条 0..0xFFFF 按字节推进(.c:1422),一条 0..7 按文件推进(.c:1305-1306、.c:1469),但没有剩余时间或百分比文字。
失败时残留的 .tmp 只在收尾路径清理,崩溃或断电后会留在游戏目录里。
F11 错误没有代码也没有日志
错误全部走 MessageBoxW,.c:768 用 0x40 图标,.c:774 用 0x30 图标。sub_401BA0 内部返回一串 0x8000000x 风格的值,WinMain 只按区间映射成两三句笼统文案。用户拿不到错误码,开发者也没有日志可查。
3. 它还做对的几件事
以下设计可以保留:
- 先写
.tmp再MoveFileExW(REPLACE_EXISTING)覆盖,单文件替换接近原子操作。 - 安装循环按 1 MB 分块流式读写,没有把 677 MB 一次性载入内存。
- 写完每个文件后用
BGI.hvl清单摘要核对,能挡掉数据截断或损坏。 - 有取消标志(0x416A40/0x416A3C),按块检查,可以中途退出。
4. 重写时逐条消除
| 原版 | 替代做法 |
|---|---|
| 手动选目录、C:\ 起步 | 先查注册表、Steam 库和常见路径,找不到再让用户选,并把浏览框定位到候选目录 |
| 单向状态机 | 幂等处理:原版、已打、半打三种状态都能重跑、校验、修复 |
| 无备份 | 覆盖前备份到 backup/,提供 --restore |
| “for some reason” | 错误码 + 具体文件 + 日志 |
| 锁死英文 | 跟随系统语言或首屏提供语言选项 |
| 整包 677 MB | 格式本身是整包,按文件显示字节进度和剩余时间,跳过摘要已一致的文件 |
| 找不到就报“可能损坏” | 搜索魔数自定位,失败时说明具体原因 |
