文章 15
评论 3
浏览 65386
PS1 月下夜想曲 Best 版本研究(五):改造工具自己的八个坑

PS1 月下夜想曲 Best 版本研究(五):改造工具自己的八个坑

前情

为了让《月下夜想曲》的改造工具能处理日版,我们做了不少「改造之外」的活:它自己先崩了好几次。

这篇把踩过的坑按类型理一遍。这些问题跟版本无关,写任何游戏编辑器都可能撞上,所以单独记一篇。

一、显存泄漏:打开几张地图就崩

症状:连续打开几张地图之后程序直接崩溃,没有任何报错。

原因:Map::Cleanup() 只清了普通纹理,漏掉了每个房间的 8 张 texture page。一张地图泄漏几 MB 显存,开几张就把显卡那口气耗完了。

修复:补上删除逻辑,同时把 map_vram、expanded_map_vram、entity_cluts_texture、framebuffer 一起释放。

教训:只要对象是按房间生命周期创建的,「销毁」路径必须和新创建的东西一一对应。漏一个就是慢性泄漏,很难当场发现。

二、死循环:CPU 几乎为 0,界面不动

症状:加载地图时卡在「整理实体图形」这一步,进度条不动;打开任务管理器一看,CPU 几乎为 0——不像在算,更像在等。

原因:处理多边形时,如果贴图页不存在就 continue。但continue 之前没有推进 polygon_addr,于是同一个多边形被反复处理,永远出不来。触发它的是 CAT 地图的 obj 0x032。

这种 bug 特别阴——CPU 不高是因为没有计算量,只是原地打转。

修复三件事:

  1. 三个 continue 分支都先推进链表指针再 continue;
  2. 加 512 个多边形的处理上限;
  3. 加 tag 自指检测,防止循环链表。

教训:用 continue / break 跳过元素时,先想清楚「推进」这一步有没有被跳过。

三、中文显示成问号:字体选错了

症状:界面里的「查」「载」「图」「实」这些字显示成 ?。

原因有两层:

  1. 只加载了 GetGlyphRangesJapanese(日文字形范围),简体字根本不在里面;
  2. 字体优先选了 MS Gothic——那是日文字体,没有简体字形。

修复:

  • 用 ImFontGlyphRangesBuilder 组合「中文常用字 + 日文 + 所有界面翻译串」;
  • 主字体改成微软雅黑;
  • 再把 MS Gothic 以 MergeMode 合并进来,补充假名和日文汉字。

教训:中日文混排时,字形覆盖范围要按「实际会出现哪些字符」来算,不能拿一个语言的范围凑合。

四、界面空白:窗口 ID 被翻译搞没了

症状:地图加载完,主界面空白,只剩一个 VRAM 查看器。

原因很微妙。ImGui 的窗口 ID 是根据窗口标题算出来的。我们把窗口名用 tr() 翻译成中文之后,ID 变了,而 DockBuilderDockWindow 里写的还是英文原名,于是停靠失败,窗口变成一个 124x54 的浮动小窗。

修复:停靠用的窗口标题保持英文原名(Main Viewport / Properties / VRAM Viewer / Main DockSpace),只翻译窗口里的内容。

教训:ImGui 里「显示用的名字」和「身份用的名字」是同一个东西时,翻译就会改身份。要么用 ### 分隔显示名和 ID,要么干脆别翻译停靠标题。

五、加载时控制台把程序卡死

症状:从命令行启动时,加载到一半整个程序不动。

原因:日志写 stdout。Windows 控制台一旦进入「选择文本」模式(在窗口里拖了一下鼠标),所有写入都会被阻塞,加载线程随之卡住。

修复:日志默认写 SotN_Editor.log,不再阻塞;需要控制台输出时设环境变量 SOTN_LOG_CONSOLE=1。

教训:不要用 stdout 当日志通道,尤其在有线程和长任务的时候。

六、中文路径一选就闪退

症状:在文件对话框里选中 SLPM_860.23(放在中文目录下)立刻闪退。

原因:文件对话框返回的是 UTF-8 路径,而 fopen() 按系统 ANSI 代码页解释,中文目录名打不开,返回 NULL,紧接着 fseek(NULL) 就崩了。

修复:新增 Text::fopen_utf8(),先按 UTF-8、失败再按 ANSI 用 _wfopen 打开;并且给所有文件打开补上失败判断。

顺带两个相关修复:

  • 文件过滤器原来只写了 exe,EXE,67,日版既没扩展名也不是 67,所以对话框里根本看不到它。改成 67,23,exe,EXE,再加一个「所有文件」,选中后校验 PS-X EXE 文件头。
  • 命令行参数里的中文路径失效:argv 是 ANSI,程序内部统一 UTF-8,于是加了 ansi_to_utf8 在启动时转换。

教训:Windows 上「路径是 UTF-8 还是 ANSI」这件事,必须在一开始就定死。

七、第二次启动不加载数据

症状:第一次启动正常,关掉再开,数据不自动加载了。

原因:原来的自动加载依赖 ImGui::IsWindowAppearing()。窗口布局从 imgui.ini 恢复之后,这个值是 false。

修复:改成只判断「三个路径都已就绪且尚未初始化」。

教训:别把业务逻辑挂在 UI 框架的「首次出现」语义上,那个语义只在特定条件下成立。

八、加载线程里的异常直接杀进程

症状:打开地图偶发闪退。

原因:用 std::filesystem::path 拿 ANSI 字符串去构造 UTF-8 会抛异常,而这段代码在加载线程里,异常没人接,进程直接终止。

修复:统一改用 std::filesystem::u8path(),并给加载线程加了 try/catch,出错时把信息显示到界面。

附:几个排查用的环境变量

变量作用
SOTN_LOG_CONSOLE=1日志同时输出到控制台
SOTN_LOG_DEBUG=1打开调试日志
SOTN_SYNC_LOAD=1单线程加载,便于定位线程问题
SOTN_NO_BUFFER_WINDOW=1复用主窗口 GL 上下文,排查显卡驱动问题

小结

这八类问题里,真正难的只有两类:死循环(因为它伪装成「卡住」而不是「出错」)和 ImGui 的窗口 ID(因为它牵涉到框架的内部机制)。

其余大多是「没做对应关系」——创建了没销毁、打开失败没判断、路径编码没统一。

写工具的经验大概就是:把失败路径当成主路径来写。


系列文章


标题:PS1 月下夜想曲 Best 版本研究(五):改造工具自己的八个坑
作者:Miaocat
地址:https://small7g.cn/articles/2026/11/04/1793783140885.html

Lazy Cat Days. 什么都有,又好像什么都没有.

取消