资讯动态

给 Sudachi 写存档编辑器:从路径定位到安全回写的完整做法

发布时间:2026/10/24 2:14:57 来源:尧图企业网站定制
给 Sudachi 写存档编辑器从路径定位到安全回写的完整做法【免费下载链接】sudachiSudachi is a Nintendo Switch emulator for Android, Linux, macOS and Windows, written in C项目地址: https://gitcode.com/GitHub_Trending/suda/sudachi给 SudachiC 编写的 Switch 模拟器做一个 SaveData 存档编辑器目标很明确给定一个用户和一款游戏你能定位到它的存档目录、读出容量元数据、把修改安全写回去。Sudachi 的寻址规则集中在SaveDataFactory一个类里读懂它编辑器就完成了一半。全文按先能读 → 再能读对 → 最后能安全改三步推进。准备清单开工前该读的文件src/core/file_sys/savedata_factory.cppGetFullPath、新旧位置判定、大小文件读写都在这一个文件里src/core/file_sys/savedata_factory.h类接口以及.sudachi_save_size文件名常量的定义处src/core/file_sys/fs_save_data_types.hSaveDataSpaceId、SaveDataType枚举SaveDataSize与UserId结构体可选src/core/file_sys/vfs/目录了解GetDirectoryRelative这类句柄获取方式任务一能读——用户存档路径怎么拼出来 要解决什么输入user_id和title_id输出一个能直接打开的目录路径。关键原理路径 空间前缀 类型相关段。三个空间各有固定前缀System 是/system/User 是/user/Temporary 是/temp/。你日常打交道的 User 空间里Account账号档和 Device设备档走同一条拼接分支固定段0 用户 UUID title_id。user_id是 128 位的u128拆成高低两个 64 位各格式化出 16 位十六进制目录名正好 32 位。// out 为空间前缀例如 /user/Account 与 Device 同分支 case SaveDataType::Account: case SaveDataType::Device: return fmt::format({}save/{:016X}/{:016X}{:016X}/{:016X}, out, 0, user_id[1], user_id[0], title_id);其余类型一句话带过Cache 只跟 title_id 走save/cache/title_idTemporary 以 16 位的0开头System 档用save_id定位。动手验证给这个纯字符串函数写单测断言三件事——固定user_id时输出目录名恰好 32 位十六进制title_id传 0 时应被替换成程序自身 ID 再拼接输出以/user/save/0/开头。任务二能读对——新旧两代目录如何避免漏档 要解决什么同一个存档可能躺在旧位置或未来位置你的编辑器必须知道它实际在哪。关键原理GetFullPath走主分支之前会先算一条新格式路径并检查它是否存在。新格式只覆盖 Account 和 Device前者是/user/save/account/uuid/title_id/0后者是/user/save/device/title_id/0uuid 为标准 UUID 字符串。只要新目录存在就直接返回新路径不再回退旧位置。注意匹配前还做了title_id ~(0xFFULL)即把标题 ID 最低字节清零再比较。if (std::string future_path GetFutureSaveDataPath(space, type, title_id ~(0xFFULL), user_id); !future_path.empty()) { if (const auto future_dir dir-GetDirectoryRelative(future_path); future_dir ! nullptr) { return future_path; // 新位置已存在优先使用 } }两代根目录各有现成函数GetUserGameSaveDataRoot(user_id, future)生成future为 false 时是/user/save/0/32 位 UUID为 true 时是/user/save/account/uuid。动手验证构造两个测试目录一个按旧格式、一个按新格式放入同名存档。断言旧格式场景返回旧路径新格式场景返回account/开头的路径两者并存时只认新路径。任务三能安全改——大小文件 .sudachi_save_size 怎么读写 要解决什么改存档前你得知道它声明的容量改完后这份元数据不能失真。关键原理每个存档目录里有一个 16 字节的元数据文件.sudachi_save_size注意是点开头。内容是一个SaveDataSize结构体normal和journal两个u64分别记录常规区与日志区大小。读的逻辑很短文件不存在或不足 16 字节一律按{0, 0}处理够长就直接按内存布局读成结构体。// 读文件缺失或不足 16 字节统一按空值处理 if (size_file nullptr || size_file-GetSize() sizeof(SaveDataSize)) { return {0, 0}; } // 写先 Resize 到 16 字节再整体写结构体 size_file-Resize(sizeof(SaveDataSize)); size_file-WriteObject(new_value);还有一条写入护栏别假设打开即创建。Open时目录不存在是否顺手创建由ShouldSaveDataBeAutomaticallyCreated判定——Cache、Temporary 恒允许User 空间下的 Account/Device 档只在program_id与system_save_data_id均为 0 时允许。System 档和带明确 ID 的存档一律不行。所以你的工具要区分只读浏览与写入两种模式写入前先把.sudachi_save_size复制一份.bak。动手验证跑一次读 → 改 → 回读往返写入一个已知SaveDataSize回读必须逐字段一致。再制造一个 8 字节的坏文件断言读回{0, 0}且不抛异常。最后实测.bak恢复流程能还原原始 16 字节。集成测试可放在src/tests/下用模拟器真实存档跑同一往返。常见问题title_id 为 0 时为什么拼出的路径对不上原因Account/Device 请求里title_id为 0 表示当前进程模拟器会先回填成program_id再拼路径。正确做法编辑器拿到元数据后先做同样的替换别把 0 当合法 ID 拼进目录名。迁移扫描为什么只查旧目录会漏档原因新位置存在时模拟器优先使用新位置旧目录可能早已空掉。正确做法新旧两代根目录都扫任一路径命中即算找到并在界面标注当前代次。大小文件不足 16 字节算不算损坏原因模拟器对缺失或截断一律按{0, 0}兜底它代表未初始化而不是损坏。正确做法读侧照抄这个兜底逻辑写侧在动数据前先留.bak。为什么不能指望打开即创建目录原因自动创建只覆盖 Cache/Temporary以及两个 ID 全为 0 的 User 空间 Account/Device 档System 档与带明确 ID 的存档不在其列误建目录会污染存档树。正确做法浏览模式保持只读确需目录时显式调用Create。多用户存档目录名为什么长得像原因目录名是user_id高 64 位在前、低 64 位在后的 32 位十六进制肉眼难分顺序写反还会指错目录。正确做法展示时转成标准 UUID 字符串存储统一按user_id[1]user_id[0]顺序拼接。交付前自查清单路径拼接单测全绿固定 UUID、title_id0 回填、最低字节清零三组断言都通过新旧两代根目录同时扫描命中集合与模拟器实际读取一致且界面标注了位置代次.sudachi_save_size往返一致写入已知值回读逐字段相同坏文件读回{0, 0}.bak备份与恢复流程实测可还原原始 16 字节浏览模式下 System 档和带 ID 存档不产生任何新目录扩展方向留三个口子云同步可对接src/network/的房间机制加密档可复用xts_archive与 crypto 模块批量迁移按用户 UUID → 设备做模板映射界面栈直接沿用 Sudachi 桌面端的 Qt配QTreeView和现成十六进制控件最省心。存档编辑器做到最后拼的是你对SaveDataFactory寻址规则的还原度双代路径读全、16 字节元数据写准、自动创建查严——顺序反了就得返工。【免费下载链接】sudachiSudachi is a Nintendo Switch emulator for Android, Linux, macOS and Windows, written in C项目地址: https://gitcode.com/GitHub_Trending/suda/sudachi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑