
Dolphin 的 WIA 与 RVZ 光盘镜像格式完全解析从二进制结构到压缩与随机访问原理【免费下载链接】dolphinDolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.项目地址: https://gitcode.com/GitHub_Trending/do/dolphin导读WIAWii ISO Archive与 RVZRevolution Zero是两种面向 GameCube / Wii 光盘镜像的高压缩比容器格式其中 WIA 由 wit 工具首创而 RVZ 由 Dolphin 模拟器在 WIA 基础上扩展而来现已成为 Dolphin 用户压缩游戏镜像的主流选择。本文以 Dolphin 仓库中的官方格式文档 docs/WiaAndRvz.md 为主体结合 WIABlob.h、WIABlob.cpp、WIACompression.h、LaggedFibonacciGenerator.cpp 等源码实现逐字节讲解 WIA/RVZ 的二进制布局、Wii 分区去哈希存储、哈希异常hash exception机制、五种压缩算法以及 RVZ 独有的伪随机填充数据无损编码方案。读完本文你将能够独立解析 WIA/RVZ 文件、理解 Dolphin 读写这两种格式时的全部底层决策并具备实现读写器的完整规范依据。一、WIA 格式总览相对 GCZ 的三大进步WIA 格式版本为 1.00最初实现于 wit v2.40a。相比旧式 GCZ 压缩格式WIA 的独特之处在于支持 bzip2、LZMA、LZMA2 三种压缩算法RVZ 又增加了 Zstandard见后文Wii 分区数据以解密且去除哈希的形式存储从而获得远高于原始镜像的可压缩性。与几乎所有压缩版 GC/Wii 光盘镜像格式一样WIA 将数据划分为若干块wit 中称之为chunkDolphin 源码中对应group。每个块独立压缩这使得对压缩数据的随机访问成为可能——读取某个块时只需解压该块本身无需解压整个文件。Dolphin 源码中的实现印证了这一设计WIARVZFileReader内部维护一个缓存块m_cached_chunkRead()时按chunk_size定位并仅解压目标块见 WIABlob.h 的Chunk类。GetBlockSize()直接返回wia_disc_t中的chunk_size而HasFastRandomAccessInBlock()返回false表示块内访问仍需顺序解压。以下所有结构体名称与字段名均直接取自 wit 源码。除非特别说明WIA 文件中的数据可以任意顺序存放所有整数均为大端序big endiansha1_hash_t指 20 字节的 SHA-1 摘要数组。二、文件头wia_file_head_t偏移 0x0长度 0x48该结构体存储在文件偏移 0x0 处固定为 0x48 字节wit 源码注释明确声明其格式永远不会改变。Dolphin 中对应的定义是WIAHeader1并用static_assert(sizeof(WIAHeader1) 0x48)强制校验尺寸见 WIABlob.h。关于版本号的编码方式wit 源码中有如下注释//----------------------------------------------------- // Format of version number: AABBCCDD A.BB | A.BB.CC // If D ! 0x00 D ! 0xff append: beta D //-----------------------------------------------------| 类型与名称 | 说明 | |--|--| |char magic[4]| 恒为WIA\x1。Dolphin 中以WIA_MAGIC 0x01414957即WIA\x1的字节交换形式表示见 WIABlob.h。 | |u32 version| WIA 格式版本号。 | |u32 version_compatible| 若读取程序支持此处指示的版本就可以读取该文件。version可以高于version_compatiblewit v2.40a 将前者设为0x01000000后者设为0x00090000。 | |u32 disc_size|wia_disc_t结构体的字节大小。wit v2.40a 写入该值时总把compr_data的全部 7 个字节计入。 | |sha1_hash_t disc_hash|wia_disc_t结构体的 SHA-1 哈希参与哈希的字节数由disc_size决定。例如可能需要对compr_data的全部 7 个字节计算哈希而不论compr_data_len声称多少。 | |u64 iso_file_size| 光盘原始大小即与 WIA 内容等价的 ISO 文件大小。 | |u64 wia_file_size| 本文件自身的大小。 | |sha1_hash_t file_head_hash| 本结构体从开头到file_head_hash字段之前不含自身的 SHA-1 哈希。 |Dolphin 的Initialize()在打开文件时会做严格的校验链先校验 magic再校验版本兼容性然后计算header_1_hash验证文件头完整性并核对wia_file_size是否与实际文件大小一致见 WIABlob.cpp。Dolphin 写入端则设置WIAWIA_VERSION 0x01000000WIA_VERSION_WRITE_COMPATIBLE 0x01000000WIA_VERSION_READ_COMPATIBLE 0x00080000RVZRVZ_VERSION 0x01000000RVZ_VERSION_WRITE_COMPATIBLE 0x00030000RVZ_VERSION_READ_COMPATIBLE 0x00030000见 WIABlob.h。版本兼容性检查的实现值得注意WIABlob.cppconst u32 version RVZ ? RVZ_VERSION : WIA_VERSION; const u32 version_read_compatible RVZ ? RVZ_VERSION_READ_COMPATIBLE : WIA_VERSION_READ_COMPATIBLE; const u32 file_version Common::swap32(m_header_1.version); const u32 file_version_compatible Common::swap32(m_header_1.version_compatible); if (version file_version_compatible || version_read_compatible file_version) { ERROR_LOG_FMT(DISCIO, Unsupported version {} in {}, VersionToString(file_version), path); return false; }即读取方既要求自己的版本不低于文件声明的version_compatible也要求文件版本不高于读取方自己能接受的version_read_compatible上限之外。wit 源码中关于为何 WIA 写入端把version_compatible定为 1.0 而非 0.9Dolphin 在注释中给出了说明WIA 0.9 从未出现在 wit 的任何正式发布中难以验证其兼容性因此保守地写为 1.0。三、盘信息结构wia_disc_t偏移 0x48该结构体紧跟在wia_file_head_t之后位于文件偏移 0x48 处。Dolphin 对应定义为WIAHeader2尺寸 0xdc见 WIABlob.h。| 类型与名称 | 说明 | |--|--| |u32 disc_type| wit 将其设为0 表示未知实际中是否发生存疑、1 表示 GameCube 光盘、2 表示 Wii 光盘。Dolphin 转换时依据输入卷的平台类型填充WIABlob.cppGameCubeDisc 写 1WiiDisc 写 2其余为 0。 | |u32 compression| 压缩方法0 NONE1 PURGE见wia_segment_t一节2 BZIP23 LZMA4 LZMA2。Dolphin 以枚举WIARVZCompressionType表示并额外加入Zstd 5见 WIABlob.h。 | |u32 compr_level| 压缩器使用的压缩级别仅作信息用途。注意 Dolphin 在WIAHeader2中将其声明为有符号s32因为 Zstandard 支持负压缩级别见 WIABlob.h。 | |u32 chunk_size| 数据划分的块大小必须是 2 MiB 的整数倍。Dolphin 的GetBlockSize()直接返回该值。 | |u8 dhead[0x80]| 光盘镜像的前 0x80 个字节。 | |u32 n_part|wia_part_t结构体的数量。 | |u32 part_t_size| 单个wia_part_t结构体的大小若小于sizeof(wia_part_t)缺失的字节以0x00填充。 | |u64 part_off| 文件中wia_part_t结构体数组的存储偏移未压缩。 | |sha1_hash_t part_hash|wia_part_t结构体数组的 SHA-1 哈希参与哈希的字节数为n_part * part_t_size。 | |u32 n_raw_data|wia_raw_data_t结构体的数量。 | |u64 raw_data_off| 文件中wia_raw_data_t结构体数组的存储偏移压缩存储。 | |u32 raw_data_size| 所有wia_raw_data_t结构体的压缩后总大小。 | |u32 n_groups|wia_group_t结构体的数量。 | |u64 group_off| 文件中wia_group_t结构体数组的存储偏移压缩存储。 | |u32 group_size| 所有wia_group_t结构体的压缩后总大小。 | |u8 compr_data_len|compr_data数组中实际使用的字节数。 | |u8 compr_data[7]| 压缩器专用数据见下文。 |3.1 压缩器专用数据compr_data压缩方法为NONE、PURGE 或 BZIP2时compr_data_len为 0压缩方法为LZMA 或 LZMA2时专用数据采用7-Zip SDK的格式存储若使用 liblzma 等库需要先做转换。Dolphin 的LZMACompressor/LZMADecompressor通过lzma_filter将 7-Zip 属性转换为 liblzma 的lzma_options_lzma见 WIACompression.h。LZMA的专用数据长 5 字节首字节编码lc、pb、lp三个参数后四个字节以小端序编码字典大小。首字节解码逻辑如下代码取自 7-Zip SDKd data[0]; if (d (9 * 5 * 5)) return SZ_ERROR_UNSUPPORTED; p-lc d % 9; d / 9; p-pb d / 5; p-lp d % 5;LZMA2的专用数据仅 1 个字节编码字典大小解码如下代码取自 7-Zip SDK#define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) 1)) ((p) / 2 11)) if (prop 40) return SZ_ERROR_UNSUPPORTED; dicSize (prop 40) ? 0xFFFFFFFF : LZMA2_DIC_SIZE_FROM_PROP(prop);任何压缩方法都不使用预置字典preset dictionary。3.2 压缩级别范围Dolphin 为各压缩方法定义了允许的压缩级别范围GetAllowedCompressionLevels见 WIABlob.cppbzip2 / LZMA / LZMA2{1, 9}ZstandardGUI 界面下为{1, ZSTD_maxCLevel()}命令行下为{ZSTD_minCLevel(), ZSTD_maxCLevel()}。注释特别说明Zstandard 的 0 级是默认级别当前为 3 级的特殊值而负的ZSTD_minCLevel()数值很大直接展示会使 UI 显得怪异因此 GUI 模式下限取 1。四、分区数据描述wia_part_data_t与wia_part_t4.1wia_part_data_t| 类型与名称 | 说明 | |--|--| |u32 first_sector| 该数据在光盘上起始的扇区号。一个扇区为 32 KiB不含哈希时为 31 KiB。 | |u32 n_sectors| 本结构体覆盖的光盘扇区数。一个扇区为 32 KiB不含哈希时为 31 KiB。 | |u32 group_index| 指向本结构体所覆盖数据的第一个wia_group_t的索引其余wia_group_t索引依次递增。 | |u32 n_groups| 本数据使用的wia_group_t数量。 |Dolphin 的PartitionDataEntry与此一一对应static_assert(sizeof(PartitionDataEntry) 0x10)见 WIABlob.h。4.2wia_part_tWii 分区与去哈希存储该结构体用于追踪 Wii 分区数据。在真实光盘上这些数据是加密且带哈希的而 WIA 中存储的 Wii 分区数据不包含分区开头未加密区域该区域存放 ticket、TMD、证书链和 H3 表。因此对典型游戏分区而言pd[0].first_sector * 0x8000得到的是0x0F820000而非0x0F800000。Wii 分区数据以解密、去哈希的形式存储光盘上每 0x8000 字节WIA 文件中压缩前只存 0x7C00 字节。如果需要还原哈希读取程序必须先按从零创建 Wii 光盘镜像的规则重新计算哈希再应用随数据一同存储的哈希异常hash exception见wia_except_list_t一节。这与 Dolphin 中VolumeWii的常量完全吻合VolumeWii.h 定义了BLOCK_HEADER_SIZE 0x0400哈希区、BLOCK_DATA_SIZE 0x7C00数据区、BLOCKS_PER_GROUP 0x40每组 64 块、GROUP_TOTAL_SIZE 0x8000 * 0x40 2 MiB。| 类型与名称 | 说明 | |--|--| |u8 part_key[16]| 该分区的 title key128 位 AES可用于重新加密分区数据。此密钥可直接使用无需先用 Wii common key 解密。 | |wia_part_data_t pd[2]| 引用 wit 源码注释segment 0 较小用于管理数据boot .. fstsegment 1 容纳其余数据。wit 划分两段的临界点是FST 结束偏移向上取整到下一个 2 MiB。将第一段的大小设为非 2 MiB 倍数很可能不是好主意除非第二段大小为 0。 |Dolphin 的PartitionEntry即WiiKey partition_key std::arrayPartitionDataEntry, 2尺寸 0x30见 WIABlob.h。Wii 加密/解密路径则由WiiEncryptionCachem_encryption_cache缓存配合ReadWiiDecrypted()接口实现解密直读见 WIABlob.h。五、原始数据描述wia_raw_data_t该结构体用于追踪不以wia_part_t存储的光盘数据。这些数据除压缩外原样存储。| 类型与名称 | 说明 | |--|--| |u64 raw_data_off| 该数据在光盘上的起始偏移。 | |u64 raw_data_size| 本结构体覆盖的光盘字节数。 | |u32 group_index| 指向本数据第一个wia_group_t的索引其余索引依次递增。 | |u32 n_groups| 本数据使用的wia_group_t数量。 |Dolphin 对应RawDataEntrystatic_assert(sizeof(RawDataEntry) 0x18)见 WIABlob.h。5.1 第一个wia_raw_data_t的特殊性第一个wia_raw_data_t的raw_data_off为 0x80、raw_data_size为 0x4FF80但实际包含0x50000 字节的数据其中前 0x80 字节应从wia_disc_t的dhead读取。正确处理方式不是对第一个wia_raw_data_t做特判而是将偏移向下取整到前一个 0x8000 的倍数并等量增加大小使结束偏移保持不变。即读取端应把该条目视为偏移 0x0、大小 0x50000 来处理前 0x80 字节被dhead覆盖。六、组描述wia_group_t该结构体直接指向实际光盘数据压缩存储。数据的解读方式取决于它被wia_part_data_t还是wia_raw_data_t引用详见wia_part_t一节。一个wia_group_t正常包含chunk_size字节的解压后数据对不计哈希的 Wii 分区数据则为chunk_size / 0x8000 * 0x7C00字节其中不计入任何wia_except_list_t结构体。不过当n_sectors * 0x8000对wia_part_data_t或raw_data_size对wia_raw_data_t不能被chunk_size整除时对应wia_part_data_t/wia_raw_data_t的最后一个wia_group_t所含数据会少于正常值。| 类型与名称 | 说明 | |--|--| |u32 data_off4| 压缩数据在文件中的存储偏移除以 4后存储。 | |u32 data_size| 压缩数据的大小包含任何wia_except_list_t结构体也包含使用 NONE 或 PURGE 压缩方法时在wia_except_list_t之后所需的填充字节。0 是特殊值表示解压后数据的每个字节都是0x00且若有wia_except_list_t包含 0 个异常。 |Dolphin 中WIAGroupEntry为 8 字节data_offset注释 2RVZGroupEntry为 12 字节见 WIABlob.h与文档一致。七、哈希异常wia_exception_t与wia_except_list_t7.1wia_exception_t该结构体表示重新计算的哈希数据与原始哈希数据之间的一个 20 字节差异。当为一个大小不能被 2 MiB 整除的wia_group_t重新计算哈希时大小含哈希缺失的字节在哈希计算中应视为零。wit 的写入代码似乎假定读取代码不把这些缺失字节视为零但 wit 与 Dolphin 的读取代码都将它们视为零Dolphin 的写入代码则直接假定读取代码将其视为零。写入行为差异wit 的写入代码只为实际哈希数据中的不匹配输出wia_exception_t不为填充数据通常全零输出Dolphin 的写入代码同时为哈希数据和填充数据输出wia_exception_t。当 Dolphin 需要为 32 字节长的填充区写异常时会写两个异常一个覆盖填充区前 20 字节一个覆盖后 20 字节从而在两个wia_exception_t之间产生 12 字节的重叠。| 类型与名称 | 说明 | |--|--| |u16 offset| 在哈希中的偏移。这里偏移0x0000-0x0400映射到完整 2 MiB 数据中的0x0000-0x04000x0400-0x0800映射到0x8000-0x8400以此类推。每个新的wia_except_list_t中偏移从 0 重新开始。 | |sha1_hash_t hash| 需要替换给定偏移处自动生成哈希的哈希值。替换应发生在计算完当前 2 MiB 数据的所有哈希之后、加密这些哈希之前。 |Dolphin 的HashExceptionEntry定义为u16 offset SHA-1 Digest尺寸 0x16见 WIABlob.h。7.2wia_except_list_t每个 Wii 分区数据的wia_group_t在真实数据之前都包含一个或多个wia_except_list_t——该组数据每 2 MiB 对应一个。每个wia_group_t的wia_except_list_t数量恒为chunk_size / 0x200000即使该组因位于分区末尾而数据量少于正常值也不例外。| 类型与名称 | 说明 | |--|--| |u16 n_exceptions|wia_exception_t结构体的数量。 | |wia_exception_t exception[n_exceptions]| 每个wia_exception_t描述分区数据哈希与原始哈希之间的一处差异。 |读取端异常数量上限出于内存管理考虑读取程序可能限制单个wia_except_list_t中的异常数量。Dolphin 的读取代码上限为52×64 3328除非压缩方法是 NONE 或 PURGE此时无上限足以覆盖全部哈希与全部填充wit 的读取代码按47×64 3008的最大需求编写足以覆盖全部哈希但不含填充不过由于 wit 分配了超出需求的内存实际似乎可以超出一部分而不出问题写入代码可以安全地假定读取代码至少能处理每个wia_except_list_t3328 个异常。颇具讽刺意味的是wia_except_list_t的处理方式本身也存在随压缩方法而变的例外PURGE压缩方法下wia_except_list_t不压缩存储即位于第一个wia_segment_t之前BZIP2、LZMA、LZMA2下则与其余数据一起压缩NONE 与 PURGE方法下若最后一个wia_except_list_t的结束偏移不能被 4 整除其后会插入填充使后续数据从 4 字节边界开始其他压缩方法不插入此填充。Dolphin 的Chunk::HandleExceptions()正是这一逻辑的实现解压时先剥离异常列表依据m_compressed_exception_lists判断其是否与数据一同压缩并按align参数决定是否补齐 4 字节对齐见 WIABlob.h。八、PURGE 压缩方法与wia_segment_tPURGE 是一种极简压缩方法高效存储全零数据段其余数据原样存放。| 类型与名称 | 说明 | |--|--| |u32 offset|data在解压后数据中的偏移任何wia_except_list_t不计入解压后数据。 | |u32 size|data的字节数。 | |u8 data[size]| 数据。 |每个 PURGE 块包含零个或多个按offset升序排列的wia_segment_t一个 0x14 字节的 SHA-1 哈希覆盖所有wia_except_list_t若有和所有wia_segment_t。解压后数据中未被任何wia_segment_t覆盖的字节全部置为0x00。Dolphin 中PurgeSegment为u32 offset u32 size共 8 字节static_assert(sizeof(PurgeSegment) 0x08)PurgeCompressor/PurgeDecompressor实现了该逻辑其中解压器利用Common::SHA1::Context校验段数据哈希见 WIACompression.h。写入时PurgeCompressor::AddPrecedingDataOnlyForPurgeHashing用于把此前已输出的数据纳入哈希计算这是 PURGE 特有的需求因为段之间没有压缩流的连续性。九、RVZ 格式在 WIA 之上的四项扩展RVZ 与 WIA 高度同源差异可归结为四点新增 Zstandard 压缩方法wia_disc_t的compression设为 5 表示使用 Zstandard且没有压缩器专用数据compr_level应视为有符号Zstandard 支持负压缩级别。Dolphin 的ZstdCompressor/ZstdDecompressor直接封装ZSTD_CStream/ZSTD_DStream见 WIACompression.h。移除 PURGE压缩方法RVZ 的压缩方法只有 NONE、BZIP2、LZMA、LZMA2、Zstd。支持小于 2 MiB 的块大小块大小最小 32 KiB且必须是 2 的幂与 WIA 相同大于 2 MiB 的块不必是 2 的幂只需是 2 MiB 的整数倍对 Wii 分区数据每个块包含一个wia_except_list_t其中只包含该块的异常不涉及其他块偏移 0 指向当前块的第一个哈希而非完整 2 MiB 数据的第一个哈希。wia_group_t结构体被扩展见rvz_group_t一节并且伪随机填充数据以无损方式存储见RVZ packing一节。十、rvz_group_t扩展的组描述相比wia_group_trvz_group_t改变了data_size最高有效位的含义并新增一个属性。下文压缩数据指文件中的实际存储形态当压缩方法为 NONE 时这些压缩数据实际并未压缩。| 类型与名称 | 说明 | |--|--| |u32 data_off4| 压缩数据在文件中的存储偏移除以 4 后存储。 | |u32 data_size|最高位MSB为 1表示数据以wia_disc_t指示的压缩方法存储为 0 表示以NONE方法存储。低 31 位为压缩数据大小包含任何wia_except_list_t也包含使用 NONE 方法时在wia_except_list_t之后所需的填充。低 31 位为0是特殊值表示解压并解包后每个字节均为0x00且若有wia_except_list_t含 0 个异常。 | |u32 rvz_packed_size|解压后、但尚未解码 RVZ packing 之前的大小。若为 0表示该组不使用 RVZ packing。 |Dolphin 的RVZGroupEntry为u32 data_offset u32 data_size u32 rvz_packed_size共 12 字节见 WIABlob.h。读取端在Chunk构造时根据rvz_packed_size是否为 0 决定是否包裹一层RVZPackDecompressorWIABlob.cppGetOutBytesWrittenExcludingExceptions用于区分 packed 数据与异常列表的字节边界。十一、RVZ packing伪随机填充数据的无损编码GameCube / Wii 光盘中充斥着伪随机PRNG 生成的填充数据。WIA 只能原样存储或让它们占据压缩流而 RVZ 引入了一套编码方案可以无损地再生这些填充数据从而大幅提升压缩率。RVZ packing 可以作用于wia_group_t数据任何 bzip2 / LZMA / Zstandard 压缩都在其之上叠加即读取 RVZ 时先做 bzip2/LZMA/Zstandard 解压再解码 RVZ packing。解码流程读取 4 字节解释为 32 位无符号大端整数记为size若size的最高位未置位直接读取size字节并原样输出若size的最高位已置位清除最高位得到size然后读取68 字节 PRNG 种子数据按下面的 PRNG 算法输出size字节重复以上步骤直到输入全部读完。Dolphin 的写入端RVZPack()WIABlob.cpp与此完全对应非填充段写入swap32(non_junk_bytes) 原始字节填充段写入swap32(junk_bytes) | 0x80000000 68 字节种子rvz_packed_size累计为sizeof(u32) 段长。填充段复用allow_junk_reuse机制允许同一段填充数据引用已写过的种子。11.1 PRNG 算法Lagged Fibonacci Generator用于生成 GameCube / Wii 光盘填充数据的 PRNG 是一种滞后斐波那契生成器Lagged Fibonacci GeneratorLFG参数为f xorj 32k 521。初始化分配 521 个 32 位字的缓冲区u32 buffer[521];将 68 字节17 个字的种子数据拷贝到缓冲区起始处。RVZ 文件中种子数据以大端存储非大端系统需对每个字做字节交换Dolphin 的SetSeed即逐字调用Common::swap32见 LaggedFibonacciGenerator.cpp。随后用以下代码填充缓冲区其余部分for (size_t i 17; i 521; i) buffer[i] (buffer[i - 17] 23) ^ (buffer[i - 16] 9) ^ buffer[i - 1];推进状态以下代码使状态前进一个完整缓冲区长度。必须先运行 4 次才能开始输出数据之后每输出 521 个字运行一次。for (size_t i 0; i 32; i) buffer[i] ^ buffer[i 521 - 32]; for (size_t i 32; i 521; i) buffer[i] ^ buffer[i - 32];运行上述代码 4 次之后即可从缓冲区输出数据——但仅当输出位置的偏移能被 32 KiB 整除时wia_raw_data_t以光盘起始为基准wia_part_t以分区数据起始为基准。否则必须先让状态前进offset % 0x8000字节。注意wia_part_t的偏移不计哈希但基准仍然是 32 KiB 而非 31 KiB。输出一个字u8* out; u32* buffer_ptr; /* ... */ *(out) *buffer_ptr 24; *(out) *buffer_ptr 18; // NB: 18, not 16 *(out) *buffer_ptr 8; *(out) *buffer_ptr; buffer_ptr;注意输出第二字节时位移是18 而非 16——这是光盘原始生成算法的一个怪癖Dolphin 在 LaggedFibonacciGenerator.cpp 中专门处理了该位丢失问题Reinitialize()通过(m_buffer[i] 2 0x00FC0000) | ((m_buffer[i16] ^ m_buffer[i15]) 9 0x00030000)重建被位移 18 截掉的比特位前两个字的 bit 16/17 无法重建但位移 18 的输出结果不受影响。Dolphin 的LaggedFibonacciGenerator还实现了GetSeed()从已有数据反向恢复种子Backward()Reinitialize()并返回该种子能重建的字节数——这正是伪随机填充数据无损存储的核心写入时遇到疑似填充数据先尝试用 521 字数据反推种子若能重建则只存 68 字节种子读取时用同一 PRNG 原样再生。十二、Dolphin 读写实现速查若需深入源码可按以下路径继续追踪| 主题 | 源码位置 | |--|--| | 文件头、盘头、各条目结构体定义与static_assert| WIABlob.h | | 打开校验magic、版本、两级 SHA-1、文件大小 | WIABlob.cpp | | 压缩级别范围含 Zstd 负级别 | WIABlob.cpp | | 各解压器 / 压缩器NONE、PURGE、bzip2、LZMA、LZMA2、Zstd、RVZPack | WIACompression.h | | LFG 生成器种子重建、前向/后向推进、bit 18 位移修正 | LaggedFibonacciGenerator.cpp | | RVZ packing 写入非填充段/填充段、种子复用 | WIABlob.cpp | | Wii 哈希块布局0x400 哈希 0x7C00 数据 × 64 块/组 | VolumeWii.h | | 转换入口Convert()写文件头、disc_type、版本号 | WIABlob.cpp |Dolphin 的读者/写者以模板WIARVZFileReaderRVZ统一实现两种格式WIAFileReader WIARVZFileReaderfalseRVZFileReader WIARVZFileReadertrue见 WIABlob.h大量逻辑通过if constexpr/std::conditional_t在两种格式间共享这也解释了为何两种格式在结构上如此接近。十三、总结与读写兼容性要点WIA 与 RVZ 是围绕分块独立压缩 随机访问这一核心设计的镜像格式。WIA 通过解密、去哈希存储 Wii 分区数据0x7C00/0x8000 的比例获得高可压缩性并用哈希异常机制保证还原出的哈希与原始光盘完全一致RVZ 在其基础上引入 Zstandard、小块支持、组条目的 MSB 复用语义以及基于 Lagged Fibonacci 生成器的填充数据再生方案进一步压榨冗余。对实现者而言最容易踩坑的几个点值得牢记大端序所有整数均为大端包括 RVZ packing 中的 4 字节size头与 LFG 种子第一个 raw data 的偏移/大小错位0x80/0x4FF80 实际含 0x50000 字节正确做法是向下取整到 0x8000 对齐并补足大小data_sizeWIA与低 31 位RVZ为 0 时代表全零数据加空异常列表的捷径哈希异常的处理顺序先计算全部哈希、再替换异常、最后才加密异常上限写入方应确保单个wia_except_list_t不超过 3328 个异常覆盖哈希与填充以保证能被 Dolphin 读取LFG 的位 18 位移怪癖与先推进 4 次、每 521 字推进一次、偏移非 32 KiB 对齐时先前进余数的推进规则是 RVZ packing 解码正确性的关键。【免费下载链接】dolphinDolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.项目地址: https://gitcode.com/GitHub_Trending/do/dolphin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考