diff --git a/SabreTools.FileTypes/CHD/CHDFile.cs b/SabreTools.FileTypes/CHD/CHDFile.cs index e2ceef34..08449739 100644 --- a/SabreTools.FileTypes/CHD/CHDFile.cs +++ b/SabreTools.FileTypes/CHD/CHDFile.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using SabreTools.IO.Extensions; +using SabreTools.Models.CHD; namespace SabreTools.FileTypes.CHD { @@ -9,16 +10,14 @@ namespace SabreTools.FileTypes.CHD /// This is code adapted from chd.h and chd.cpp in MAME /// Additional archival code from https://github.com/rtissera/libchdr/blob/master/src/chd.h /// - public abstract class CHDFile : BaseFile + public class CHDFile : BaseFile { #region Private instance variables - protected const string Signature = "MComprHD"; - /// /// Model representing the correct CHD header /// - protected Models.CHD.Header? _header; + protected Header? _header; #endregion @@ -48,11 +47,11 @@ namespace SabreTools.FileTypes.CHD // Read and return the current CHD return version switch { - 1 => CHDFileV1.Deserialize(stream), - 2 => CHDFileV2.Deserialize(stream), - 3 => CHDFileV3.Deserialize(stream), - 4 => CHDFileV4.Deserialize(stream), - 5 => CHDFileV5.Deserialize(stream), + 1 => DeserializeV1(stream), + 2 => DeserializeV2(stream), + 3 => DeserializeV3(stream), + 4 => DeserializeV4(stream), + 5 => DeserializeV5(stream), _ => null, }; } @@ -64,6 +63,179 @@ namespace SabreTools.FileTypes.CHD #endregion + #region Deserializers + + /// + /// Parse and validate the header as if it's V1 + /// + private static CHDFile? DeserializeV1(Stream stream) + { + var header = new HeaderV1(); + + byte[] tagBytes = stream.ReadBytes(8); + header.Tag = Encoding.ASCII.GetString(tagBytes); + if (header.Tag != Constants.SignatureString) + return null; + + header.Length = stream.ReadUInt32BigEndian(); + if (header.Length != Constants.HeaderV1Size) + return null; + + header.Version = stream.ReadUInt32BigEndian(); + header.Flags = (Flags)stream.ReadUInt32BigEndian(); + header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); + if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB) + return null; + + header.HunkSize = stream.ReadUInt32BigEndian(); + header.TotalHunks = stream.ReadUInt32BigEndian(); + header.Cylinders = stream.ReadUInt32BigEndian(); + header.Heads = stream.ReadUInt32BigEndian(); + header.Sectors = stream.ReadUInt32BigEndian(); + header.MD5 = stream.ReadBytes(16); + header.ParentMD5 = stream.ReadBytes(16); + + return new CHDFile { _header = header, MD5 = header.MD5 }; + } + + /// + /// Parse and validate the header as if it's V2 + /// + private static CHDFile? DeserializeV2(Stream stream) + { + var header = new HeaderV2(); + + byte[] tagBytes = stream.ReadBytes(8); + header.Tag = Encoding.ASCII.GetString(tagBytes); + if (header.Tag != Constants.SignatureString) + return null; + + header.Length = stream.ReadUInt32BigEndian(); + if (header.Length != Constants.HeaderV2Size) + return null; + + header.Version = stream.ReadUInt32BigEndian(); + header.Flags = (Flags)stream.ReadUInt32BigEndian(); + header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); + if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB) + return null; + + header.HunkSize = stream.ReadUInt32BigEndian(); + header.TotalHunks = stream.ReadUInt32BigEndian(); + header.Cylinders = stream.ReadUInt32BigEndian(); + header.Heads = stream.ReadUInt32BigEndian(); + header.Sectors = stream.ReadUInt32BigEndian(); + header.MD5 = stream.ReadBytes(16); + header.ParentMD5 = stream.ReadBytes(16); + header.BytesPerSector = stream.ReadUInt32BigEndian(); + + return new CHDFile { _header = header, MD5 = header.MD5 }; + } + + /// + /// Parse and validate the header as if it's V2 + /// + private static CHDFile? DeserializeV3(Stream stream) + { + var header = new HeaderV3(); + + byte[] tagBytes = stream.ReadBytes(8); + header.Tag = Encoding.ASCII.GetString(tagBytes); + if (header.Tag != Constants.SignatureString) + return null; + + header.Length = stream.ReadUInt32BigEndian(); + if (header.Length != Constants.HeaderV3Size) + return null; + + header.Version = stream.ReadUInt32BigEndian(); + header.Flags = (Flags)stream.ReadUInt32BigEndian(); + header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); + if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB_PLUS) + return null; + + header.TotalHunks = stream.ReadUInt32BigEndian(); + header.LogicalBytes = stream.ReadUInt64BigEndian(); + header.MetaOffset = stream.ReadUInt64BigEndian(); + header.MD5 = stream.ReadBytes(16); + header.ParentMD5 = stream.ReadBytes(16); + header.HunkBytes = stream.ReadUInt32BigEndian(); + header.SHA1 = stream.ReadBytes(20); + header.ParentSHA1 = stream.ReadBytes(20); + + return new CHDFile { _header = header, MD5 = header.MD5, SHA1 = header.SHA1 }; + } + + /// + /// Parse and validate the header as if it's V4 + /// + private static CHDFile? DeserializeV4(Stream stream) + { + var header = new HeaderV4(); + + byte[] tagBytes = stream.ReadBytes(8); + header.Tag = Encoding.ASCII.GetString(tagBytes); + if (header.Tag != Constants.SignatureString) + return null; + + header.Length = stream.ReadUInt32BigEndian(); + if (header.Length != Constants.HeaderV4Size) + return null; + + header.Version = stream.ReadUInt32BigEndian(); + header.Flags = (Flags)stream.ReadUInt32BigEndian(); + header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); + if (header.Compression > CompressionType.CHDCOMPRESSION_AV) + return null; + + header.TotalHunks = stream.ReadUInt32BigEndian(); + header.LogicalBytes = stream.ReadUInt64BigEndian(); + header.MetaOffset = stream.ReadUInt64BigEndian(); + header.HunkBytes = stream.ReadUInt32BigEndian(); + header.SHA1 = stream.ReadBytes(20); + header.ParentSHA1 = stream.ReadBytes(20); + header.RawSHA1 = stream.ReadBytes(20); + + return new CHDFile { _header = header, SHA1 = header.SHA1 }; + } + + /// + /// Parse and validate the header as if it's V5 + /// + private static CHDFile? DeserializeV5(Stream stream) + { + var header = new HeaderV5(); + + byte[] tagBytes = stream.ReadBytes(8); + header.Tag = Encoding.ASCII.GetString(tagBytes); + if (header.Tag != Constants.SignatureString) + return null; + + header.Length = stream.ReadUInt32BigEndian(); + if (header.Length != Constants.HeaderV5Size) + return null; + + header.Version = stream.ReadUInt32BigEndian(); + header.Compressors = new uint[4]; + for (int i = 0; i < header.Compressors.Length; i++) + { + header.Compressors[i] = stream.ReadUInt32BigEndian(); + } + + header.LogicalBytes = stream.ReadUInt64BigEndian(); + header.MapOffset = stream.ReadUInt64BigEndian(); + header.MetaOffset = stream.ReadUInt64BigEndian(); + header.HunkBytes = stream.ReadUInt32BigEndian(); + header.UnitBytes = stream.ReadUInt32BigEndian(); + header.RawSHA1 = stream.ReadBytes(20); + header.SHA1 = stream.ReadBytes(20); + header.ParentSHA1 = stream.ReadBytes(20); + + return new CHDFile { _header = header, SHA1 = header.SHA1 }; + } + + #endregion + #region Helpers /// @@ -82,17 +254,17 @@ namespace SabreTools.FileTypes.CHD stream.SeekIfPossible(); // Check the signature - if (!string.Equals(tag, Signature, StringComparison.Ordinal)) + if (!string.Equals(tag, Constants.SignatureString, StringComparison.Ordinal)) return 0; // Match the version to header length - return version switch + return (version, length) switch { - 1 => length == CHDFileV1.HeaderSize ? version : 0, - 2 => length == CHDFileV2.HeaderSize ? version : 0, - 3 => length == CHDFileV3.HeaderSize ? version : 0, - 4 => length == CHDFileV4.HeaderSize ? version : 0, - 5 => length == CHDFileV5.HeaderSize ? version : 0, + (1, Constants.HeaderV1Size) => version, + (2, Constants.HeaderV2Size) => version, + (3, Constants.HeaderV3Size) => version, + (4, Constants.HeaderV4Size) => version, + (5, Constants.HeaderV5Size) => version, _ => 0, }; } diff --git a/SabreTools.FileTypes/CHD/CHDFileV1.cs b/SabreTools.FileTypes/CHD/CHDFileV1.cs deleted file mode 100644 index e5a782bd..00000000 --- a/SabreTools.FileTypes/CHD/CHDFileV1.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; -using SabreTools.Models.CHD; - -namespace SabreTools.FileTypes.CHD -{ - /// - /// CHD V1 File - /// - public class CHDFileV1 : CHDFile - { - internal const int HeaderSize = 76; - - /// - /// Parse and validate the header as if it's V1 - /// - internal static CHDFileV1? Deserialize(Stream stream) - { - var header = new HeaderV1(); - - byte[] tagBytes = stream.ReadBytes(8); - header.Tag = Encoding.ASCII.GetString(tagBytes); - if (header.Tag != Signature) - return null; - - header.Length = stream.ReadUInt32BigEndian(); - if (header.Length != HeaderSize) - return null; - - header.Version = stream.ReadUInt32BigEndian(); - header.Flags = (Flags)stream.ReadUInt32BigEndian(); - header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); - if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB) - return null; - - header.HunkSize = stream.ReadUInt32BigEndian(); - header.TotalHunks = stream.ReadUInt32BigEndian(); - header.Cylinders = stream.ReadUInt32BigEndian(); - header.Heads = stream.ReadUInt32BigEndian(); - header.Sectors = stream.ReadUInt32BigEndian(); - header.MD5 = stream.ReadBytes(16); - header.ParentMD5 = stream.ReadBytes(16); - - return new CHDFileV1 { _header = header, MD5 = header.MD5 }; - } - } -} diff --git a/SabreTools.FileTypes/CHD/CHDFileV2.cs b/SabreTools.FileTypes/CHD/CHDFileV2.cs deleted file mode 100644 index a4f51916..00000000 --- a/SabreTools.FileTypes/CHD/CHDFileV2.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; -using SabreTools.Models.CHD; - -namespace SabreTools.FileTypes.CHD -{ - /// - /// CHD V2 File - /// - public class CHDFileV2 : CHDFile - { - internal const int HeaderSize = 80; - - /// - /// Parse and validate the header as if it's V2 - /// - internal static CHDFileV2? Deserialize(Stream stream) - { - var header = new HeaderV2(); - - byte[] tagBytes = stream.ReadBytes(8); - header.Tag = Encoding.ASCII.GetString(tagBytes); - if (header.Tag != Signature) - return null; - - header.Length = stream.ReadUInt32BigEndian(); - if (header.Length != HeaderSize) - return null; - - header.Version = stream.ReadUInt32BigEndian(); - header.Flags = (Flags)stream.ReadUInt32BigEndian(); - header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); - if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB) - return null; - - header.HunkSize = stream.ReadUInt32BigEndian(); - header.TotalHunks = stream.ReadUInt32BigEndian(); - header.Cylinders = stream.ReadUInt32BigEndian(); - header.Heads = stream.ReadUInt32BigEndian(); - header.Sectors = stream.ReadUInt32BigEndian(); - header.MD5 = stream.ReadBytes(16); - header.ParentMD5 = stream.ReadBytes(16); - header.BytesPerSector = stream.ReadUInt32BigEndian(); - - return new CHDFileV2 { _header = header, MD5 = header.MD5 }; - } - } -} diff --git a/SabreTools.FileTypes/CHD/CHDFileV3.cs b/SabreTools.FileTypes/CHD/CHDFileV3.cs deleted file mode 100644 index d0fe05b4..00000000 --- a/SabreTools.FileTypes/CHD/CHDFileV3.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; -using SabreTools.Models.CHD; - -namespace SabreTools.FileTypes.CHD -{ - /// - /// CHD V3 File - /// - public class CHDFileV3 : CHDFile - { - internal const int HeaderSize = 120; - - /// - /// Parse and validate the header as if it's V3 - /// - internal static CHDFileV3? Deserialize(Stream stream) - { - var header = new HeaderV3(); - - byte[] tagBytes = stream.ReadBytes(8); - header.Tag = Encoding.ASCII.GetString(tagBytes); - if (header.Tag != Signature) - return null; - - header.Length = stream.ReadUInt32BigEndian(); - if (header.Length != HeaderSize) - return null; - - header.Version = stream.ReadUInt32BigEndian(); - header.Flags = (Flags)stream.ReadUInt32BigEndian(); - header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); - if (header.Compression > CompressionType.CHDCOMPRESSION_ZLIB_PLUS) - return null; - - header.TotalHunks = stream.ReadUInt32BigEndian(); - header.LogicalBytes = stream.ReadUInt64BigEndian(); - header.MetaOffset = stream.ReadUInt64BigEndian(); - header.MD5 = stream.ReadBytes(16); - header.ParentMD5 = stream.ReadBytes(16); - header.HunkBytes = stream.ReadUInt32BigEndian(); - header.SHA1 = stream.ReadBytes(20); - header.ParentSHA1 = stream.ReadBytes(20); - - return new CHDFileV3 { _header = header, MD5 = header.MD5, SHA1 = header.SHA1 }; - } - } -} diff --git a/SabreTools.FileTypes/CHD/CHDFileV4.cs b/SabreTools.FileTypes/CHD/CHDFileV4.cs deleted file mode 100644 index b4fec45d..00000000 --- a/SabreTools.FileTypes/CHD/CHDFileV4.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; -using SabreTools.Models.CHD; - -namespace SabreTools.FileTypes.CHD -{ - /// - /// CHD V4 File - /// - public class CHDFileV4 : CHDFile - { - internal const int HeaderSize = 108; - - /// - /// Parse and validate the header as if it's V4 - /// - internal static CHDFileV4? Deserialize(Stream stream) - { - var header = new HeaderV4(); - - byte[] tagBytes = stream.ReadBytes(8); - header.Tag = Encoding.ASCII.GetString(tagBytes); - if (header.Tag != Signature) - return null; - - header.Length = stream.ReadUInt32BigEndian(); - if (header.Length != HeaderSize) - return null; - - header.Version = stream.ReadUInt32BigEndian(); - header.Flags = (Flags)stream.ReadUInt32BigEndian(); - header.Compression = (CompressionType)stream.ReadUInt32BigEndian(); - if (header.Compression > CompressionType.CHDCOMPRESSION_AV) - return null; - - header.TotalHunks = stream.ReadUInt32BigEndian(); - header.LogicalBytes = stream.ReadUInt64BigEndian(); - header.MetaOffset = stream.ReadUInt64BigEndian(); - header.HunkBytes = stream.ReadUInt32BigEndian(); - header.SHA1 = stream.ReadBytes(20); - header.ParentSHA1 = stream.ReadBytes(20); - header.RawSHA1 = stream.ReadBytes(20); - - return new CHDFileV4 { _header = header, SHA1 = header.SHA1 }; - } - } -} diff --git a/SabreTools.FileTypes/CHD/CHDFileV5.cs b/SabreTools.FileTypes/CHD/CHDFileV5.cs deleted file mode 100644 index a32445fd..00000000 --- a/SabreTools.FileTypes/CHD/CHDFileV5.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; -using SabreTools.Models.CHD; - -namespace SabreTools.FileTypes.CHD -{ - /// - /// CHD V5 File - /// - public class CHDFileV5 : CHDFile - { - internal const int HeaderSize = 124; - - /// - /// Parse and validate the header as if it's V5 - /// - internal static CHDFileV5? Deserialize(Stream stream) - { - var header = new HeaderV5(); - - byte[] tagBytes = stream.ReadBytes(8); - header.Tag = Encoding.ASCII.GetString(tagBytes); - if (header.Tag != Signature) - return null; - - header.Length = stream.ReadUInt32BigEndian(); - if (header.Length != HeaderSize) - return null; - - header.Version = stream.ReadUInt32BigEndian(); - header.Compressors = new uint[4]; - for (int i = 0; i < header.Compressors.Length; i++) - { - header.Compressors[i] = stream.ReadUInt32BigEndian(); - } - - header.LogicalBytes = stream.ReadUInt64BigEndian(); - header.MapOffset = stream.ReadUInt64BigEndian(); - header.MetaOffset = stream.ReadUInt64BigEndian(); - header.HunkBytes = stream.ReadUInt32BigEndian(); - header.UnitBytes = stream.ReadUInt32BigEndian(); - header.RawSHA1 = stream.ReadBytes(20); - header.SHA1 = stream.ReadBytes(20); - header.ParentSHA1 = stream.ReadBytes(20); - - return new CHDFileV5 { _header = header, SHA1 = header.SHA1 }; - } - } -} diff --git a/SabreTools.FileTypes/CHD/Constants.cs b/SabreTools.FileTypes/CHD/Constants.cs new file mode 100644 index 00000000..6629efae --- /dev/null +++ b/SabreTools.FileTypes/CHD/Constants.cs @@ -0,0 +1,17 @@ +namespace SabreTools.FileTypes.CHD +{ + internal static class Constants + { + public const string SignatureString = "MComprHD"; + + #region Header Sizes + + public const int HeaderV1Size = 76; + public const int HeaderV2Size = 80; + public const int HeaderV3Size = 120; + public const int HeaderV4Size = 108; + public const int HeaderV5Size = 124; + + #endregion + } +} \ No newline at end of file diff --git a/SabreTools.Test/DatItems/DatItemTests.cs b/SabreTools.Test/DatItems/DatItemTests.cs index 0eb7015d..8e05c2cf 100644 --- a/SabreTools.Test/DatItems/DatItemTests.cs +++ b/SabreTools.Test/DatItems/DatItemTests.cs @@ -159,7 +159,7 @@ namespace SabreTools.Test.DatItems { FileType.Folder => new Folder(), FileType.AaruFormat => new AaruFormat(), - FileType.CHD => new CHDFileV5(), + FileType.CHD => new CHDFile(), FileType.ZipArchive => new ZipArchive(), _ => new BaseFile(), };