AssetRipper 数据库拆解:看懂存储与查询只需这 4 个抽象
2026/9/20 5:51:30 网站建设 项目流程

AssetRipper 数据库拆解:看懂存储与查询只需这 4 个抽象

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

AssetRipper 数据库并不是真正的数据库引擎,而是这个 Unity 资产提取工具藏在源码深处的一套键值存储与查询机制。下面从一条真实配置的落盘过程切入,把 AssetRipper 数据存储子系统拆开讲清楚,读完你能独立给项目加一条新配置。

一条配置是怎么变成文件里的字符串的

先看这条配置是怎么落进存储的。你在 GUI 里把"脚本导入级别"从 2 调到 0,内存里变化的其实是一个 ImportSettings 对象;但保存设置文件时,磁盘上写的是一段 JSON 文本。为什么存成字符串而不是直接写对象?因为整个存储层是"文件无关"的:DataEntry 这一层(所有条目的共同根类)不关心内容最终以什么形式存在,它只规定每个条目必须能被 Clear() 清空,字符串化这件事被统一推后到 Text 属性上完成。

🧩 所以三层抽象的分工很干脆:DataEntry 是根,DataInstance 管"单值"(一个对象),DataSet 管"序列"(一组值,可直接遍历)。真正值得记死的是 DataInstance 里的 Text 属性:

public T Value { get; set; } public sealed override string Text { get => serializer.Serialize(Value); // 读取时才序列化 set => Value = serializer.Deserialize(value); // 写入时才反序列化 }

也就是说:反序列化发生在有人给 Text 赋值的那一步(从文件读进内存),序列化发生在有人读 Text 的那一步(从内存写回文件),内存里始终持有对象本身。整个子系统的结构大致如下:

全部实现集中在 Source/AssetRipper.Configuration/ 目录,十几个文件,一次能读完。

三种序列化怎么选:纯文本、可解析类型、结构化对象

⚙️ 上面那个 serializer 字段就是分叉口。基类 DataSerializer 只要求三个方法,负责把 T 和 string 互转:

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

存裸文本时用 StringDataSerializer

它就是恒等映射,适合 README 这类纯说明文本、不需要任何解释的原始内容。CoreConfiguration 的调试数据里就有一条这样的字符串条目,走的是这条最轻的路径。

存可解析类型时用 ParsableDataSerializer

前提是 T 实现 IParsable (能靠 T.TryParse 从一行字符串还原自己,int、Guid 都算)。它的 Deserialize 是容错的:解析失败不抛异常,而是回落到 CreateNew() 给一个默认值,坏文本不会把整个加载流程打断。

存结构化对象时用 JsonDataSerializer

ImportSettings 这种嵌套对象走这条线:构造时传入 System.Text.Json 的 JsonTypeInfo (可理解为编译期就绑定好的类型信息,省掉运行时反射),反序列化同样包着 try/catch,坏数据退回 new T()。

把选型压缩成一句话:内容是一坨不需要解释的文本,用字符串序列化;内容能用一行字符串完整表达,用可解析类型;内容是有字段层次的 POCO,用 JSON。这篇 AssetRipper 源码解析到此可以下结论:两个"容错"分支都会静默回落,这条特性后面避坑时还会再提。

如何写入一条配置、如何读回它

✅ 调用侧其实比内部简单得多。配置管理走 CoreConfiguration 的属性封装,键名直接用类型名:

var settings = new CoreConfiguration(); // 写入配置:属性 setter 内部执行 SetStoredValue settings.ImportSettings.ScriptContentLevel = ScriptContentLevel.Level0; // 读取配置:键不存在或类型不符时返回 false,不抛异常 settings.SingletonData.TryGetStoredValue<ImportSettings>( nameof(ImportSettings), out ImportSettings? imported);

CoreConfiguration.cs 的构造函数里能看到默认值是怎么放进去的:SingletonData.Add(nameof(ImportSettings), new JsonDataInstance<ImportSettings>(...));导出侧的 ProcessingSettings、ExportSettings 则是 FullConfiguration.cs 用同一招补进去的。

资产依赖列表这类"一组值"走 ListDataStorage。它的 Add 重载按元素类型自动挑选容器:字符串列表装箱为 StringDataSet,满足 IParsable 的类型装箱为 ParsableDataSet 。

// 写入列表:int 满足 IParsable<int>,自动用 ParsableDataSet<int> settings.ListData.Add("Fibonacci", new List<int> { 1, 1, 2, 3, 5, 8 }); // 读回:经基类取出,再用字符串视图遍历 DataSet set = settings.ListData.GetValue<DataSet>("Fibonacci"); foreach (string item in set.Strings) { Console.WriteLine(item); // 1 1 2 3 5 8 }

保存动作本身发生在设置页:ConfigurationFilesPage.cs 把编辑器里的内容整体赋给某个条目的 .Text,"保存文件"就等价于把 Text 序列化后写盘。

进阶与避坑:遇到 X 就考虑 Y

🛠️ 读源码到一半你可能会撞上这几个情况,对应的解法项目里都已经写好了:

  • 遇到"界面要编辑一个尚不存在的键":别手动 TryGet 再加,用 GUI 侧现成的 GetOrAdd 扩展(DataExtensions.cs):
GameFileLoader.Settings.SingletonData.GetOrAdd(key).Text = content;
  • 遇到"按名字高频查找同一个列表元素":DataSet 是线性遍历,自己维护一份Dictionary<string, int>索引即可,别指望存储层替你建索引。
  • 遇到"想改配置但不想重建实例":SetStoredValue 会原地修改已存在 DataInstance 的 Value,前提是键已存在,否则抛 KeyNotFoundException。
  • 遇到"配置文件坏了但程序没报错":回想前面说的容错解析——坏文本会静默回落成 CreateNew() 的默认值。设置突然"变回默认"时,先查文件内容,别怀疑代码逻辑。
  • 遇到多线程并发写:DataStorage 底层就是 Dictionary,没有任何锁。GUI 当前单线程使用没问题;自己写插件或工具若要跨线程共享同一份配置,同步得自己加。

行动建议与延伸阅读

如果只带走一条建议:给 AssetRipper 加新配置时,永远走"键 + DataInstance /DataSet + 对应序列化器"这个组合,别绕过 Text 直接往字典里塞字符串,否则读回时拿不到类型化对象。延伸阅读按依赖顺序走一遍就通了: DataSerializer.cs → DataInstance.cs → CoreConfiguration.cs → FullConfiguration.cs → ConfigurationFilesPage.cs,看完在自己的分支里加一个配置键,是最快的检验方式。

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

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

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

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

立即咨询