mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
First round of cleanup for DolphinLib additions
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using System.IO;
|
||||
using SabreTools.Data.Models.GCZ;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
#pragma warning disable IDE0017 // Simplify object initialization
|
||||
@@ -31,6 +30,7 @@ namespace SabreTools.Serialization.Readers
|
||||
return null;
|
||||
|
||||
// Validate block count — guard against absurdly large tables
|
||||
// TODO: Determine if this block count is arbitrary
|
||||
if (archive.Header.NumBlocks == 0 || archive.Header.NumBlocks > 0x100000)
|
||||
return null;
|
||||
|
||||
@@ -38,15 +38,17 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Read block pointer table (8 bytes per block)
|
||||
archive.BlockPointers = new ulong[numBlocks];
|
||||
byte[] ptrBuf = data.ReadBytes(numBlocks * 8);
|
||||
for (int i = 0; i < numBlocks; i++)
|
||||
archive.BlockPointers[i] = System.BitConverter.ToUInt64(ptrBuf, i * 8);
|
||||
{
|
||||
archive.BlockPointers[i] = data.ReadUInt64LittleEndian();
|
||||
}
|
||||
|
||||
// Read block hash table (4 bytes per block, Adler-32)
|
||||
archive.BlockHashes = new uint[numBlocks];
|
||||
byte[] hashBuf = data.ReadBytes(numBlocks * 4);
|
||||
for (int i = 0; i < numBlocks; i++)
|
||||
archive.BlockHashes[i] = System.BitConverter.ToUInt32(hashBuf, i * 4);
|
||||
{
|
||||
archive.BlockHashes[i] = data.ReadUInt32LittleEndian();
|
||||
}
|
||||
|
||||
return archive;
|
||||
}
|
||||
@@ -56,16 +58,23 @@ namespace SabreTools.Serialization.Readers
|
||||
}
|
||||
}
|
||||
|
||||
private static GczHeader ParseGczHeader(Stream data)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a GczHeader
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled GczHeader on success, null on error</returns>
|
||||
public static GczHeader ParseGczHeader(Stream data)
|
||||
{
|
||||
var header = new GczHeader();
|
||||
header.MagicCookie = data.ReadUInt32LittleEndian();
|
||||
header.SubType = data.ReadUInt32LittleEndian();
|
||||
header.CompressedDataSize = data.ReadUInt64LittleEndian();
|
||||
header.DataSize = data.ReadUInt64LittleEndian();
|
||||
header.BlockSize = data.ReadUInt32LittleEndian();
|
||||
header.NumBlocks = data.ReadUInt32LittleEndian();
|
||||
return header;
|
||||
var obj = new GczHeader();
|
||||
|
||||
obj.MagicCookie = data.ReadUInt32LittleEndian();
|
||||
obj.SubType = data.ReadUInt32LittleEndian();
|
||||
obj.CompressedDataSize = data.ReadUInt64LittleEndian();
|
||||
obj.DataSize = data.ReadUInt64LittleEndian();
|
||||
obj.BlockSize = data.ReadUInt32LittleEndian();
|
||||
obj.NumBlocks = data.ReadUInt32LittleEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Extensions;
|
||||
using SabreTools.Data.Models.NintendoDisc;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
@@ -31,31 +33,27 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Determine platform from magic words; fall back to GameId prefix for
|
||||
// GC discs that omit the magic word (e.g. some redump/scene ISOs)
|
||||
if (disc.Header.WiiMagic == Constants.WiiMagicWord)
|
||||
disc.Platform = Platform.Wii;
|
||||
else if (disc.Header.GCMagic == Constants.GCMagicWord)
|
||||
disc.Platform = Platform.GameCube;
|
||||
else if (disc.Header.GameId != null && disc.Header.GameId.Length >= 1
|
||||
&& IsGameCubeTitleType(disc.Header.GameId[0]))
|
||||
disc.Platform = Platform.GameCube;
|
||||
else
|
||||
disc.Platform = Platform.Unknown;
|
||||
Platform platform = disc.Header.GetPlatform();
|
||||
|
||||
// Parse Wii-specific structures
|
||||
if (disc.Platform == Platform.Wii)
|
||||
if (platform == Platform.Wii)
|
||||
{
|
||||
// Partition table starts at 0x40000
|
||||
long partTableEnd = initialOffset + Constants.WiiPartitionTableAddress
|
||||
long partTableEnd = initialOffset
|
||||
+ Constants.WiiPartitionTableAddress
|
||||
+ (Constants.WiiPartitionGroupCount * 8);
|
||||
if (data.Length >= partTableEnd)
|
||||
if (partTableEnd < data.Length)
|
||||
{
|
||||
data.Seek(initialOffset + Constants.WiiPartitionTableAddress, SeekOrigin.Begin);
|
||||
disc.PartitionTableEntries = ParsePartitionTable(data, initialOffset);
|
||||
}
|
||||
|
||||
// Region data at 0x4E000
|
||||
long regionEnd = initialOffset + Constants.WiiRegionDataAddress + Constants.WiiRegionDataSize;
|
||||
if (data.Length >= regionEnd)
|
||||
if (regionEnd < data.Length)
|
||||
{
|
||||
data.Seek(initialOffset + Constants.WiiRegionDataAddress, SeekOrigin.Begin);
|
||||
disc.RegionData = ParseRegionData(data);
|
||||
disc.RegionData = ParseWiiRegionData(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,139 +65,215 @@ namespace SabreTools.Serialization.Readers
|
||||
}
|
||||
}
|
||||
|
||||
#region Header parsing
|
||||
|
||||
/// <summary>
|
||||
/// Parses just the disc header fields from the given stream without requiring
|
||||
/// the full 0x440-byte boot block. Requires at least 0x82 bytes (enough to
|
||||
/// reach AudioStreaming and StreamingBufferSize) to be useful; the DOL/FST
|
||||
/// fields will be zero when the stream is shorter than 0x42B bytes.
|
||||
/// Parse a Stream into a DiscHeader
|
||||
/// </summary>
|
||||
public static DiscHeader? ParseDiscHeaderOnly(Stream? data)
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled DiscHeader on success, null on error</returns>
|
||||
public static DiscHeader ParseDiscHeader(Stream data)
|
||||
{
|
||||
if (data is null || !data.CanRead || data.Length - data.Position < 6)
|
||||
return null;
|
||||
try { return ParseDiscHeader(data); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private static DiscHeader ParseDiscHeader(Stream data)
|
||||
{
|
||||
var header = new DiscHeader();
|
||||
var obj = new DiscHeader();
|
||||
|
||||
// 0x000: 4-char title code + 2-char maker code stored as one 6-byte GameId field
|
||||
byte[] gameIdBytes = data.ReadBytes(Constants.GameIdLength);
|
||||
header.GameId = Encoding.ASCII.GetString(gameIdBytes).TrimEnd('\0');
|
||||
byte[] gameIdBytes = data.ReadBytes(6);
|
||||
obj.GameId = Encoding.ASCII.GetString(gameIdBytes);
|
||||
|
||||
// Maker code is the last 2 chars of the GameId (offsets 0x004–0x005).
|
||||
// Dolphin reads it with Read(0x4, 2) — there is no separate field at 0x006.
|
||||
header.MakerCode = header.GameId != null && header.GameId.Length >= 6
|
||||
? header.GameId.Substring(4, 2)
|
||||
: string.Empty;
|
||||
obj.DiscNumber = data.ReadByteValue();
|
||||
obj.DiscVersion = data.ReadByteValue();
|
||||
obj.AudioStreaming = data.ReadByteValue();
|
||||
obj.StreamingBufferSize = data.ReadByteValue();
|
||||
obj.Padding00A = data.ReadBytes(0x0E);
|
||||
obj.WiiMagic = data.ReadUInt32BigEndian();
|
||||
obj.GCMagic = data.ReadUInt32BigEndian();
|
||||
|
||||
// 0x006: disc number, 0x007: revision (Dolphin GetDiscNumber/GetRevision)
|
||||
header.DiscNumber = data.ReadByteValue();
|
||||
header.DiscVersion = data.ReadByteValue();
|
||||
// 0x008: audio streaming, 0x009: streaming buffer size
|
||||
header.AudioStreaming = data.ReadByteValue();
|
||||
header.StreamingBufferSize = data.ReadByteValue();
|
||||
byte[] titleBytes = data.ReadBytes(0x60);
|
||||
obj.GameTitle = Encoding.ASCII.GetString(titleBytes);
|
||||
|
||||
// Skip unused 0x0E bytes (offsets 0x00A–0x017)
|
||||
data.ReadBytes(0x0E);
|
||||
obj.DisableHashVerification = data.ReadByteValue();
|
||||
obj.DisableDiscEncryption = data.ReadByteValue();
|
||||
obj.Padding082 = data.ReadBytes(0x39E);
|
||||
obj.DolOffset = data.ReadUInt32BigEndian();
|
||||
obj.FstOffset = data.ReadUInt32BigEndian();
|
||||
obj.FstSize = data.ReadUInt32BigEndian();
|
||||
obj.Padding42C = data.ReadBytes(0x14);
|
||||
|
||||
header.WiiMagic = data.ReadUInt32BigEndian();
|
||||
header.GCMagic = data.ReadUInt32BigEndian();
|
||||
|
||||
byte[] titleBytes = data.ReadBytes(Constants.GameTitleLength);
|
||||
header.GameTitle = Encoding.ASCII.GetString(titleBytes).TrimEnd('\0');
|
||||
|
||||
header.DisableHashVerification = data.Position < data.Length ? data.ReadByteValue() : (byte)0;
|
||||
header.DisableDiscEncryption = data.Position < data.Length ? data.ReadByteValue() : (byte)0;
|
||||
|
||||
// Skip to DOL/FST offset fields at 0x420.
|
||||
// Position so far: 6+1+1+1+1+14+4+4+96+1+1 = 130 = 0x82
|
||||
int skipToBootBlock = Constants.DolOffsetField - 0x82;
|
||||
if (data.Length - data.Position < skipToBootBlock + 12)
|
||||
return header;
|
||||
|
||||
data.ReadBytes(skipToBootBlock);
|
||||
|
||||
header.DolOffset = data.ReadUInt32BigEndian();
|
||||
header.FstOffset = data.ReadUInt32BigEndian();
|
||||
header.FstSize = data.ReadUInt32BigEndian();
|
||||
|
||||
// Skip the remaining bytes to complete the 0x440 header
|
||||
// We are at 0x420 + 12 = 0x42C; need to reach 0x440
|
||||
data.ReadBytes(Constants.DiscHeaderSize - (Constants.DolOffsetField + 12));
|
||||
|
||||
return header;
|
||||
return obj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wii partition table parsing
|
||||
|
||||
private static WiiPartitionTableEntry[]? ParsePartitionTable(Stream data, long baseOffset)
|
||||
{
|
||||
data.Seek(baseOffset + Constants.WiiPartitionTableAddress, SeekOrigin.Begin);
|
||||
|
||||
// Read 4 partition groups; each group has a count and a shifted offset
|
||||
var allEntries = new System.Collections.Generic.List<WiiPartitionTableEntry>();
|
||||
|
||||
for (int g = 0; g < Constants.WiiPartitionGroupCount; g++)
|
||||
{
|
||||
uint count = data.ReadUInt32BigEndian();
|
||||
uint shiftedOffset = data.ReadUInt32BigEndian();
|
||||
|
||||
if (count == 0)
|
||||
continue;
|
||||
|
||||
long tableOffset = baseOffset + ((long)shiftedOffset << 2);
|
||||
long savedPosition = data.Position;
|
||||
|
||||
if (tableOffset + ((long)count * 8) > data.Length)
|
||||
{
|
||||
data.Seek(savedPosition, SeekOrigin.Begin);
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Seek(tableOffset, SeekOrigin.Begin);
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var entry = new WiiPartitionTableEntry();
|
||||
uint rawOffset = data.ReadUInt32BigEndian();
|
||||
entry.Offset = (long)rawOffset << 2;
|
||||
entry.Type = data.ReadUInt32BigEndian();
|
||||
allEntries.Add(entry);
|
||||
}
|
||||
|
||||
data.Seek(savedPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return allEntries.Count > 0 ? allEntries.ToArray() : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wii region data parsing
|
||||
|
||||
private static WiiRegionData ParseRegionData(Stream data)
|
||||
{
|
||||
var region = new WiiRegionData();
|
||||
region.RegionSetting = data.ReadUInt32BigEndian();
|
||||
region.AgeRatings = data.ReadBytes(16);
|
||||
return region;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the GameId first character is a known GameCube title type prefix.
|
||||
/// Used as a fallback when the GC magic word is absent from the disc image.
|
||||
/// Parse a byte array into a DOLHeader
|
||||
/// </summary>
|
||||
private static bool IsGameCubeTitleType(char c)
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled DOLHeader on success, null on error</returns>
|
||||
public static DOLHeader ParseDOLHeader(byte[] data, ref int offset)
|
||||
{
|
||||
return c == 'G' || c == 'D' || c == 'R';
|
||||
var obj = new DOLHeader();
|
||||
|
||||
obj.SectionOffsetTable = new uint[18];
|
||||
for (int i = 0; i < obj.SectionOffsetTable.Length; i++)
|
||||
{
|
||||
obj.SectionOffsetTable[i] = data.ReadUInt32BigEndian(ref offset);
|
||||
}
|
||||
|
||||
obj.SectionAddressTable = new uint[18];
|
||||
for (int i = 0; i < obj.SectionAddressTable.Length; i++)
|
||||
{
|
||||
obj.SectionAddressTable[i] = data.ReadUInt32BigEndian(ref offset);
|
||||
}
|
||||
|
||||
obj.SectionLengthsTable = new uint[18];
|
||||
for (int i = 0; i < obj.SectionLengthsTable.Length; i++)
|
||||
{
|
||||
obj.SectionLengthsTable[i] = data.ReadUInt32BigEndian(ref offset);
|
||||
}
|
||||
|
||||
obj.BSSAddress = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.BSSLength = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.EntryPoint = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.Padding = data.ReadBytes(ref offset, 0x1C);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a ParseFileSystemTable
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled ParseFileSystemTable on success, null on error</returns>
|
||||
public static FileSystemTable? ParseFileSystemTable(byte[] data)
|
||||
{
|
||||
// Check that the root entry exists
|
||||
if (data.Length < 12)
|
||||
return null;
|
||||
|
||||
var obj = new FileSystemTable();
|
||||
|
||||
// Read the root entry first
|
||||
int offset = 0;
|
||||
obj.Unknown = data.ReadBytes(ref offset, 8);
|
||||
obj.EntryCount = data.ReadUInt32BigEndian(ref offset);
|
||||
if (obj.EntryCount < 1 || (obj.EntryCount * 12) > data.Length)
|
||||
return null;
|
||||
|
||||
// Read all entries
|
||||
offset = 0;
|
||||
obj.Entries = new FileSystemTableEntry[obj.EntryCount];
|
||||
for (int i = 0; i < obj.Entries.Length; i++)
|
||||
{
|
||||
obj.Entries[i] = ParseFileSystemTableEntry(data, ref offset);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a FileSystemTableEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled FileSystemTableEntry on success, null on error</returns>
|
||||
public static FileSystemTableEntry ParseFileSystemTableEntry(byte[] data, ref int offset)
|
||||
{
|
||||
var obj = new FileSystemTableEntry();
|
||||
|
||||
obj.NameOffset = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.FileOffset = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.FileSize = data.ReadUInt32BigEndian(ref offset);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a partition table
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled partition table on success, null on error</returns>
|
||||
public static WiiPartitionTableEntry[]? ParsePartitionTable(Stream data, long initialOffset)
|
||||
{
|
||||
// Read 4 partition groups; each group has a count and a shifted offset
|
||||
var allEntries = new List<WiiPartitionTableEntry>();
|
||||
|
||||
for (int i = 0; i < Constants.WiiPartitionGroupCount; i++)
|
||||
{
|
||||
var group = ParseWiiPartitionGroup(data, initialOffset);
|
||||
if (group.Count == 0)
|
||||
continue;
|
||||
|
||||
// TODO: Keep group entries separate
|
||||
allEntries.AddRange(group.Entries);
|
||||
}
|
||||
|
||||
return allEntries.Count > 0 ? [.. allEntries] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiiPartitionGroup
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="initialOffset">Initial offset into the stream</param>
|
||||
/// <returns>Filled WiiPartitionTableEntry on success, null on error</returns>
|
||||
public static WiiPartitionGroup ParseWiiPartitionGroup(Stream data, long initialOffset)
|
||||
{
|
||||
var obj = new WiiPartitionGroup();
|
||||
|
||||
obj.Count = data.ReadUInt32BigEndian();
|
||||
obj.Offset = data.ReadUInt32BigEndian();
|
||||
|
||||
// Empty groups should not attempt parsing
|
||||
if (obj.Count == 0)
|
||||
return obj;
|
||||
|
||||
// Determine the table offset
|
||||
long tableOffset = initialOffset + (obj.Offset << 2);
|
||||
if (tableOffset + (obj.Count * 8) > data.Length)
|
||||
return obj;
|
||||
|
||||
// Seek to the table, if possible
|
||||
long savedPosition = data.Position;
|
||||
data.Seek(tableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Parse the table
|
||||
List<WiiPartitionTableEntry> entries = [];
|
||||
for (uint i = 0; i < obj.Count; i++)
|
||||
{
|
||||
var entry = ParseWiiPartitionTableEntry(data);
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
// Set the entries and reset the stream
|
||||
obj.Entries = [.. entries];
|
||||
data.Seek(savedPosition, SeekOrigin.Begin);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiiPartitionTableEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled WiiPartitionTableEntry on success, null on error</returns>
|
||||
public static WiiPartitionTableEntry ParseWiiPartitionTableEntry(Stream data)
|
||||
{
|
||||
var obj = new WiiPartitionTableEntry();
|
||||
|
||||
obj.Offset = data.ReadUInt32BigEndian();
|
||||
obj.Type = data.ReadUInt32BigEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiiRegionData
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled WiiRegionData on success, null on error</returns>
|
||||
public static WiiRegionData ParseWiiRegionData(Stream data)
|
||||
{
|
||||
var obj = new WiiRegionData();
|
||||
|
||||
obj.RegionSetting = data.ReadUInt32BigEndian();
|
||||
obj.AgeRatings = data.ReadBytes(16);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,44 +26,57 @@ namespace SabreTools.Serialization.Readers
|
||||
var archive = new DiscImage();
|
||||
|
||||
// Parse Header1
|
||||
archive.Header1 = ParseHeader1(data);
|
||||
|
||||
// Validate magic
|
||||
archive.Header1 = ParseWiaHeader1(data);
|
||||
if (archive.Header1.Magic != Constants.WiaMagic && archive.Header1.Magic != Constants.RvzMagic)
|
||||
return null;
|
||||
|
||||
// Parse Header2
|
||||
archive.Header2 = ParseHeader2(data);
|
||||
archive.Header2 = ParseWiaHeader2(data);
|
||||
|
||||
// Parse partition entries (Wii discs only)
|
||||
if (archive.Header2.NumberOfPartitionEntries > 0
|
||||
&& archive.Header2.PartitionEntriesOffset > 0)
|
||||
if (archive.Header2.NumberOfPartitionEntries > 0 && archive.Header2.PartitionEntriesOffset > 0)
|
||||
{
|
||||
data.Seek(initialOffset + (long)archive.Header2.PartitionEntriesOffset, SeekOrigin.Begin);
|
||||
archive.PartitionEntries = ParsePartitionEntries(
|
||||
data, (int)archive.Header2.NumberOfPartitionEntries);
|
||||
|
||||
archive.PartitionEntries = new PartitionEntry[archive.Header2.NumberOfPartitionEntries];
|
||||
for (int i = 0; i < archive.PartitionEntries.Length; i++)
|
||||
{
|
||||
archive.PartitionEntries[i] = ParsePartitionEntry(data); ;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse raw data entries
|
||||
if (archive.Header2.NumberOfRawDataEntries > 0
|
||||
&& archive.Header2.RawDataEntriesOffset > 0)
|
||||
if (archive.Header2.NumberOfRawDataEntries > 0 && archive.Header2.RawDataEntriesOffset > 0)
|
||||
{
|
||||
data.Seek(initialOffset + (long)archive.Header2.RawDataEntriesOffset, SeekOrigin.Begin);
|
||||
archive.RawDataEntries = ParseRawDataEntries(
|
||||
data, (int)archive.Header2.NumberOfRawDataEntries);
|
||||
|
||||
archive.RawDataEntries = new RawDataEntry[archive.Header2.NumberOfRawDataEntries];
|
||||
for (int i = 0; i < archive.Header2.NumberOfRawDataEntries; i++)
|
||||
{
|
||||
archive.RawDataEntries[i] = ParseRawDataEntry(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse group entries
|
||||
if (archive.Header2.NumberOfGroupEntries > 0
|
||||
&& archive.Header2.GroupEntriesOffset > 0)
|
||||
if (archive.Header2.NumberOfGroupEntries > 0 && archive.Header2.GroupEntriesOffset > 0)
|
||||
{
|
||||
data.Seek(initialOffset + (long)archive.Header2.GroupEntriesOffset, SeekOrigin.Begin);
|
||||
if (archive.Header1.Magic == Constants.RvzMagic)
|
||||
archive.RvzGroupEntries = ParseRvzGroupEntries(
|
||||
data, (int)archive.Header2.NumberOfGroupEntries);
|
||||
{
|
||||
archive.RvzGroupEntries = new RvzGroupEntry[archive.Header2.NumberOfGroupEntries];
|
||||
for (int i = 0; i < archive.Header2.NumberOfGroupEntries; i++)
|
||||
{
|
||||
archive.RvzGroupEntries[i] = ParseRvzGroupEntry(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
archive.GroupEntries = ParseWiaGroupEntries(
|
||||
data, (int)archive.Header2.NumberOfGroupEntries);
|
||||
{
|
||||
archive.GroupEntries = new WiaGroupEntry[archive.Header2.NumberOfGroupEntries];
|
||||
for (int i = 0; i < archive.Header2.NumberOfGroupEntries; i++)
|
||||
{
|
||||
archive.GroupEntries[i] = ParseWiaGroupEntry(data); ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return archive;
|
||||
@@ -74,121 +87,184 @@ namespace SabreTools.Serialization.Readers
|
||||
}
|
||||
}
|
||||
|
||||
#region Header parsing
|
||||
|
||||
private static WiaHeader1 ParseHeader1(Stream data)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a PartitionDataEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled PartitionDataEntry on success, null on error</returns>
|
||||
public static PartitionDataEntry ParsePartitionDataEntry(Stream data)
|
||||
{
|
||||
var h = new WiaHeader1();
|
||||
h.Magic = data.ReadUInt32LittleEndian();
|
||||
h.Version = data.ReadUInt32BigEndian();
|
||||
h.VersionCompatible = data.ReadUInt32BigEndian();
|
||||
h.Header2Size = data.ReadUInt32BigEndian();
|
||||
h.Header2Hash = data.ReadBytes(20);
|
||||
h.IsoFileSize = data.ReadUInt64BigEndian();
|
||||
h.WiaFileSize = data.ReadUInt64BigEndian();
|
||||
h.Header1Hash = data.ReadBytes(20);
|
||||
return h;
|
||||
var obj = new PartitionDataEntry();
|
||||
|
||||
obj.FirstSector = data.ReadUInt32BigEndian();
|
||||
obj.NumberOfSectors = data.ReadUInt32BigEndian();
|
||||
obj.GroupIndex = data.ReadUInt32BigEndian();
|
||||
obj.NumberOfGroups = data.ReadUInt32BigEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static WiaHeader2 ParseHeader2(Stream data)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a PartitionEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled PartitionEntry on success, null on error</returns>
|
||||
public static PartitionEntry ParsePartitionEntry(Stream data)
|
||||
{
|
||||
var h = new WiaHeader2();
|
||||
h.DiscType = (WiaDiscType)data.ReadUInt32BigEndian();
|
||||
h.CompressionType = (WiaRvzCompressionType)data.ReadUInt32BigEndian();
|
||||
h.CompressionLevel = data.ReadInt32BigEndian();
|
||||
h.ChunkSize = data.ReadUInt32BigEndian();
|
||||
h.DiscHeader = data.ReadBytes(0x80);
|
||||
h.NumberOfPartitionEntries = data.ReadUInt32BigEndian();
|
||||
h.PartitionEntrySize = data.ReadUInt32BigEndian();
|
||||
h.PartitionEntriesOffset = data.ReadUInt64BigEndian();
|
||||
h.PartitionEntriesHash = data.ReadBytes(20);
|
||||
h.NumberOfRawDataEntries = data.ReadUInt32BigEndian();
|
||||
h.RawDataEntriesOffset = data.ReadUInt64BigEndian();
|
||||
h.RawDataEntriesSize = data.ReadUInt32BigEndian();
|
||||
h.NumberOfGroupEntries = data.ReadUInt32BigEndian();
|
||||
h.GroupEntriesOffset = data.ReadUInt64BigEndian();
|
||||
h.GroupEntriesSize = data.ReadUInt32BigEndian();
|
||||
h.CompressorDataSize = data.ReadByteValue();
|
||||
h.CompressorData = data.ReadBytes(7);
|
||||
return h;
|
||||
var obj = new PartitionEntry();
|
||||
|
||||
obj.PartitionKey = data.ReadBytes(16);
|
||||
obj.DataEntry0 = ParsePartitionDataEntry(data);
|
||||
obj.DataEntry1 = ParsePartitionDataEntry(data);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Table parsing
|
||||
|
||||
private static PartitionEntry[] ParsePartitionEntries(Stream data, int count)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a RawDataEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled RawDataEntry on success, null on error</returns>
|
||||
public static RawDataEntry ParseRawDataEntry(Stream data)
|
||||
{
|
||||
var entries = new PartitionEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var e = new PartitionEntry();
|
||||
e.PartitionKey = data.ReadBytes(16);
|
||||
e.DataEntry0 = ParsePartitionDataEntry(data);
|
||||
e.DataEntry1 = ParsePartitionDataEntry(data);
|
||||
entries[i] = e;
|
||||
}
|
||||
var obj = new RawDataEntry();
|
||||
|
||||
return entries;
|
||||
obj.DataOffset = data.ReadUInt64BigEndian();
|
||||
obj.DataSize = data.ReadUInt64BigEndian();
|
||||
obj.GroupIndex = data.ReadUInt32BigEndian();
|
||||
obj.NumberOfGroups = data.ReadUInt32BigEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static PartitionDataEntry ParsePartitionDataEntry(Stream data)
|
||||
/// <summary>
|
||||
/// Parse a byte array into a RawDataEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled RawDataEntry on success, null on error</returns>
|
||||
public static RawDataEntry ParseRawDataEntry(byte[] data, ref int offset)
|
||||
{
|
||||
var e = new PartitionDataEntry();
|
||||
e.FirstSector = data.ReadUInt32BigEndian();
|
||||
e.NumberOfSectors = data.ReadUInt32BigEndian();
|
||||
e.GroupIndex = data.ReadUInt32BigEndian();
|
||||
e.NumberOfGroups = data.ReadUInt32BigEndian();
|
||||
return e;
|
||||
var obj = new RawDataEntry();
|
||||
|
||||
obj.DataOffset = data.ReadUInt64BigEndian(ref offset);
|
||||
obj.DataSize = data.ReadUInt64BigEndian(ref offset);
|
||||
obj.GroupIndex = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.NumberOfGroups = data.ReadUInt32BigEndian(ref offset);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static RawDataEntry[] ParseRawDataEntries(Stream data, int count)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a RvzGroupEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled RvzGroupEntry on success, null on error</returns>
|
||||
public static RvzGroupEntry ParseRvzGroupEntry(Stream data)
|
||||
{
|
||||
var entries = new RawDataEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var e = new RawDataEntry();
|
||||
e.DataOffset = data.ReadUInt64BigEndian();
|
||||
e.DataSize = data.ReadUInt64BigEndian();
|
||||
e.GroupIndex = data.ReadUInt32BigEndian();
|
||||
e.NumberOfGroups = data.ReadUInt32BigEndian();
|
||||
entries[i] = e;
|
||||
}
|
||||
var obj = new RvzGroupEntry();
|
||||
|
||||
return entries;
|
||||
obj.DataOffset = data.ReadUInt32BigEndian();
|
||||
obj.DataSize = data.ReadUInt32BigEndian();
|
||||
obj.RvzPackedSize = data.ReadUInt32BigEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static WiaGroupEntry[] ParseWiaGroupEntries(Stream data, int count)
|
||||
/// <summary>
|
||||
/// Parse a byte array into a RvzGroupEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled RvzGroupEntry on success, null on error</returns>
|
||||
public static RvzGroupEntry ParseRvzGroupEntry(byte[] data, ref int offset)
|
||||
{
|
||||
var entries = new WiaGroupEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var e = new WiaGroupEntry();
|
||||
// DataOffset stored as actual_offset >> 2
|
||||
e.DataOffset = (ulong)data.ReadUInt32BigEndian() << 2;
|
||||
e.DataSize = data.ReadUInt32BigEndian();
|
||||
entries[i] = e;
|
||||
}
|
||||
var obj = new RvzGroupEntry();
|
||||
|
||||
return entries;
|
||||
obj.DataOffset = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.DataSize = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.RvzPackedSize = data.ReadUInt32BigEndian(ref offset);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static RvzGroupEntry[] ParseRvzGroupEntries(Stream data, int count)
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiaGroupEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled WiaGroupEntry on success, null on error</returns>
|
||||
public static WiaGroupEntry ParseWiaGroupEntry(Stream data)
|
||||
{
|
||||
var entries = new RvzGroupEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var e = new RvzGroupEntry();
|
||||
// DataOffset stored as actual_offset >> 2
|
||||
e.DataOffset = (ulong)data.ReadUInt32BigEndian() << 2;
|
||||
e.DataSize = data.ReadUInt32BigEndian();
|
||||
e.RvzPackedSize = data.ReadUInt32BigEndian();
|
||||
entries[i] = e;
|
||||
}
|
||||
var obj = new WiaGroupEntry();
|
||||
|
||||
return entries;
|
||||
obj.DataOffset = data.ReadUInt32BigEndian();
|
||||
obj.DataSize = data.ReadUInt32BigEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Parse a byte array into a WiaGroupEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <returns>Filled WiaGroupEntry on success, null on error</returns>
|
||||
public static WiaGroupEntry ParseWiaGroupEntry(byte[] data, ref int offset)
|
||||
{
|
||||
var obj = new WiaGroupEntry();
|
||||
|
||||
obj.DataOffset = data.ReadUInt32BigEndian(ref offset);
|
||||
obj.DataSize = data.ReadUInt32BigEndian(ref offset);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiaHeader1
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled WiaHeader1 on success, null on error</returns>
|
||||
public static WiaHeader1 ParseWiaHeader1(Stream data)
|
||||
{
|
||||
var obj = new WiaHeader1();
|
||||
|
||||
obj.Magic = data.ReadUInt32LittleEndian();
|
||||
obj.Version = data.ReadUInt32BigEndian();
|
||||
obj.VersionCompatible = data.ReadUInt32BigEndian();
|
||||
obj.Header2Size = data.ReadUInt32BigEndian();
|
||||
obj.Header2Hash = data.ReadBytes(20);
|
||||
obj.IsoFileSize = data.ReadUInt64BigEndian();
|
||||
obj.WiaFileSize = data.ReadUInt64BigEndian();
|
||||
obj.Header1Hash = data.ReadBytes(20);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a WiaHeader2
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled WiaHeader2 on success, null on error</returns>
|
||||
public static WiaHeader2 ParseWiaHeader2(Stream data)
|
||||
{
|
||||
var obj = new WiaHeader2();
|
||||
|
||||
obj.DiscType = (WiaDiscType)data.ReadUInt32BigEndian();
|
||||
obj.CompressionType = (WiaRvzCompressionType)data.ReadUInt32BigEndian();
|
||||
obj.CompressionLevel = data.ReadInt32BigEndian();
|
||||
obj.ChunkSize = data.ReadUInt32BigEndian();
|
||||
obj.DiscHeader = data.ReadBytes(0x80);
|
||||
obj.NumberOfPartitionEntries = data.ReadUInt32BigEndian();
|
||||
obj.PartitionEntrySize = data.ReadUInt32BigEndian();
|
||||
obj.PartitionEntriesOffset = data.ReadUInt64BigEndian();
|
||||
obj.PartitionEntriesHash = data.ReadBytes(20);
|
||||
obj.NumberOfRawDataEntries = data.ReadUInt32BigEndian();
|
||||
obj.RawDataEntriesOffset = data.ReadUInt64BigEndian();
|
||||
obj.RawDataEntriesSize = data.ReadUInt32BigEndian();
|
||||
obj.NumberOfGroupEntries = data.ReadUInt32BigEndian();
|
||||
obj.GroupEntriesOffset = data.ReadUInt64BigEndian();
|
||||
obj.GroupEntriesSize = data.ReadUInt32BigEndian();
|
||||
obj.CompressorDataSize = data.ReadByteValue();
|
||||
obj.CompressorData = data.ReadBytes(7);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user