资讯动态

OpenSC2K SCURK 项目文件格式深度解析:ZIP 容器、图层文档、检查点与安全保存机制

发布时间:2026/10/13 12:36:34 来源:尧图企业网站定制
游戏开发【免费下载链接】OpenSC2KOpenSC2K - Open Source remake of Sim City 2000 using rust and godot. Available for macOS, Windows and Linux项目地址https://gitcode.com/gh_mirrors/op/OpenSC2K点击查看免费下载本篇技术指南完整解析 OpenSC2K 内置像素美术编辑器 SCURK 的项目文件.scurk格式覆盖其 ZIP 容器结构、JSON 清单与调色板协议、索引 PNG 与透明遮罩编码、文档/图层/图章/资源/检查点的数据模型、全套硬性限制以及从原子化保存到 30 秒自动恢复的持久化流程。读者看完后将能够用标准 ZIP 工具直接检视.scurk文件内部结构理解其与 MIF 图块集的边界关系并掌握该格式的校验规则与安全写盘原理。背景为什么 SCURK 需要一种新格式OpenSC2K 的 SCURKSimCity Urban Renewal Kit编辑器用于绘制替换游戏原版美术的图块。其底层载体是 MIF 图块集格式对应 game/src/assets/scurk_mif.gd其字节编解码在 native/core/assets/src/scurk/mif.rs。MIF 只能表达最终成品的位图与名称数据无法承载编辑器工作状态例如每个图块的多层图层layer可复用图章stamp编辑过程中的检查点checkpoint。SCURK 项目文件.scurk扩展名专门用于持久化这些编辑器数据。它的核心设计原则是项目文件是编辑器的工作格式不是游戏可直接消费的格式。要将美术用于游戏必须通过File Export Tile Set导出扁平化的 MIF 文件而File Save与File Save As只写项目文件不会顺带生成 MIF。项目文件是一个 ZIP 容器内部混合了 JSON 元数据、MIF 原始字节、索引 PNG 图像与资源字节但读取方永远不会把成员解压到文件系统见 game/src/tools/scurk/scurk_zip.gd 的类注释。容器结构ZIP 内的命名约定项目文件是标准 ZIP 归档任何标准 ZIP 工具都可以列出并解压其成员。文档给出了一个典型布局project.json palette.json original.mif current/current.mif current/documents/0000/original.png current/documents/0000/layers/0000.png current/documents/0000/layers/0001.png current/documents/0000/layers/0001.mask.png current/stamps/0000.png current/resources/0000.bin checkpoints/0000/current.mif checkpoints/0000/documents/0000/original.png checkpoints/0000/documents/0000/layers/0000.png一个容易忽视的关键设计是成员路径一律使用数字序号而非逻辑名称规则如下文档目录current/documents/0000等按排序后的文档键顺序编号图层与图章图像按各自数组顺序编号资源成员按排序后的资源名顺序编号成员扩展名沿用资源名扩展名的前提是扩展名长度不超过 10 个字符且是合法标识符否则统一为.bin检查点目录checkpoints/0000等按检查点数组顺序编号。由于逻辑名可能包含斜杠、Unicode 字符甚至..例如测试里就使用了../palette:雪这样的文档键写入器绝不会把逻辑名直接当作成员路径。这在 game/tests/scurk_project_archive_test.gd 中被明确断言Logical names do not become archive paths。实现上native/core/assets/src/scurk/project/archive.rs 的snapshot()会对文档键与资源名分别keys.sort()后再按序号生成路径从而保证成员命名的确定性。读取器在以下任一条件下拒绝整个归档存在未被元数据引用的成员被引用的成员缺失清单信封manifest envelope、调色板记录或像素描述符pixel descriptor含不支持的字段。对应地Rust 解码端用HashSet追踪used成员扫描结束后若有成员未进入used集合即报The project archive contains an unreferenced member测试_test_invalid中Unreferenced archive members are rejected正是验证这一点。清单Manifestproject.jsonproject.json是单个 JSON 对象字段严格固定字段值formatopensc2k-scurkversion2palettepalette.jsonproject逻辑项目记录见下文 Project record信封把项目字段与归档字段分离即使项目记录内部有名为format或palette的字段也会留在project对象里不会污染顶层。测试_test_members验证了这一点manifest.project.format future project field。GDScript 侧的常量定义在 game/src/tools/scurk/scurk_project_archive.gdFORMAT opensc2k-scurk、VERSION 2、MANIFEST project.json、PALETTE palette.json。解码端会严格检查version必须精确等于2测试覆盖了0、1、3、2.5、2、null等非法值并拒绝format不匹配或含未知顶层字段的清单。调色板rgb 与 index-encoding 两种模式palette.json同样是字段严格固定的单个 JSON 对象字段值kindrgb或index-encodingcolors长度为 256 的颜色数组每个颜色是[红, 绿, 蓝]三个 0–255 的整数kind: rgb表示项目携带真实颜色。编辑器加载项目时使用这些颜色即使当前配置的游戏调色板不同也照用。测试_palette_bytes()构造了每通道独立变化的 256 色并断言All palette RGB bytes survive。无真实颜色的项目使用kind: index-encoding其 256 色必须是灰阶颜色n恒为[n, n, n]。这样每个索引在画面上显示为它的灰度值编辑器改用配置的游戏调色板。若编辑器此时能拿到真实颜色下一次保存会把它们以kind: rgb写出。测试_test_index_encoding断言这种灰阶编码往返无损且不会凭空发明 RGB 调色板。项目调色板只作用于Paint 编辑器及其图像导出。MIF 文件存储的是调色板索引而非颜色因此游戏与 Place Print 使用配置的游戏调色板——同一个项目在不同调色板下显示效果可能不同。此外项目记录里的metadata.palette存放的是编辑器的偏好数据如收藏色、最近颜色它与项目调色板是两回事game/src/ui/scurk/scurk_editor_studio.gd 的save_project()通过editor.palette_panel.export_state()将其写入project.metadata[palette]。索引图像与透明遮罩每个图像成员是满足以下全部条件的 PNG8 位索引色恰好 256 个调色板条目顺序与palette.json完全一致遮罩图像使用灰阶调色板宽高与其文档/图章一致。读取器在解码前先校验尺寸——Rust 端image()在调用 PNG 解码器分配输出之前直接从 PNG 头部的固定偏移宽度偏移 16、高度偏移 20读取宽高并比对测试中的Image dimensions must match the document/Mask dimensions must match the document即由此而来。项目数据中的像素值语义-1表示透明0–255为调色板索引其余值非法。重复颜色保留各自独立索引测试断言Duplicate RGB colors keep distinct indices。元数据中的像素字段pixel field是一个对象字段值image索引 PNG 图像的成员路径transparency_mask可选遮罩图像的成员路径常规情况下透明像素直接使用透明的调色板条目写入器选用该图像没有用到的最低调色板索引来承载透明。但当图像用满全部 256 个索引且仍有透明像素时就没有空闲索引了此时写入器会在图像旁生成图像名.mask.png遮罩遮罩中0表示透明、255表示不透明其他值非法测试分别用127、-1、255验证非法遮罩图像在每个透明像素处存入索引0读取器在遮罩为透明的像素处忽略图像值并还原为-1带遮罩的像素字段其图像不得含透明像素。该逻辑在 native/core/assets/src/scurk/project/archive.rs 的pixels()中used集合同时包含-1且数量达到 257256 索引加透明时判定mask_needed。测试_all_states()构造了256 个不透明索引 1 个透明态共 257 种状态断言图层与图章都带遮罩且遮罩精确还原每个像素的透明度。外部编辑器可以修改图像成员但必须保持索引色、有序调色板、尺寸、成员路径与遮罩值。读取器会拒绝使用 RGB 色或调色板顺序不一致的图像A project PNG has the wrong ordered palette.。需要再次强调项目文件不能替代Export Tile Set命令。项目记录Project record的数据模型project对象包含这些字段字段内容revision变更计数器整数范围 0 到 9007199254740991original_mif项目开始时的 MIF 字节的成员路径current_mif当前扁平化 MIF 字节的成员路径metadata作者、许可证、编辑器偏好等 JSON 对象resources资源名到成员路径的映射documents文档键到文档的映射stamps图章数组checkpoints检查点数组从最旧到最新读取器只加载 JSON 值、MIF 字节、PNG 图像与资源字节绝不加载脚本、Godot 资源、对象或文件系统路径每个 MIF 成员在使用前都会被解析且不能为空见 game/src/tools/scurk/scurk_project.gd 的_valid_mifnot bytes.is_empty() bytes.size() MAX_MIF_BYTES Mif.new().parse(bytes)。文档Documents文档键是 1–128 字符的文本编辑器使用大精灵 ID:视图形式例如1001:0。文档字段字段内容width像素宽度1–128height像素高度1–256active活动图层索引必须指向layers中的某个图层original_pixels文档起始像素像素字段layers1–32 个图层组成的数组自底向上图层字段字段内容name1–256 字符文本不能只含空格visible布尔locked布尔pixels像素字段扁平化规则编辑器把可见图层自底向上叠加上层的不透明像素覆盖下层透明像素保留下层隐藏图层不参与结果。编辑器不会在锁定图层上绘制也不会删除锁定图层。从源码看flatten_range()逐层遍历if not bool(layer.visible): continue然后对每个像素if source[index] 0: pixels[index] source[index]——与文档描述完全一致。编辑器默认使用 128×256 的工作区文档更小的文档与图章同样合法。图章Stamps字段内容name1–256 字符文本不能只含空格width像素宽度1–128height像素高度1–256spacing整数1–256pixels像素字段资源Resources资源名是 1–256 字符的文本它是标识符而非文件路径每个资源最多 16 MiB 字节。从ScurkProject._valid_state()可看到资源名同样受长度与内容约束且资源字节数上限为MAX_MIF_BYTES。检查点Checkpoints字段内容name1–256 字符文本不能只含空格createdUTC 日期时间文本revision创建检查点时的项目修订号snapshot含current_mif、documents、metadata、resources、stamps的对象快照不包含检查点列表或原始 MIF。一个项目最多 24 个检查点添加时若列表已满编辑器移除最旧的检查点。恢复检查点时编辑器用快照替换当前状态——原始 MIF 不变修订号增加。add_checkpoint的实现印证了满则淘汰最旧if checkpoints.size() MAX_CHECKPOINTS: checkpoints.pop_front()。测试_test_checkpoints逐个恢复全部检查点并断言恢复后编辑不会与快照像素共享引用Restored layer edits do not alias checkpoint pixels。未知字段的保留策略一次加载–保存循环会保留以下内容项目记录的未知字段文档、图层、图章与检查点的未知字段metadata 与资源字节所有 MIF 成员的字节。但不保留检查点快照对象中的未知字段。由于 JSON 不区分整数与浮点数循环往返保留的是数值本身而非存储类型。实现上extra_fields保存项目记录顶层未知字段game/src/tools/scurk/scurk_project.gd 的to_dictionary()先合并extra_fields再写快照字段测试断言The archive envelope preserves colliding unknown project fields与Archive round trip retains every project field。硬性限制一览项目限制项目文件大小128 MiBZIP 成员未压缩总大小128 MiB项目数据大小64 MiB单个 MIF 或资源16 MiB文档数1,500每个文档的图层数32图章数256资源数1,024检查点数24JSON 嵌套深度32 层这些常量集中在 game/src/tools/scurk/scurk_project_limits.gdGDScript 与 Rust 侧共享同一套数值。其中项目数据大小的计算公式是当前状态与所有检查点的以下各项之和——原始 MIF 与每个当前 MIF 的字节数资源字节数每张图像每个像素 2 字节。注意PNG 压缩不改变项目数据大小读取器会在解压 ZIP 或解码图像之前先校验声明的大小Rust 端charge()逐项累加并在超限时立刻中止。另外PNG 调色板与 ZIP 头部会额外占用文件字节因此包含大量小图像的项目可能超出文件大小上限即使其项目数据大小仍在许可范围内。ZIP 规则确定性输出与严格输入成员路径使用 UTF-8 与正斜杠路径不得含反斜杠、冒号、零字节、空组件、.或..成员使用存储stored或 DEFLATE 压缩写入器只在压缩能让成员更小时才用 DEFLATE读取器在同样限制内接受 ZIP64 记录与数据描述符读取器不接受加密或跨多磁盘的归档写入器按路径排序成员并写入固定日期1980-01-01因此相同项目数据必然产生相同文件字节测试直接断言Archive output is deterministic与project.to_bytes().bytes encoded.bytes读取器从不把成员解压到文件系统。Rust 端zip::encode在 native/formats/src/bridge/zip.rs 可见其always_deflate开关归档编码前members.sort_by(|a, b| a.0.cmp(b.0))保证排序确定性。保存流程先验证再编码最后原子替换每次写项目前编辑器都会做双重自检检查项目数据from_dictionary全量校验编码文件然后把编码结果再解码一遍校验。任一检查失败保存即失败。编辑器绝不会为了凑合大小而删除图层或检查点。GDScript 的to_bytes()正是编码→再解码→通过才返回字节的流程。落盘采用临时文件 原子重命名策略game/src/tools/scurk/scurk_project.gd 的save_path()在目标目录写入临时文件名含进程 ID 与时间戳如xxx.scurk.pid.usec.tmpflush 后重新读回并逐字节比对比对一致才把临时文件重命名为目标文件名任一步失败则删除临时文件目标文件保持不变保存从不删除源 MIF 文件或导入的文件。测试_test_storage验证了失败路径序列化失败时目标字节不变目标路径被目录占用替换失败时旧输出仍在且临时文件被清理。自动保存与崩溃恢复编辑器打开且项目有未保存修改时每 30 秒写一次自动保存文件game/src/ui/scurk/scurk_editor_studio.gd 的_process()中autosave_elapsed 30.0触发autosave()。自动保存文件路径为user://scurk/recovery.scurk使用与正常保存相同的格式与安全写盘方法autosave_path()直接复用save_path()。编辑器启动时若存在恢复文件会弹出Recover SCURK project对话框三个选项的行为Recover加载恢复文件Ignore把恢复文件重命名为同目录下的recovery-ignored-time.scurkKeep for later关闭对话框不改动文件。File Recover Autosave会打开恢复文件所在目录若当前打开的项目有未保存修改编辑器会先询问再替换。自动保存与多会话恢复还有一个精妙的编排见 game/src/tools/scurk/scurk_edit_session.gd 的autosave()与save_project()写自动保存时若存在其他会话遗留的恢复文件先把旧文件复制为recovery-time.scurk再写自己的恢复文件成功保存后编辑器会删除自己的恢复文件以及它加载过的恢复文件避免陈旧恢复数据误导用户。从源码验证格式的正确姿势格式编解码的权威实现在 native/core/assets/src/scurk/project/archive.rs编码encode/ 解码decode其值树Node类型Bytes、Pixels等非 JSON 类型定义于 native/core/assets/src/scurk/project/record.rsGDScript 侧对象模型与校验逻辑在 game/src/tools/scurk/scurk_project.gd它把所有静态限制与字段规则_valid_state、_json_safe、像素值域、名称与尺寸校验集中实现行为级验证最直观的入口是 game/tests/scurk_project_archive_test.gd它无需原始游戏数据即可运行覆盖了 ZIP 容器识别、确定性输出、索引编码回退、遮罩、调色板、检查点恢复、各类畸形成员拒绝与原子保存失败路径是理解本格式各项规则的可执行文档。小结SCURK 项目格式的设计目标非常明确完整保存编辑器的可继续编辑状态同时保持严格的、确定性的、可校验的字节契约。它用 ZIP 容器把四类数据JSON 元数据、MIF 字节、索引 PNG、资源字节隔离管理用数字序号成员路径规避逻辑名带来的路径安全隐患用 256 索引 可选遮罩的双轨方案解决透明像素编码用全套硬性限制与先验后写、写后再验的双向校验保证文件安全最后以 30 秒自动保存与多会话恢复编排为用户的创作过程兜底。理解这套格式是深入 SCURK 编辑器实现、排查项目文件问题或为 OpenSC2K 生态开发配套工具的起点。赞分享游戏开发【免费下载链接】OpenSC2KOpenSC2K - Open Source remake of Sim City 2000 using rust and godot. Available for macOS, Windows and Linux项目地址https://gitcode.com/gh_mirrors/op/OpenSC2K点击查看免费下载相关推荐Certbot Reverter 模块深度解析配置文件检查点保存与回滚机制Certbot Reverter 模块深度解析配置文件检查点保存与回滚机制 本文围绕 Certbot 的核心回滚组件 certbot.reverter.Rev网络安全CLI后端dotnetbook完全指南深入理解.NET平台架构的核心原理dotnetbook完全指南深入理解.NET平台架构的核心原理 dotnetbook是一份全面的.NET平台架构学习资源提供了英文、中文和俄文三种语言版本数据库低代码后端前端Nhost 文档全文检索底层zapx v16 的 ZAP 段文件二进制格式深度解析Nhost 文档全文检索底层zapx v16 的 ZAP 段文件二进制格式深度解析 Nhost 的 CLI 内置文档全文搜索功能其底层依赖 vendored后端认证鉴权数据库无服务开发工具云原生上一篇Wretch v2 到 v3 迁移完整指南破坏性变更、customError 与上传进度监控实战下一篇N_m3u8DL-RE 实战指南三步走通从首次下载到直播录制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价 →
↑