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
{ /// 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; } } /// /// Get the matching CHD version, if possible /// /// Initial offset to use in address comparisons /// Matching version, 0 if none 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 } /// /// Parse a Stream into a HeaderV1 /// 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; } /// /// Parse a Stream into a V2 header /// 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; } /// /// Parse a Stream into a V3 header /// 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; } /// /// Parse a Stream into a V4 header /// 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; } /// /// Parse a Stream into a V5 header /// 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; } } }