AssetRipper 数据存储拆解:3 层抽象让配置与元信息不落数据库也能持久化
2026/9/20 22:13:13 网站建设 项目流程

AssetRipper 数据存储拆解:3 层抽象让配置与元信息不落数据库也能持久化

【免费下载链接】AssetRipperGUI application to analyze game files项目地址: https://gitcode.com/GitHub_Trending/as/AssetRipper

AssetRipper 的数据存储层是一组不起眼的内存类,像个带类型标签的抽屉柜,而不是真正的数据库:你调过的导入设置、资产清单、提取出的元信息都进这个柜子,随时可存盘、可读回。它服务于这个分析和提取 Unity 游戏文件的 GUI 应用,这些数据的持久化全由这一层负责。

🎯 提完一个游戏,这些开关存在哪?

提取完一个游戏,要记住的选项不少:脚本要不要反编译、图片导出成什么格式、反编译到哪一级。程序关掉后这些开关不能丢,又不能散落在一堆配置文件里。AssetRipper 把它们全部收进同一个存储层。

📦 为什么不用数据库,而是造一个"抽屉柜"

三个设计决策决定了这层架构的样子

决策一:字典而非数据库。配置量级很小,撑死十几个键,也不需要复杂查询,启动时一次性全量进内存。基类 DataStorage 本质就是给 Dictionary<string, T> 包了几个方法:按键取、带类型检查的 TryGetValue、Add。在这里上数据库,属于用叉车搬几本书。

决策二:为什么要 DataEntry 抽象。柜子里每种抽屉都要求继承同一个基类,而基类只规定一个方法:Clear()。看着单薄,但它让 DataStorage 能同时泛型地装下"单例条目"和"列表条目":一键全清、统一类型约束,以后新增条目类型也进同一个容器。源码见 DataEntry.cs,只有 6 行。

决策三:存的是"带类型的包装",不是裸字符串。每个键持有的值不是字符串,而是一个内部装着强类型值和对应序列化器的包装对象,字符串 Text 只是从值派生出来的"视图"。于是内存侧永远是可直接使用的对象,字符串侧永远是能落盘的文本。

这是 AssetRipper 的发行目录,里面没有任何数据库文件——整个数据层活在进程内存里,磁盘上的文件只是一份字符串快照。

🔁 一条导入设置从写入到读回的完整旅程

一次完整的数据往返

拿导入设置举例。CoreConfiguration 的构造器启动时先登记默认值:往单例存储里 Add 一个以 ImportSettings 为键的 JsonDataInstance,保证就算还没写过、直接读也不会抛 KeyNotFoundException。源码见 CoreConfiguration.cs。

写入。用户在界面勾选开关时,属性 setter 把强类型值写进包装对象的 Value 字段;GUI 的设置页也可以直接改原始文本,走 instance.Text = 新文本,setter 内部会调反序列化器把字符串解析回对象。两条路线最后都汇到同一个 Value。

落盘。持久化层看到的只是每个条目的 Text——一堆字符串,写进配置文件,全程与设置的具体类型无关。这是"字符串视图"设计的红利:将来换持久化格式,调用方一行不用动。

读回。下次启动,文本被填回对应条目的 Text,setter 触发序列化器的 Deserialize,Value 变回强类型对象。文本损坏时,反序列化器返回默认值而不是抛异常——这点"宽容"下一节细说。

🧩 两种反序列化路径:IParsable 还是 JSON,怎么选

看这组签名,就能理解"序列化器"这个角色要负责什么:

public abstract class DataSerializer<T> { public abstract T Deserialize(string text); public abstract string Serialize(T value); public abstract T CreateNew(); }

两个实现类,选型标准很直白:

  • ParsableDataSerializer:面向实现了 IParsable (有静态 TryParse 方法)的类型。纯字符串双向转换、无嵌套、开销小,UnityVersion 这类标量配置项走这条。
  • JsonDataSerializer:面向带嵌套结构的设置对象,比如装着多个子选项的 ImportSettings。它走 System.Text.Json 的源生成,构造时接收编译期产出的 JsonTypeInfo ,序列化不经过运行时反射。

两条路径脾气一致:宽容解析。文本为空、TryParse 失败、JSON 坏了,都不抛异常,一律退回 CreateNew() 的默认值。配置文件是用户可能手改的文件,优雅降级比抛异常拖垮整个程序更合适。

"宽容"的逻辑就这么几行,以 IParsable 路径为例:

if (T.TryParse(text, null, out T? value)) { return value; } return CreateNew(); // 坏数据不抛异常,退回默认

💾 保存并恢复导入设置:一个完整场景

场景:用户改完脚本反编译级别,关掉程序、下次打开时它得还记着。

看这几行,就能理解调用方和存储层是如何解耦的:

// 保存:调用方只认属性,不知道存储的存在 config.ImportSettings.ScriptContentLevel = ScriptContentLevel.Level2; // 恢复:下次启动直接读回 var level = config.ImportSettings.ScriptContentLevel;

ImportSettings 属性的 get/set 背后,就是对 "ImportSettings" 这个键的 GetStoredValue 和 SetStoredValue。GUI 设置页则遍历存储的 Keys 渲染选项列表,用户改完写回 Text——与属性同一条路,最终收敛到同一个 Value。

这是打开 AssetRipper 后的欢迎页配置界面,用户看到选项面板,面板背后就是前面说的单例存储——每个下拉框都对应 ImportSettings 里的一个字段。

🧭 三条可以搬进自己项目的设计经验

  1. 把持久化格式做成"字符串视图"。内存里的值是真相,字符串只是投影。换 JSON 文件、INI 甚至数据库,只需改 Text 的读写,调用方不动,代价只是一个契约类。
  2. 宽容而非严格。配置数据的反序列化失败应返回默认值而不是抛异常,顺手就能救回用户改坏一半的配置文件。
  3. 用最小的公共契约。DataEntry 基类只有一个 Clear(),约束越小,泛型容器能装的类型越多;别把还没需要的要求写进基类。

【免费下载链接】AssetRipperGUI application to analyze game files项目地址: https://gitcode.com/GitHub_Trending/as/AssetRipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询