SerializedFile 是 Unity 自定义的混合序列化格式,含头部、对象块、数据流及可选压缩,需先解析头部版本与压缩标识再分路径处理,不能直接用 ifstream 读取。

SerializedFile 是什么,为什么不能直接用 ifstream 读
Unity 的 SerializedFile 不是普通二进制文件,而是带自定义头部、对象块(Object Info)、数据流(Data Block)和可选 LZ4/Brotli 压缩的混合格式。直接用 std::ifstream 读到的只是字节流,没有解析出类名、字段名、引用关系——你看到的会是一堆无法识别的 0x00 和乱码指针偏移。
关键点在于:Unity 在 5.6+ 引入了新的序列化格式(SerializedFile v19+),头部结构变了,且默认启用 LZ4 压缩;老版本(如 4.x)用的是未压缩的 Binary Format,但仍有自己的元数据布局。不区分版本硬解,大概率在 m_FileSize 或 m_Version 字段就解析失败。
必须先提取并验证 SerializedFile 头部
头部前 20 字节固定为 magic + version + file size:0x00000000 0x00000013 0x00000000... 这类值不是“随便跳过”,而是决定后续解析路径的关键开关。
-
m_Version≥ 19:启用新格式,需检查m_ObjectsOffset和m_MetadataSize,且m_CompressedLength非零时要先解压DataBlock -
m_Version≤ 18:走旧 Binary Format 路径,对象信息紧接头部,无压缩层,但要注意m_Endianess(小端/大端)影响所有整数字段解析 - 必须校验
m_HeaderSize是否等于实际读取的头部长度,否则后续所有偏移都错位——这是最常被忽略的崩溃点
如何安全读取 ObjectInfo 并定位实际数据
每个 ObjectInfo 描述一个 Unity 对象(如 GameObject、Mesh),但它本身不存数据,只存 m_PathID、m_TypeID 和 m_ByteStart/m_ByteSize。真正数据在后面的 DataBlock 中,且可能跨多个块(尤其压缩后)。
立即学习“C++免费学习笔记(深入)”;
- 不要假设
m_ByteStart是全局文件偏移——它相对于DataBlock起始位置(即m_ObjectsOffset + sizeof(Header)) -
m_TypeID需查 Unity 内置类型表(如21=Mesh,1=GameObject),该表随 Unity 版本变化,硬编码会失效;建议从GlobalGameManager或ScriptableObject的typeTree中动态加载 - 若遇到
m_ByteSize == 0,说明该对象是外部引用(AssetBundle或Resources),此时应停止解析,而非尝试读空区域
解压 DataBlock 前必须确认压缩算法和边界
Unity 只在 m_Version ≥ 19 且 m_CompressedLength > 0 时启用压缩,但算法不是固定的:LZ4(v19–v2021.2)、Brotli(v2021.3+),且压缩范围仅覆盖 DataBlock 主体,不包括 ObjectInfo 表或尾部校验区。
- 错误地对整个文件调用
LZ4_decompress_safe会导致内存越界——必须严格按m_CompressedLength和m_UncompressedLength操作 - Brotli 解压需链接
libbrotlidec,且 Unity 使用的 Brotli 参数(如 window size = 22)与标准工具链不完全一致,建议复用unitypack的 C++ binding 或直接调用UnityPy的 C API 封装 - 未压缩时,
m_CompressedLength仍可能非零(Unity bug),需以m_UncompressedLength为准分配缓冲区
真正的难点不在解压本身,而在于:你永远不知道下一个 ObjectInfo 的 m_ByteStart 是指向解压后的数据,还是原始压缩流——必须根据 m_IsStreamed 标志和当前解析上下文动态切换读取源。


















