Files
SabreTools.Serialization/SabreTools.Serialization.Readers/CHD.cs
Matt Nadareski 8f49e190d8 Fix everything
2026-03-24 19:17:25 -04:00

273 lines
9.8 KiB
C#

using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.CHD;
using SabreTools.IO.Extensions;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Serialization.Readers
{
// TODO: Expand this to full CHD files eventually
public class CHD : BaseBinaryReader<Header>
{
/// <inheritdoc/>
public override Header? Deserialize(Stream? data)
{
// If the data is invalid
if (data is null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
// Determine the header version
uint version = GetVersion(data, initialOffset);
// Read and return the current CHD
switch (version)
{
case 1:
var headerV1 = ParseHeaderV1(data);
if (headerV1.Tag != Constants.SignatureString)
return null;
if (headerV1.Length != Constants.HeaderV1Size)
return null;
if (headerV1.Compression > CompressionType.CHDCOMPRESSION_ZLIB)
return null;
return headerV1;
case 2:
var headerV2 = ParseHeaderV2(data);
if (headerV2.Tag != Constants.SignatureString)
return null;
if (headerV2.Length != Constants.HeaderV2Size)
return null;
if (headerV2.Compression > CompressionType.CHDCOMPRESSION_ZLIB)
return null;
return headerV2;
case 3:
var headerV3 = ParseHeaderV3(data);
if (headerV3.Tag != Constants.SignatureString)
return null;
if (headerV3.Length != Constants.HeaderV3Size)
return null;
if (headerV3.Compression > CompressionType.CHDCOMPRESSION_ZLIB_PLUS)
return null;
return headerV3;
case 4:
var headerV4 = ParseHeaderV4(data);
if (headerV4.Tag != Constants.SignatureString)
return null;
if (headerV4.Length != Constants.HeaderV4Size)
return null;
if (headerV4.Compression > CompressionType.CHDCOMPRESSION_AV)
return null;
return headerV4;
case 5:
var headerV5 = ParseHeaderV5(data);
if (headerV5.Tag != Constants.SignatureString)
return null;
if (headerV5.Length != Constants.HeaderV5Size)
return null;
return headerV5;
default:
return null;
}
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Get the matching CHD version, if possible
/// </summary>
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
/// <returns>Matching version, 0 if none</returns>
private static uint GetVersion(Stream data, long initialOffset)
{
// Read the header values
byte[] tagBytes = data.ReadBytes(8);
string tag = Encoding.ASCII.GetString(tagBytes);
uint length = data.ReadUInt32BigEndian();
uint version = data.ReadUInt32BigEndian();
// Seek back to start
data.SeekIfPossible(initialOffset);
// Check the signature
if (!string.Equals(tag, Constants.SignatureString, StringComparison.Ordinal))
return 0;
// Match the version to header length
#if NET472_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return (version, length) switch
{
(1, Constants.HeaderV1Size) => version,
(2, Constants.HeaderV2Size) => version,
(3, Constants.HeaderV3Size) => version,
(4, Constants.HeaderV4Size) => version,
(5, Constants.HeaderV5Size) => version,
_ => 0,
};
#else
return version switch
{
1 => length == Constants.HeaderV1Size ? version : 0,
2 => length == Constants.HeaderV2Size ? version : 0,
3 => length == Constants.HeaderV3Size ? version : 0,
4 => length == Constants.HeaderV4Size ? version : 0,
5 => length == Constants.HeaderV5Size ? version : 0,
_ => 0,
};
#endif
}
/// <summary>
/// Parse a Stream into a HeaderV1
/// </summary>
public static HeaderV1 ParseHeaderV1(Stream data)
{
var obj = new HeaderV1();
byte[] tag = data.ReadBytes(8);
obj.Tag = Encoding.ASCII.GetString(tag);
obj.Length = data.ReadUInt32BigEndian();
obj.Version = data.ReadUInt32BigEndian();
obj.Flags = (Flags)data.ReadUInt32BigEndian();
obj.Compression = (CompressionType)data.ReadUInt32BigEndian();
obj.HunkSize = data.ReadUInt32BigEndian();
obj.TotalHunks = data.ReadUInt32BigEndian();
obj.Cylinders = data.ReadUInt32BigEndian();
obj.Heads = data.ReadUInt32BigEndian();
obj.Sectors = data.ReadUInt32BigEndian();
obj.MD5 = data.ReadBytes(16);
obj.ParentMD5 = data.ReadBytes(16);
return obj;
}
/// <summary>
/// Parse a Stream into a V2 header
/// </summary>
public static HeaderV2 ParseHeaderV2(Stream data)
{
var obj = new HeaderV2();
byte[] tag = data.ReadBytes(8);
obj.Tag = Encoding.ASCII.GetString(tag);
obj.Length = data.ReadUInt32BigEndian();
obj.Version = data.ReadUInt32BigEndian();
obj.Flags = (Flags)data.ReadUInt32BigEndian();
obj.Compression = (CompressionType)data.ReadUInt32BigEndian();
obj.HunkSize = data.ReadUInt32BigEndian();
obj.TotalHunks = data.ReadUInt32BigEndian();
obj.Cylinders = data.ReadUInt32BigEndian();
obj.Heads = data.ReadUInt32BigEndian();
obj.Sectors = data.ReadUInt32BigEndian();
obj.MD5 = data.ReadBytes(16);
obj.ParentMD5 = data.ReadBytes(16);
obj.BytesPerSector = data.ReadUInt32BigEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a V3 header
/// </summary>
public static HeaderV3 ParseHeaderV3(Stream data)
{
var obj = new HeaderV3();
byte[] tag = data.ReadBytes(8);
obj.Tag = Encoding.ASCII.GetString(tag);
obj.Length = data.ReadUInt32BigEndian();
obj.Version = data.ReadUInt32BigEndian();
obj.Flags = (Flags)data.ReadUInt32BigEndian();
obj.Compression = (CompressionType)data.ReadUInt32BigEndian();
obj.TotalHunks = data.ReadUInt32BigEndian();
obj.LogicalBytes = data.ReadUInt64BigEndian();
obj.MetaOffset = data.ReadUInt64BigEndian();
obj.MD5 = data.ReadBytes(16);
obj.ParentMD5 = data.ReadBytes(16);
obj.HunkBytes = data.ReadUInt32BigEndian();
obj.SHA1 = data.ReadBytes(20);
obj.ParentSHA1 = data.ReadBytes(20);
return obj;
}
/// <summary>
/// Parse a Stream into a V4 header
/// </summary>
public static HeaderV4 ParseHeaderV4(Stream data)
{
var obj = new HeaderV4();
byte[] tag = data.ReadBytes(8);
obj.Tag = Encoding.ASCII.GetString(tag);
obj.Length = data.ReadUInt32BigEndian();
obj.Version = data.ReadUInt32BigEndian();
obj.Flags = (Flags)data.ReadUInt32BigEndian();
obj.Compression = (CompressionType)data.ReadUInt32BigEndian();
obj.TotalHunks = data.ReadUInt32BigEndian();
obj.LogicalBytes = data.ReadUInt64BigEndian();
obj.MetaOffset = data.ReadUInt64BigEndian();
obj.HunkBytes = data.ReadUInt32BigEndian();
obj.SHA1 = data.ReadBytes(20);
obj.ParentSHA1 = data.ReadBytes(20);
obj.RawSHA1 = data.ReadBytes(20);
return obj;
}
/// <summary>
/// Parse a Stream into a V5 header
/// </summary>
public static HeaderV5 ParseHeaderV5(Stream data)
{
var obj = new HeaderV5();
byte[] tag = data.ReadBytes(8);
obj.Tag = Encoding.ASCII.GetString(tag);
obj.Length = data.ReadUInt32BigEndian();
obj.Version = data.ReadUInt32BigEndian();
obj.Compressors = new CodecType[4];
for (int i = 0; i < 4; i++)
{
obj.Compressors[i] = (CodecType)data.ReadUInt32BigEndian();
}
obj.LogicalBytes = data.ReadUInt64BigEndian();
obj.MapOffset = data.ReadUInt64BigEndian();
obj.MetaOffset = data.ReadUInt64BigEndian();
obj.HunkBytes = data.ReadUInt32BigEndian();
obj.UnitBytes = data.ReadUInt32BigEndian();
obj.RawSHA1 = data.ReadBytes(20);
obj.SHA1 = data.ReadBytes(20);
obj.ParentSHA1 = data.ReadBytes(20);
return obj;
}
}
}