Skip to content

状态: 🟢 已修复

日期: 2026-07-17 严重程度: 🔴 P1 影响范围: frontend/src/model-loader.ts, frontend/src/vmd-loader.ts, frontend/src/audio.ts, frontend/src/props.ts, frontend/src/outfit-overlay.ts, frontend/src/vmd-layers.ts, internal/app/发现方式: 开发发现(ADR-124 迁移中) 修复方案: 5 项修复——[]byte base64 解码 / 新增 ListDirRecursive / ImportMeshAsync 语义修正 / 新增 FileExists / resource_root 隔离

ADR-124 文件系统迁移踩坑记录

日期:2026-07-17 关联:ADR-124(HTTP 中转 → ArrayBuffer 直传) 状态:已修复


Bug 1:Wails v3 []byte 序列化为 base64 字符串

现象ReadFileBytes 返回的不是 Uint8Array,直接传给 ImportMeshAsync 后二进制数据被 .toString() 后当作 URL 路径请求,报 net::ERR_FAILED

根因:Go []byte → JSON 序列化为 base64 字符串 → response.json() 返回 string。Go 注释 "automatically maps []byte to Uint8Array" 与实际行为不符。

修复:所有 ReadFileBytes 调用点需 atob 解码:

typescript
function decodeBase64(b64: string): Uint8Array {
    const bin = atob(b64);
    const bytes = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
    return bytes;
}

影响范围model-loader.tsvmd-loader.ts(3处)、audio.tsprops.tsoutfit-overlay.tsvmd-layers.ts(3处)


Bug 2:ListDir 仅返回顶层文件名

现象:PMX 纹理在子目录(如 textures/body.png)中,ListDir 只返回 body.png,与 PMX 内部引用的 textures/body.png 不匹配,材质全白。

根因ListDiros.ReadDir 只遍历一层,不递归。

修复:新增 ListDirRecursivefilepath.WalkDir),返回 {name, relativePath} 结构体,TS 侧 collectTextureFiles 使用。


Bug 3:ImportMeshAsync 第一个参数语义

现象ImportMeshAsync(data, scene, opts) 的第一个参数是 meshNames(网格名过滤器),不是文件数据。Uint8Array 被强转后当 URL 使用。

根因:babylon-mmd 的 ImportMeshAsync 实际接受 ArrayBufferView 作为第一个参数(绕过类型检查需 as any),但标准 Babylon.js SceneLoader.ImportMeshAsync 不支持此用法。

修复(ImportMeshAsync as any)(pmxBytes, scene, { pluginExtension: '.pmx', ... })


Bug 4:Outfit 纹理探测用 ReadFileBytes 极慢

现象outfit.ts HEAD 探测改为 ReadFileBytes 后,每探一个纹理都读整个文件,大纹理文件导致扫描超时。

根因ReadFileBytes 读全部内容,不适合做存在性检查。

修复:新增 FileExists(path) Go binding(仅 os.Stat),探测改用 FileExists


Bug 5:resource_root 配置污染

现象:广场下载时临时目录路径被写入 config.jsonresource_root,导致 ScanModelDir 扫描错误目录,音乐库和道具列表为空。

根因resource_root 被持久化为 TestDownloadFromPlaza_SizeLimit3748633684/001。之前的 HTTP 路径通过 IsolateModelDir 做了安全隔离,绕开了错误路径。直读文件系统后路径必须准确。

修复:设置里手动把 resource_root 改回正确路径。

教训resource_root 写入前应校验目录存在性,避免临时路径污染。


设计决策

决策原因
纹理保留 HTTP 加载referenceFilesrelativePath 需与 PMX 内部引用(按 header encoding UTF-8/UTF-16)精确匹配;磁盘文件名可能因 ZIP 解压为 Shift-JIS/GBK 字节而不匹配,递归扫描 + basename fallback 成本高
resolveModelDir 独立于 resolveFileUrlresolveFileUrl 启动 HTTP 服务器有副作用,仅需目录路径时不应触发
FileExists 独立于 ReadFileBytes存在性检查不应读全部文件内容