diff --git a/BurnOutSharp.Models/MoPaQ/Archive.cs b/BurnOutSharp.Models/MoPaQ/Archive.cs new file mode 100644 index 00000000..08a6bb65 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/Archive.cs @@ -0,0 +1,61 @@ +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// MPQ (MoPaQ) is an archive format developed by Blizzard Entertainment, + /// purposed for storing data files, images, sounds, music and videos for + /// their games. The name MoPaQ comes from the author of the format, + /// Mike O'Brien (Mike O'brien PaCK). + /// + /// + public class Archive + { + // TODO: Data before archive, ignored + + /// + /// MPQ User Data (optional) + /// + public UserData UserData { get; set; } + + /// + /// MPQ Header (required) + /// + public ArchiveHeader ArchiveHeader { get; set; } + + // TODO: Files (optional) + // TODO: Special files (optional) + + /// + /// HET Table (optional) + /// + public HetTable HetTable { get; set; } + + /// + /// BET Table (optional) + /// + public BetTable BetTable { get; set; } + + /// + /// Hash Table (optional) + /// + public HashEntry[] HashTable { get; set; } + + /// + /// Block Table (optional) + /// + public BlockEntry[] BlockTable { get; set; } + + /// + /// Hi-Block Table (optional) + /// + /// + /// Since World of Warcraft - The Burning Crusade, Blizzard extended + /// the MPQ format to support archives larger than 4GB. The hi-block + /// table holds the higher 16-bits of the file position in the MPQ. + /// Hi-block table is plain array of 16-bit values. This table is + /// not encrypted. + /// + public short[] HiBlockTable { get; set; } + + // TODO: Strong digital signature + } +} diff --git a/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs b/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs new file mode 100644 index 00000000..4a1fe308 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs @@ -0,0 +1,180 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// MoPaQ archive header + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class ArchiveHeader + { + #region V1 Properties + + /// + /// The MPQ archive signature + /// + public uint Signature; + + /// + /// Size of the archive header + /// + public uint HeaderSize; + + /// + /// Size of MPQ archive + /// + /// + /// This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive + /// is calculated as the size from the beginning of the archive to the end of the hash table, + /// block table, or extended block table (whichever is largest). + /// + public uint ArchiveSize; + + /// + /// 0 = Format 1 (up to The Burning Crusade) + /// 1 = Format 2 (The Burning Crusade and newer) + /// 2 = Format 3 (WoW - Cataclysm beta or newer) + /// 3 = Format 4 (WoW - Cataclysm beta or newer) + /// + public ushort FormatVersion; + + /// + /// Power of two exponent specifying the number of 512-byte disk sectors in each logical sector + /// in the archive. The size of each logical sector in the archive is 512 * 2 ^ BlockSize. + /// + public ushort BlockSize; + + /// + /// Offset to the beginning of the hash table, relative to the beginning of the archive. + /// + public uint HashTablePosition; + + /// + /// Offset to the beginning of the block table, relative to the beginning of the archive. + /// + public uint BlockTablePosition; + + /// + /// Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for + /// the original MoPaQ format, or less than 2^20 for the Burning Crusade format. + /// + public uint HashTableSize; + + /// + /// Number of entries in the block table + /// + public uint BlockTableSize; + + #endregion + + #region V2 Properties + + /// + /// Offset to the beginning of array of 16-bit high parts of file offsets. + /// + public ulong HiBlockTablePosition; + + /// + /// High 16 bits of the hash table offset for large archives. + /// + public ushort HashTablePositionHi; + + /// + /// High 16 bits of the block table offset for large archives. + /// + public ushort BlockTablePositionHi; + + #endregion + + #region V3 Properties + + /// + /// 64-bit version of the archive size + /// + public ulong ArchiveSizeLong; + + /// + /// 64-bit position of the BET table + /// + public ulong BetTablePosition; + + /// + /// 64-bit position of the HET table + /// + public ulong HetTablePosition; + + #endregion + + #region V4 Properties + + /// + /// Compressed size of the hash table + /// + public ulong HashTableSizeLong; + + /// + /// Compressed size of the block table + /// + public ulong BlockTableSizeLong; + + /// + /// Compressed size of the hi-block table + /// + public ulong HiBlockTableSize; + + /// + /// Compressed size of the HET block + /// + public ulong HetTableSize; + + /// + /// Compressed size of the BET block + /// + public ulong BetTablesize; + + /// + /// Size of raw data chunk to calculate MD5. + /// + /// MD5 of each data chunk follows the raw file data. + public uint RawChunkSize; + + /// + /// MD5 of the block table before decryption + /// + /// 0x10 bytes + public byte[] BlockTableMD5; + + /// + /// MD5 of the hash table before decryption + /// + /// 0x10 bytes + public byte[] HashTableMD5; + + /// + /// MD5 of the hi-block table + /// + /// 0x10 bytes + public byte[] HiBlockTableMD5; + + /// + /// MD5 of the BET table before decryption + /// + /// 0x10 bytes + public byte[] BetTableMD5; + + /// + /// MD5 of the HET table before decryption + /// + /// 0x10 bytes + public byte[] HetTableMD5; + + /// + /// MD5 of the MPQ header from signature to (including) HetTableMD5 + /// + /// 0x10 bytes + public byte[] MpqHeaderMD5; + + #endregion + } +} diff --git a/BurnOutSharp.Models/MoPaQ/BetTable.cs b/BurnOutSharp.Models/MoPaQ/BetTable.cs new file mode 100644 index 00000000..4b6d7bfb --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/BetTable.cs @@ -0,0 +1,140 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// The BET table is present if the BetTablePos64 member of MPQ header is set + /// to nonzero. BET table is a successor of classic block table, and can fully + /// replace it. It is also supposed to be more effective. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class BetTable + { + // TODO: Extract this out and make in common between HET and BET + #region Common Table Headers + + /// + /// 'BET\x1A' + /// + public uint Signature; + + /// + /// Version. Seems to be always 1 + /// + public uint Version; + + /// + /// Size of the contained table + /// + public uint DataSize; + + #endregion + + /// + /// Size of the entire hash table, including the header (in bytes) + /// + public uint TableSize; + + /// + /// Number of files in the BET table + /// + public uint FileCount; + + /// + /// Unknown, set to 0x10 + /// + public uint Unknown; + + /// + /// Size of one table entry (in bits) + /// + public uint TableEntrySize; + + /// + /// Bit index of the file position (within the entry record) + /// + public uint FilePositionBitIndex; + + /// + /// Bit index of the file size (within the entry record) + /// + public uint FileSizeBitIndex; + + /// + /// Bit index of the compressed size (within the entry record) + /// + public uint CompressedSizeBitIndex; + + /// + /// Bit index of the flag index (within the entry record) + /// + public uint FlagIndexBitIndex; + + /// + /// Bit index of the ??? (within the entry record) + /// + public uint UnknownBitIndex; + + /// + /// Bit size of file position (in the entry record) + /// + public uint FilePositionBitCount; + + /// + /// Bit size of file size (in the entry record) + /// + public uint FileSizeBitCount; + + /// + /// Bit size of compressed file size (in the entry record) + /// + public uint CompressedSizeBitCount; + + /// + /// Bit size of flags index (in the entry record) + /// + public uint FlagIndexBitCount; + + /// + /// Bit size of ??? (in the entry record) + /// + public uint UnknownBitCount; + + /// + /// Total size of the BET hash + /// + public uint TotalBetHashSize; + + /// + /// Extra bits in the BET hash + /// + public uint BetHashSizeExtra; + + /// + /// Effective size of BET hash (in bits) + /// + public uint BetHashSize; + + /// + /// Size of BET hashes array, in bytes + /// + public uint BetHashArraySize; + + /// + /// Number of flags in the following array + /// + public uint FlagCount; + + /// + /// Followed by array of file flags. Each entry is 32-bit size and its meaning is the same like + /// + /// Size from + public uint[] FlagsArray; + + // File table. Size of each entry is taken from dwTableEntrySize. + // Size of the table is (dwTableEntrySize * dwMaxFileCount), round up to 8. + + // Array of BET hashes. Table size is taken from dwMaxFileCount from HET table + } +} diff --git a/BurnOutSharp.Models/MoPaQ/BlockEntry.cs b/BurnOutSharp.Models/MoPaQ/BlockEntry.cs new file mode 100644 index 00000000..ff769260 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/BlockEntry.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// Block table contains informations about file sizes and way of their storage within + /// the archive. It also contains the position of file content in the archive. Size + /// of block table entry is (like hash table entry). The block table is also encrypted. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class BlockEntry + { + /// + /// Offset of the beginning of the file data, relative to the beginning of the archive. + /// + public uint FilePosition; + + /// + /// Compressed file size + /// + public uint CompressedSize; + + /// + /// Size of uncompressed file + /// + public uint UncompressedSize; + + /// + /// Flags for the file. + /// + public MoPaQFileFlags Flags; + } +} diff --git a/BurnOutSharp.Models/MoPaQ/Enums.cs b/BurnOutSharp.Models/MoPaQ/Enums.cs new file mode 100644 index 00000000..3ccf683e --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/Enums.cs @@ -0,0 +1,106 @@ +using System; + +namespace BurnOutSharp.Models.MoPaQ +{ + [Flags] + public enum MoPaQFileFlags : uint + { + /// + /// File is compressed using PKWARE Data compression library + /// + MPQ_FILE_IMPLODE = 0x00000100, + + /// + /// File is compressed using combination of compression methods + /// + MPQ_FILE_COMPRESS = 0x00000200, + + /// + /// The file is encrypted + /// + MPQ_FILE_ENCRYPTED = 0x00010000, + + /// + /// The decryption key for the file is altered according to the + /// position of the file in the archive + /// + MPQ_FILE_FIX_KEY = 0x00020000, + + /// + /// The file contains incremental patch for an existing file in base MPQ + /// + MPQ_FILE_PATCH_FILE = 0x00100000, + + /// + /// Instead of being divided to 0x1000-bytes blocks, the file is stored + /// as single unit + /// + MPQ_FILE_SINGLE_UNIT = 0x01000000, + + /// + /// File is a deletion marker, indicating that the file no longer exists. + /// This is used to allow patch archives to delete files present in + /// lower-priority archives in the search chain. The file usually has + /// length of 0 or 1 byte and its name is a hash + /// + MPQ_FILE_DELETE_MARKER = 0x02000000, + + /// + /// File has checksums for each sector (explained in the File Data section). + /// Ignored if file is not compressed or imploded. + /// + MPQ_FILE_SECTOR_CRC = 0x04000000, + + /// + /// Set if file exists, reset when the file was deleted + /// + MPQ_FILE_EXISTS = 0x80000000, + } + + public enum Locale : short + { + Neutral = 0, + AmericanEnglish = 0, + ChineseTaiwan = 0x404, + Czech = 0x405, + German = 0x407, + English = 0x409, + Spanish = 0x40A, + French = 0x40C, + Italian = 0x410, + Japanese = 0x411, + Korean = 0x412, + Polish = 0x415, + Portuguese = 0x416, + Russian = 0x419, + EnglishUK = 0x809, + } + + public enum MoPaQPatchType : uint + { + /// + /// Blizzard-modified version of BSDIFF40 incremental patch + /// + BSD0 = 0x30445342, + + /// + /// Unknown + /// + BSDP = 0x50445342, + + /// + /// Plain replace + /// + COPY = 0x59504F43, + + /// + /// Unknown + /// + COUP = 0x50554F43, + + /// + /// Unknown + /// + CPOG = 0x474F5043, + } +} diff --git a/BurnOutSharp.Models/MoPaQ/HashEntry.cs b/BurnOutSharp.Models/MoPaQ/HashEntry.cs new file mode 100644 index 00000000..211cd1ec --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/HashEntry.cs @@ -0,0 +1,48 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// Hash table is used for searching files by name. The file name is converted to + /// two 32-bit hash values, which are then used for searching in the table. The size + /// of the hash table must always be a power of two. Each entry in the hash table + /// also contains file locale and offset into block table. Size of one entry of hash + /// table is 16 bytes. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class HashEntry + { + /// + /// The hash of the full file name (part A) + /// + public uint NameHashPartA; + + /// + /// The hash of the full file name (part B) + /// + public uint NameHashPartB; + + /// + /// The language of the file. This is a Windows LANGID data type, and uses the same values. + /// 0 indicates the default language (American English), or that the file is language-neutral. + /// + public Locale Locale; + + /// + /// The platform the file is used for. 0 indicates the default platform. + /// No other values have been observed. + /// + public ushort Platform; + + /// + /// If the hash table entry is valid, this is the index into the block table of the file. + /// Otherwise, one of the following two values: + /// - FFFFFFFFh: Hash table entry is empty, and has always been empty. + /// Terminates searches for a given file. + /// - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file). + /// Does not terminate searches for a given file. + /// + public uint BlockIndex; + } +} diff --git a/BurnOutSharp.Models/MoPaQ/HetTable.cs b/BurnOutSharp.Models/MoPaQ/HetTable.cs new file mode 100644 index 00000000..07c6b879 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/HetTable.cs @@ -0,0 +1,87 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// The HET table is present if the HetTablePos64 member of MPQ header is + /// set to nonzero. This table can fully replace hash table. Depending on + /// MPQ size, the pair of HET&BET table can be more efficient than Hash&Block + /// table. HET table can be encrypted and compressed. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class HetTable + { + // TODO: Extract this out and make in common between HET and BET + #region Common Table Headers + + /// + /// 'HET\x1A' + /// + public uint Signature; + + /// + /// Version. Seems to be always 1 + /// + public uint Version; + + /// + /// Size of the contained table + /// + public uint DataSize; + + #endregion + + /// + /// Size of the entire hash table, including the header (in bytes) + /// + public uint TableSize; + + /// + /// Maximum number of files in the MPQ + /// + public uint MaxFileCount; + + /// + /// Size of the hash table (in bytes) + /// + public uint HashTableSize; + + /// + /// Effective size of the hash entry (in bits) + /// + public uint HashEntrySize; + + /// + /// Total size of file index (in bits) + /// + public uint TotalIndexSize; + + /// + /// Extra bits in the file index + /// + public uint IndexSizeExtra; + + /// + /// Effective size of the file index (in bits) + /// + public uint IndexSize; + + /// + /// Size of the block index subtable (in bytes) + /// + public uint BlockTableSize; + + /// + /// HET hash table. Each entry is 8 bits. + /// + /// Size is derived from HashTableSize + public byte[] HashTable; + + /// + /// Array of file indexes. Bit size of each entry is taken from dwTotalIndexSize. + /// Table size is taken from dwHashTableSize. + /// + public byte[][] FileIndexes; + } +} diff --git a/BurnOutSharp.Models/MoPaQ/PatchHeader.cs b/BurnOutSharp.Models/MoPaQ/PatchHeader.cs new file mode 100644 index 00000000..09b5fff8 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/PatchHeader.cs @@ -0,0 +1,118 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// Each incremental patch file in a patch MPQ starts with a header. It is supposed + /// to be a structure with variable length. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class PatchHeader + { + #region PATCH Header + + /// + /// 'PTCH' + /// + public uint PatchSignature { get; private set; } + + /// + /// Size of the entire patch (decompressed) + /// + public int SizeOfPatchData { get; private set; } + + /// + /// Size of the file before patch + /// + public int SizeBeforePatch { get; private set; } + + /// + /// Size of file after patch + /// + public int SizeAfterPatch { get; private set; } + + #endregion + + #region MD5 Block + + /// + /// 'MD5_' + /// + public uint Md5Signature { get; private set; } + + /// + /// Size of the MD5 block, including the signature and size itself + /// + public int Md5BlockSize { get; private set; } + + /// + /// MD5 of the original (unpached) file + /// + public byte[] Md5BeforePatch { get; private set; } = new byte[0x10]; + + /// + /// MD5 of the patched file + /// + public byte[] Md5AfterPatch { get; private set; } = new byte[0x10]; + + #endregion + + #region XFRM Block + + /// + /// 'XFRM' + /// + public uint XfrmSignature { get; private set; } + + /// + /// Size of the XFRM block, includes XFRM header and patch data + /// + public int XfrmBlockSize { get; private set; } + + /// + /// Type of patch ('BSD0' or 'COPY') + /// + public MoPaQPatchType PatchType { get; private set; } + + #endregion + + #region Patch Data - BSD0 + + /// + /// 'BSDIFF40' signature + /// + public ulong BsdiffSignature { get; private set; } + + /// + /// Size of CTRL block (in bytes) + /// + public long CtrlBlockSize { get; private set; } + + /// + /// Size of DATA block (in bytes) + /// + public long DataBlockSize { get; private set; } + + /// + /// Size of file after applying the patch (in bytes) + /// + public long NewFileSize { get; private set; } + + // TODO: Fill rest of data from http://zezula.net/en/mpq/patchfiles.html + // CTRL block + // DATA block + // EXTRA block + + #endregion + + #region Patch Data - COPY + + /// + /// File data are simply replaced by the data in the patch. + /// + public byte[] PatchDataCopy { get; private set; } + + #endregion + } +} diff --git a/BurnOutSharp.Models/MoPaQ/PatchInfo.cs b/BurnOutSharp.Models/MoPaQ/PatchInfo.cs new file mode 100644 index 00000000..230eccfd --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/PatchInfo.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// This structure contains size of the patch, flags and also MD5 of the patch. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class PatchInfo + { + /// + /// Length of patch info header, in bytes + /// + public uint Length; + + /// + /// Flags. 0x80000000 = MD5 (?) + /// + public uint Flags; + + /// + /// Uncompressed size of the patch file + /// + public uint DataSize; + + /// + /// MD5 of the entire patch file after decompression + /// + /// 0x10 bytes + public byte[] MD5; + + /// + /// The sector offset table (variable length) + /// + public uint[] SectorOffsetTable; + } +} diff --git a/BurnOutSharp.Models/MoPaQ/UserData.cs b/BurnOutSharp.Models/MoPaQ/UserData.cs new file mode 100644 index 00000000..e0dcc644 --- /dev/null +++ b/BurnOutSharp.Models/MoPaQ/UserData.cs @@ -0,0 +1,37 @@ +using System.Runtime.InteropServices; + +namespace BurnOutSharp.Models.MoPaQ +{ + /// + /// MPQ User Data are optional, and is commonly used in custom maps for + /// Starcraft II. If MPQ User Data header is present, it contains an offset, + /// from where the MPQ header should be searched. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public class UserData + { + /// + /// The user data signature + /// + /// + public uint Signature; + + /// + /// Maximum size of the user data + /// + public uint UserDataSize; + + /// + /// Offset of the MPQ header, relative to the beginning of this header + /// + public uint HeaderOffset; + + /// + /// Appears to be size of user data header (Starcraft II maps) + /// + public uint UserDataHeaderSize; + + // TODO: Does this area contain extra data that should be read in? + } +} diff --git a/BurnOutSharp/FileType/MPQ.cs b/BurnOutSharp/FileType/MPQ.cs index dfa50ff0..43141f2d 100644 --- a/BurnOutSharp/FileType/MPQ.cs +++ b/BurnOutSharp/FileType/MPQ.cs @@ -99,1327 +99,535 @@ namespace BurnOutSharp.FileType // http://zezula.net/en/mpq/mpqformat.html #region TEMPORARY AREA FOR MPQ FORMAT - /// - /// MPQ (MoPaQ) is an archive format developed by Blizzard Entertainment, - /// purposed for storing data files, images, sounds, music and videos for - /// their games. The name MoPaQ comes from the author of the format, - /// Mike O'Brien (Mike O'brien PaCK). - /// internal class MoPaQArchive { - #region Properties + //#region Serialization - // Data before archive, ignored + ///// + ///// Deserialize at into a new MoPaQArchive object + ///// + //public static MoPaQArchive Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// MPQ User Data (optional) - /// - public MoPaQUserData UserData { get; private set; } + // MoPaQArchive archive = new MoPaQArchive(); - /// - /// MPQ Header (required) - /// - public MoPaQArchiveHeader ArchiveHeader { get; private set; } + // // Check for User Data + // uint possibleSignature = BitConverter.ToUInt32(data, dataPtr); + // if (possibleSignature == MoPaQUserData.SignatureValue) + // { + // // Save the current position for offset correction + // int basePtr = dataPtr; - // Files (optional) - // Special files (optional) + // // Deserialize the user data, returning null if invalid + // archive.UserData = MoPaQUserData.Deserialize(data, ref dataPtr); + // if (archive.UserData == null) + // return null; - /// - /// HET Table (optional) - /// - public MoPaQHetTable HetTable { get; private set; } + // // Set the starting position according to the header offset + // dataPtr = basePtr + (int)archive.UserData.HeaderOffset; + // } - /// - /// BET Table (optional) - /// - public MoPaQBetTable BetTable { get; private set; } + // // Check for the Header + // possibleSignature = BitConverter.ToUInt32(data, dataPtr); + // if (possibleSignature == MoPaQArchiveHeader.SignatureValue) + // { + // // Deserialize the header, returning null if invalid + // archive.ArchiveHeader = MoPaQArchiveHeader.Deserialize(data, ref dataPtr); + // if (archive.ArchiveHeader == null) + // return null; + // } - /// - /// Hash Table (optional) - /// - public MoPaQHashEntry[] HashTable { get; private set; } + // // If we don't have a header, return null + // if (archive.ArchiveHeader == null) + // return null; - /// - /// Block Table (optional) - /// - public MoPaQBlockEntry[] BlockTable { get; private set; } + // // TODO: Read in Hash Table + // // TODO: Read in Block Table + // // TODO: Read in Hi-Block Table + // // TODO: Read in BET Table + // // TODO: Read in HET Table - /// - /// Hi-Block Table (optional) - /// - /// - /// Since World of Warcraft - The Burning Crusade, Blizzard extended - /// the MPQ format to support archives larger than 4GB. The hi-block - /// table holds the higher 16-bits of the file position in the MPQ. - /// Hi-block table is plain array of 16-bit values. This table is - /// not encrypted. - /// - public short[] HiBlockTable { get; private set; } + // return archive; + //} - // Strong digital signature - - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQArchive object - /// - public static MoPaQArchive Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQArchive archive = new MoPaQArchive(); - - // Check for User Data - uint possibleSignature = BitConverter.ToUInt32(data, dataPtr); - if (possibleSignature == MoPaQUserData.SignatureValue) - { - // Save the current position for offset correction - int basePtr = dataPtr; - - // Deserialize the user data, returning null if invalid - archive.UserData = MoPaQUserData.Deserialize(data, ref dataPtr); - if (archive.UserData == null) - return null; - - // Set the starting position according to the header offset - dataPtr = basePtr + (int)archive.UserData.HeaderOffset; - } - - // Check for the Header - possibleSignature = BitConverter.ToUInt32(data, dataPtr); - if (possibleSignature == MoPaQArchiveHeader.SignatureValue) - { - // Deserialize the header, returning null if invalid - archive.ArchiveHeader = MoPaQArchiveHeader.Deserialize(data, ref dataPtr); - if (archive.ArchiveHeader == null) - return null; - } - - // If we don't have a header, return null - if (archive.ArchiveHeader == null) - return null; - - // TODO: Read in Hash Table - // TODO: Read in Block Table - // TODO: Read in Hi-Block Table - // TODO: Read in BET Table - // TODO: Read in HET Table - - return archive; - } - - #endregion + //#endregion } - /// - /// MPQ User Data are optional, and is commonly used in custom maps for - /// Starcraft II. If MPQ User Data header is present, it contains an offset, - /// from where the MPQ header should be searched. - /// internal class MoPaQUserData { - #region Constants + //#region Constants - public const int Size = 0x10; + //public const int Size = 0x10; - /// - /// Human-readable signature - /// - public static readonly string SignatureString = $"MPQ{(char)0x1B}"; + ///// + ///// Human-readable signature + ///// + //public static readonly string SignatureString = $"MPQ{(char)0x1B}"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint SignatureValue = 0x1B51504D; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint SignatureValue = 0x1B51504D; - /// - /// Signature as a byte array - /// - public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1B }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1B }; - #endregion + //#endregion - #region Properties + //#region Serialization - /// - /// The user data signature - /// - /// - public uint Signature { get; private set; } + ///// + ///// Deserialize at into a new MoPaQUserData object + ///// + //public static MoPaQUserData Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Maximum size of the user data - /// - public uint UserDataSize { get; private set; } + // MoPaQUserData userData = new MoPaQUserData(); - /// - /// Offset of the MPQ header, relative to the beginning of this header - /// - public uint HeaderOffset { get; private set; } + // userData.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // if (userData.Signature != SignatureValue) + // return null; - /// - /// Appears to be size of user data header (Starcraft II maps) - /// - public uint UserDataHeaderSize { get; private set; } + // userData.UserDataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // userData.HeaderOffset = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // userData.UserDataHeaderSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - // TODO: Does this area contain extra data that should be read in? + // return userData; + //} - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQUserData object - /// - public static MoPaQUserData Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQUserData userData = new MoPaQUserData(); - - userData.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - if (userData.Signature != SignatureValue) - return null; - - userData.UserDataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - userData.HeaderOffset = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - userData.UserDataHeaderSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - return userData; - } - - #endregion + //#endregion } - /// - /// MoPaQ archive header - /// internal class MoPaQArchiveHeader { - #region Constants + //#region Constants - #region Signatures + //#region Signatures - /// - /// Human-readable signature - /// - public static readonly string SignatureString = $"MPQ{(char)0x1A}"; + ///// + ///// Human-readable signature + ///// + //public static readonly string SignatureString = $"MPQ{(char)0x1A}"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint SignatureValue = 0x1A51504D; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint SignatureValue = 0x1A51504D; - /// - /// Signature as a byte array - /// - public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1A }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1A }; - #endregion + //#endregion - #region Header Sizes + //#region Header Sizes - public const int HeaderVersion1Size = 0x20; + //public const int HeaderVersion1Size = 0x20; - public const int HeaderVersion2Size = 0x2C; + //public const int HeaderVersion2Size = 0x2C; - public const int HeaderVersion3Size = 0x44; + //public const int HeaderVersion3Size = 0x44; - public const int HeaderVersion4Size = 0xD0; + //public const int HeaderVersion4Size = 0xD0; - #endregion + //#endregion - #endregion + //#endregion - #region V1 Properties + //#region Serialization - /// - /// The MPQ archive signature - /// - public uint Signature { get; private set; } + ///// + ///// Deserialize at into a new MoPaQArchiveHeader object + ///// + //public static MoPaQArchiveHeader Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Size of the archive header - /// - public uint HeaderSize { get; private set; } + // MoPaQArchiveHeader archiveHeader = new MoPaQArchiveHeader(); - /// - /// Size of MPQ archive - /// - /// - /// This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive - /// is calculated as the size from the beginning of the archive to the end of the hash table, - /// block table, or extended block table (whichever is largest). - /// - public uint ArchiveSize { get; private set; } + // // V1 - Common + // archiveHeader.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // if (archiveHeader.Signature != SignatureValue) + // return null; - /// - /// 0 = Format 1 (up to The Burning Crusade) - /// 1 = Format 2 (The Burning Crusade and newer) - /// 2 = Format 3 (WoW - Cataclysm beta or newer) - /// 3 = Format 4 (WoW - Cataclysm beta or newer) - /// - public ushort FormatVersion { get; private set; } + // archiveHeader.HeaderSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // archiveHeader.ArchiveSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // archiveHeader.FormatVersion = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // archiveHeader.BlockSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // archiveHeader.HashTablePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // archiveHeader.BlockTablePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // archiveHeader.HashTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // archiveHeader.BlockTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Power of two exponent specifying the number of 512-byte disk sectors in each logical sector - /// in the archive. The size of each logical sector in the archive is 512 * 2 ^ BlockSize. - /// - public ushort BlockSize { get; private set; } + // // V2 + // if (archiveHeader.FormatVersion >= 2 && archiveHeader.HeaderSize >= HeaderVersion2Size) + // { + // archiveHeader.HiBlockTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.HashTablePositionHi = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // archiveHeader.BlockTablePositionHi = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // } - /// - /// Offset to the beginning of the hash table, relative to the beginning of the archive. - /// - public uint HashTablePosition { get; private set; } + // // V3 + // if (archiveHeader.FormatVersion >= 3 && archiveHeader.HeaderSize >= HeaderVersion3Size) + // { + // archiveHeader.ArchiveSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.BetTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.HetTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // } - /// - /// Offset to the beginning of the block table, relative to the beginning of the archive. - /// - public uint BlockTablePosition { get; private set; } + // // V4 + // if (archiveHeader.FormatVersion >= 4 && archiveHeader.HeaderSize >= HeaderVersion4Size) + // { + // archiveHeader.HashTableSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.BlockTableSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.HiBlockTableSize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.HetTableSize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.BetTablesize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; + // archiveHeader.RawChunkSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for - /// the original MoPaQ format, or less than 2^20 for the Burning Crusade format. - /// - public uint HashTableSize { get; private set; } + // Array.Copy(data, dataPtr, archiveHeader.BlockTableMD5, 0, 0x10); dataPtr += 0x10; + // Array.Copy(data, dataPtr, archiveHeader.HashTableMD5, 0, 0x10); dataPtr += 0x10; + // Array.Copy(data, dataPtr, archiveHeader.HiBlockTableMD5, 0, 0x10); dataPtr += 0x10; + // Array.Copy(data, dataPtr, archiveHeader.BetTableMD5, 0, 0x10); dataPtr += 0x10; + // Array.Copy(data, dataPtr, archiveHeader.HetTableMD5, 0, 0x10); dataPtr += 0x10; + // Array.Copy(data, dataPtr, archiveHeader.MpqHeaderMD5, 0, 0x10); dataPtr += 0x10; + // } - /// - /// Number of entries in the block table - /// - public uint BlockTableSize { get; private set; } + // return archiveHeader; + //} - #endregion - - #region V2 Properties - - /// - /// Offset to the beginning of array of 16-bit high parts of file offsets. - /// - public ulong HiBlockTablePosition { get; private set; } - - /// - /// High 16 bits of the hash table offset for large archives. - /// - public ushort HashTablePositionHi { get; private set; } - - /// - /// High 16 bits of the block table offset for large archives. - /// - public ushort BlockTablePositionHi { get; private set; } - - #endregion - - #region V3 Properties - - /// - /// 64-bit version of the archive size - /// - public ulong ArchiveSizeLong { get; private set; } - - /// - /// 64-bit position of the BET table - /// - public ulong BetTablePosition { get; private set; } - - /// - /// 64-bit position of the HET table - /// - public ulong HetTablePosition { get; private set; } - - #endregion - - #region V4 Properties - - /// - /// Compressed size of the hash table - /// - public ulong HashTableSizeLong { get; private set; } - - /// - /// Compressed size of the block table - /// - public ulong BlockTableSizeLong { get; private set; } - - /// - /// Compressed size of the hi-block table - /// - public ulong HiBlockTableSize { get; private set; } - - /// - /// Compressed size of the HET block - /// - public ulong HetTableSize { get; private set; } - - /// - /// Compressed size of the BET block - /// - public ulong BetTablesize { get; private set; } - - /// - /// Size of raw data chunk to calculate MD5. - /// - /// MD5 of each data chunk follows the raw file data. - public uint RawChunkSize { get; private set; } - - /// - /// MD5 of the block table before decryption - /// - public byte[] BlockTableMD5 { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the hash table before decryption - /// - public byte[] HashTableMD5 { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the hi-block table - /// - public byte[] HiBlockTableMD5 { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the BET table before decryption - /// - public byte[] BetTableMD5 { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the HET table before decryption - /// - public byte[] HetTableMD5 { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the MPQ header from signature to (including) HetTableMD5 - /// - public byte[] MpqHeaderMD5 { get; private set; } = new byte[0x10]; - - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQArchiveHeader object - /// - public static MoPaQArchiveHeader Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQArchiveHeader archiveHeader = new MoPaQArchiveHeader(); - - // V1 - Common - archiveHeader.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - if (archiveHeader.Signature != SignatureValue) - return null; - - archiveHeader.HeaderSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - archiveHeader.ArchiveSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - archiveHeader.FormatVersion = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - archiveHeader.BlockSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - archiveHeader.HashTablePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - archiveHeader.BlockTablePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - archiveHeader.HashTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - archiveHeader.BlockTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - // V2 - if (archiveHeader.FormatVersion >= 2 && archiveHeader.HeaderSize >= HeaderVersion2Size) - { - archiveHeader.HiBlockTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.HashTablePositionHi = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - archiveHeader.BlockTablePositionHi = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - } - - // V3 - if (archiveHeader.FormatVersion >= 3 && archiveHeader.HeaderSize >= HeaderVersion3Size) - { - archiveHeader.ArchiveSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.BetTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.HetTablePosition = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - } - - // V4 - if (archiveHeader.FormatVersion >= 4 && archiveHeader.HeaderSize >= HeaderVersion4Size) - { - archiveHeader.HashTableSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.BlockTableSizeLong = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.HiBlockTableSize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.HetTableSize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.BetTablesize = BitConverter.ToUInt64(data, dataPtr); dataPtr += 8; - archiveHeader.RawChunkSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - Array.Copy(data, dataPtr, archiveHeader.BlockTableMD5, 0, 0x10); dataPtr += 0x10; - Array.Copy(data, dataPtr, archiveHeader.HashTableMD5, 0, 0x10); dataPtr += 0x10; - Array.Copy(data, dataPtr, archiveHeader.HiBlockTableMD5, 0, 0x10); dataPtr += 0x10; - Array.Copy(data, dataPtr, archiveHeader.BetTableMD5, 0, 0x10); dataPtr += 0x10; - Array.Copy(data, dataPtr, archiveHeader.HetTableMD5, 0, 0x10); dataPtr += 0x10; - Array.Copy(data, dataPtr, archiveHeader.MpqHeaderMD5, 0, 0x10); dataPtr += 0x10; - } - - return archiveHeader; - } - - #endregion + //#endregion } - /// - /// The HET table is present if the HetTablePos64 member of MPQ header is - /// set to nonzero. This table can fully replace hash table. Depending on - /// MPQ size, the pair of HET&BET table can be more efficient than Hash&Block - /// table. HET table can be encrypted and compressed. - /// internal class MoPaQHetTable { - #region Constants + //#region Constants - public const int Size = 0x44; + //public const int Size = 0x44; - /// - /// Human-readable signature - /// - public static readonly string SignatureString = $"HET{(char)0x1A}"; + ///// + ///// Human-readable signature + ///// + //public static readonly string SignatureString = $"HET{(char)0x1A}"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint SignatureValue = 0x1A544548; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint SignatureValue = 0x1A544548; - /// - /// Signature as a byte array - /// - public static readonly byte[] SignatureBytes = new byte[] { 0x48, 0x45, 0x54, 0x1A }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] SignatureBytes = new byte[] { 0x48, 0x45, 0x54, 0x1A }; - #endregion + //#endregion - // TODO: Extract this out and make in common between HET and BET - #region Common Table Headers + //#region Serialization - /// - /// 'HET\x1A' - /// - public uint Signature { get; private set; } + ///// + ///// Deserialize at into a new MoPaQHetTable object + ///// + //public static MoPaQHetTable Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Version. Seems to be always 1 - /// - public uint Version { get; private set; } + // MoPaQHetTable hetTable = new MoPaQHetTable(); - /// - /// Size of the contained table - /// - public uint DataSize { get; private set; } + // // Common Headers + // hetTable.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // if (hetTable.Signature != SignatureValue) + // return null; - #endregion + // hetTable.Version = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - #region Properties + // // HET-Specific + // hetTable.TableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.MaxFileCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.HashTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.TotalIndexSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.IndexSizeExtra = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.IndexSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hetTable.BlockTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Size of the entire hash table, including the header (in bytes) - /// - public uint TableSize { get; private set; } + // hetTable.HashTable = new byte[hetTable.HashTableSize]; + // Array.Copy(data, dataPtr, hetTable.HashTable, 0, hetTable.HashTableSize); + // dataPtr += (int)hetTable.HashTableSize; - /// - /// Maximum number of files in the MPQ - /// - public uint MaxFileCount { get; private set; } + // // TODO: Populate the file indexes array + // hetTable.FileIndexes = new byte[(int)hetTable.HashTableSize][]; - /// - /// Size of the hash table (in bytes) - /// - public uint HashTableSize { get; private set; } + // return hetTable; + //} - /// - /// Effective size of the hash entry (in bits) - /// - public uint HashEntrySize { get; private set; } - - /// - /// Total size of file index (in bits) - /// - public uint TotalIndexSize { get; private set; } - - /// - /// Extra bits in the file index - /// - public uint IndexSizeExtra { get; private set; } - - /// - /// Effective size of the file index (in bits) - /// - public uint IndexSize { get; private set; } - - /// - /// Size of the block index subtable (in bytes) - /// - public uint BlockTableSize { get; private set; } - - /// - /// HET hash table. Each entry is 8 bits. - /// - /// Size is derived from HashTableSize - public byte[] HashTable { get; private set; } - - /// - /// Array of file indexes. Bit size of each entry is taken from dwTotalIndexSize. - /// Table size is taken from dwHashTableSize. - /// - public byte[][] FileIndexes { get; private set; } - - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQHetTable object - /// - public static MoPaQHetTable Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQHetTable hetTable = new MoPaQHetTable(); - - // Common Headers - hetTable.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - if (hetTable.Signature != SignatureValue) - return null; - - hetTable.Version = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - // HET-Specific - hetTable.TableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.MaxFileCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.HashTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.TotalIndexSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.IndexSizeExtra = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.IndexSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hetTable.BlockTableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - hetTable.HashTable = new byte[hetTable.HashTableSize]; - Array.Copy(data, dataPtr, hetTable.HashTable, 0, hetTable.HashTableSize); - dataPtr += (int)hetTable.HashTableSize; - - // TODO: Populate the file indexes array - hetTable.FileIndexes = new byte[(int)hetTable.HashTableSize][]; - - return hetTable; - } - - #endregion + //#endregion } - /// - /// he BET table is present if the BetTablePos64 member of MPQ header is set - /// to nonzero. BET table is a successor of classic block table, and can fully - /// replace it. It is also supposed to be more effective. - /// internal class MoPaQBetTable { - #region Constants + //#region Constants - public const int Size = 0x88; + //public const int Size = 0x88; - /// - /// Human-readable signature - /// - public static readonly string SignatureString = $"BET{(char)0x1A}"; + ///// + ///// Human-readable signature + ///// + //public static readonly string SignatureString = $"BET{(char)0x1A}"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint SignatureValue = 0x1A544542; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint SignatureValue = 0x1A544542; - /// - /// Signature as a byte array - /// - public static readonly byte[] SignatureBytes = new byte[] { 0x42, 0x45, 0x54, 0x1A }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] SignatureBytes = new byte[] { 0x42, 0x45, 0x54, 0x1A }; - #endregion + //#endregion - // TODO: Extract this out and make in common between HET and BET - #region Common Table Headers + //#region Serialization - /// - /// 'BET\x1A' - /// - public uint Signature { get; private set; } + ///// + ///// Deserialize at into a new MoPaQBetTable object + ///// + //public static MoPaQBetTable Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Version. Seems to be always 1 - /// - public uint Version { get; private set; } + // MoPaQBetTable betTable = new MoPaQBetTable(); - /// - /// Size of the contained table - /// - public uint DataSize { get; private set; } + // // Common Headers + // betTable.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // if (betTable.Signature != SignatureValue) + // return null; - #endregion + // betTable.Version = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - #region Properties + // // BET-Specific + // betTable.TableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FileCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.Unknown = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.TableEntrySize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Size of the entire hash table, including the header (in bytes) - /// - public uint TableSize { get; private set; } + // betTable.FilePositionBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FileSizeBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.CompressedSizeBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FlagIndexBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.UnknownBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Number of files in the BET table - /// - public uint FileCount { get; private set; } + // betTable.FilePositionBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FileSizeBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.CompressedSizeBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FlagIndexBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.UnknownBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Unknown, set to 0x10 - /// - public uint Unknown { get; private set; } + // betTable.TotalBetHashSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.BetHashSizeExtra = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.BetHashSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.BetHashArraySize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // betTable.FlagCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Size of one table entry (in bits) - /// - public uint TableEntrySize { get; private set; } + // betTable.FlagsArray = new uint[betTable.FlagCount]; + // Buffer.BlockCopy(data, dataPtr, betTable.FlagsArray, 0, (int)betTable.FlagCount * 4); + // dataPtr += (int)betTable.FlagCount * 4; - /// - /// Bit index of the file position (within the entry record) - /// - public uint FilePositionBitIndex { get; private set; } + // // TODO: Populate the file table + // // TODO: Populate the hash table - /// - /// Bit index of the file size (within the entry record) - /// - public uint FileSizeBitIndex { get; private set; } + // return betTable; + //} - /// - /// Bit index of the compressed size (within the entry record) - /// - public uint CompressedSizeBitIndex { get; private set; } - - /// - /// Bit index of the flag index (within the entry record) - /// - public uint FlagIndexBitIndex { get; private set; } - - /// - /// Bit index of the ??? (within the entry record) - /// - public uint UnknownBitIndex { get; private set; } - - /// - /// Bit size of file position (in the entry record) - /// - public uint FilePositionBitCount { get; private set; } - - /// - /// Bit size of file size (in the entry record) - /// - public uint FileSizeBitCount { get; private set; } - - /// - /// Bit size of compressed file size (in the entry record) - /// - public uint CompressedSizeBitCount { get; private set; } - - /// - /// Bit size of flags index (in the entry record) - /// - public uint FlagIndexBitCount { get; private set; } - - /// - /// Bit size of ??? (in the entry record) - /// - public uint UnknownBitCount { get; private set; } - - /// - /// Total size of the BET hash - /// - public uint TotalBetHashSize { get; private set; } - - /// - /// Extra bits in the BET hash - /// - public uint BetHashSizeExtra { get; private set; } - - /// - /// Effective size of BET hash (in bits) - /// - public uint BetHashSize { get; private set; } - - /// - /// Size of BET hashes array, in bytes - /// - public uint BetHashArraySize { get; private set; } - - /// - /// Number of flags in the following array - /// - public uint FlagCount { get; private set; } - - /// - /// Followed by array of file flags. Each entry is 32-bit size and its meaning is the same like - /// - /// Size from - public uint[] FlagsArray { get; private set; } - - // File table. Size of each entry is taken from dwTableEntrySize. - // Size of the table is (dwTableEntrySize * dwMaxFileCount), round up to 8. - - // Array of BET hashes. Table size is taken from dwMaxFileCount from HET table - - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQBetTable object - /// - public static MoPaQBetTable Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQBetTable betTable = new MoPaQBetTable(); - - // Common Headers - betTable.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - if (betTable.Signature != SignatureValue) - return null; - - betTable.Version = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - // BET-Specific - betTable.TableSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FileCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.Unknown = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.TableEntrySize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - betTable.FilePositionBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FileSizeBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.CompressedSizeBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FlagIndexBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.UnknownBitIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - betTable.FilePositionBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FileSizeBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.CompressedSizeBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FlagIndexBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.UnknownBitCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - betTable.TotalBetHashSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.BetHashSizeExtra = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.BetHashSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.BetHashArraySize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - betTable.FlagCount = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - betTable.FlagsArray = new uint[betTable.FlagCount]; - Buffer.BlockCopy(data, dataPtr, betTable.FlagsArray, 0, (int)betTable.FlagCount * 4); - dataPtr += (int)betTable.FlagCount * 4; - - // TODO: Populate the file table - // TODO: Populate the hash table - - return betTable; - } - - #endregion + //#endregion } - /// - /// Hash table is used for searching files by name. The file name is converted to - /// two 32-bit hash values, which are then used for searching in the table. The size - /// of the hash table must always be a power of two. Each entry in the hash table - /// also contains file locale and offset into block table. Size of one entry of hash - /// table is 16 bytes. - /// internal class MoPaQHashEntry { - #region Constants + //#region Constants - public const int Size = 0x10; + //public const int Size = 0x10; - #endregion + //#endregion - #region Properties + //#region Serialization - /// - /// The hash of the full file name (part A) - /// - public uint NameHashPartA { get; private set; } + ///// + ///// Deserialize at into a new MoPaQHashEntry object + ///// + //public static MoPaQHashEntry Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// The hash of the full file name (part B) - /// - public uint NameHashPartB { get; private set; } + // MoPaQHashEntry hashEntry = new MoPaQHashEntry(); - /// - /// The language of the file. This is a Windows LANGID data type, and uses the same values. - /// 0 indicates the default language (American English), or that the file is language-neutral. - /// - public MoPaQLocale Locale { get; private set; } + // hashEntry.NameHashPartA = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hashEntry.NameHashPartB = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // hashEntry.Locale = (MoPaQLocale)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // hashEntry.Platform = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + // hashEntry.BlockIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// The platform the file is used for. 0 indicates the default platform. - /// No other values have been observed. - /// - public ushort Platform { get; private set; } + // return hashEntry; + //} - /// - /// If the hash table entry is valid, this is the index into the block table of the file. - /// Otherwise, one of the following two values: - /// - FFFFFFFFh: Hash table entry is empty, and has always been empty. - /// Terminates searches for a given file. - /// - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file). - /// Does not terminate searches for a given file. - /// - public uint BlockIndex { get; private set; } - - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQHashEntry object - /// - public static MoPaQHashEntry Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQHashEntry hashEntry = new MoPaQHashEntry(); - - hashEntry.NameHashPartA = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hashEntry.NameHashPartB = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - hashEntry.Locale = (MoPaQLocale)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - hashEntry.Platform = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - hashEntry.BlockIndex = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - return hashEntry; - } - - #endregion + //#endregion } - internal enum MoPaQLocale : short - { - Neutral = 0, - AmericanEnglish = 0, - ChineseTaiwan = 0x404, - Czech = 0x405, - German = 0x407, - English = 0x409, - Spanish = 0x40A, - French = 0x40C, - Italian = 0x410, - Japanese = 0x411, - Korean = 0x412, - Polish = 0x415, - Portuguese = 0x416, - Russian = 0x419, - EnglishUK = 0x809, - } - - /// - /// Block table contains informations about file sizes and way of their storage within - /// the archive. It also contains the position of file content in the archive. Size - /// of block table entry is (like hash table entry). The block table is also encrypted. - /// internal class MoPaQBlockEntry { - #region Constants + //#region Constants - public const int Size = 0x10; + //public const int Size = 0x10; - #endregion + //#endregion - #region Properties + //#region Serialization - /// - /// Offset of the beginning of the file data, relative to the beginning of the archive. - /// - public uint FilePosition { get; private set; } + ///// + ///// Deserialize at into a new MoPaQBlockEntry object + ///// + //public static MoPaQBlockEntry Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Compressed file size - /// - public uint CompressedSize { get; private set; } + // MoPaQBlockEntry blockEntry = new MoPaQBlockEntry(); - /// - /// Size of uncompressed file - /// - public uint UncompressedSize { get; private set; } + // blockEntry.FilePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // blockEntry.CompressedSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // blockEntry.UncompressedSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // blockEntry.Flags = (MoPaQFileFlags)BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - /// - /// Flags for the file. - /// - public MoPaQFileFlags Flags { get; private set; } + // return blockEntry; + //} - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQBlockEntry object - /// - public static MoPaQBlockEntry Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQBlockEntry blockEntry = new MoPaQBlockEntry(); - - blockEntry.FilePosition = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - blockEntry.CompressedSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - blockEntry.UncompressedSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - blockEntry.Flags = (MoPaQFileFlags)BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - - return blockEntry; - } - - #endregion + //#endregion } - [Flags] - internal enum MoPaQFileFlags : uint - { - /// - /// File is compressed using PKWARE Data compression library - /// - MPQ_FILE_IMPLODE = 0x00000100, - - /// - /// File is compressed using combination of compression methods - /// - MPQ_FILE_COMPRESS = 0x00000200, - - /// - /// The file is encrypted - /// - MPQ_FILE_ENCRYPTED = 0x00010000, - - /// - /// The decryption key for the file is altered according to the - /// position of the file in the archive - /// - MPQ_FILE_FIX_KEY = 0x00020000, - - /// - /// The file contains incremental patch for an existing file in base MPQ - /// - MPQ_FILE_PATCH_FILE = 0x00100000, - - /// - /// Instead of being divided to 0x1000-bytes blocks, the file is stored - /// as single unit - /// - MPQ_FILE_SINGLE_UNIT = 0x01000000, - - /// - /// File is a deletion marker, indicating that the file no longer exists. - /// This is used to allow patch archives to delete files present in - /// lower-priority archives in the search chain. The file usually has - /// length of 0 or 1 byte and its name is a hash - /// - MPQ_FILE_DELETE_MARKER = 0x02000000, - - /// - /// File has checksums for each sector (explained in the File Data section). - /// Ignored if file is not compressed or imploded. - /// - MPQ_FILE_SECTOR_CRC = 0x04000000, - - /// - /// Set if file exists, reset when the file was deleted - /// - MPQ_FILE_EXISTS = 0x80000000, - } - - /// - /// This structure contains size of the patch, flags and also MD5 of the patch. - /// internal class MoPaQPatchInfo { - #region Properties + //#region Serialization - /// - /// Length of patch info header, in bytes - /// - public uint Length { get; private set; } + ///// + ///// Deserialize at into a new MoPaQPatchInfo object + ///// + //public static MoPaQPatchInfo Deserialize(byte[] data, ref int dataPtr) + //{ + // if (data == null || dataPtr < 0) + // return null; - /// - /// Flags. 0x80000000 = MD5 (?) - /// - public uint Flags { get; private set; } + // MoPaQPatchInfo patchInfo = new MoPaQPatchInfo(); - /// - /// Uncompressed size of the patch file - /// - public uint DataSize { get; private set; } + // patchInfo.Length = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // patchInfo.Flags = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // patchInfo.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + // Array.Copy(data, dataPtr, patchInfo.MD5, 0, 0x10); dataPtr += 0x10; - /// - /// MD5 of the entire patch file after decompression - /// - public byte[] MD5 { get; private set; } = new byte[0x10]; + // // TODO: Fill the sector offset table - /// - /// The sector offset table (variable length) - /// - public uint[] SectorOffsetTable { get; private set; } + // return patchInfo; + //} - #endregion - - #region Serialization - - /// - /// Deserialize at into a new MoPaQPatchInfo object - /// - public static MoPaQPatchInfo Deserialize(byte[] data, ref int dataPtr) - { - if (data == null || dataPtr < 0) - return null; - - MoPaQPatchInfo patchInfo = new MoPaQPatchInfo(); - - patchInfo.Length = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - patchInfo.Flags = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - patchInfo.DataSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - Array.Copy(data, dataPtr, patchInfo.MD5, 0, 0x10); dataPtr += 0x10; - - // TODO: Fill the sector offset table - - return patchInfo; - } - - #endregion + //#endregion } - /// - /// Each incremental patch file in a patch MPQ starts with a header. It is supposed - /// to be a structure with variable length. - /// internal class MoPaQPatchHeader { - #region Constants + //#region Constants - #region Signatures + //#region Signatures - #region Patch Header + //#region Patch Header - /// - /// Human-readable signature - /// - public static readonly string PatchSignatureString = $"PTCH"; + ///// + ///// Human-readable signature + ///// + //public static readonly string PatchSignatureString = $"PTCH"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint PatchSignatureValue = 0x48435450; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint PatchSignatureValue = 0x48435450; - /// - /// Signature as a byte array - /// - public static readonly byte[] PatchSignatureBytes = new byte[] { 0x50, 0x54, 0x43, 0x48 }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] PatchSignatureBytes = new byte[] { 0x50, 0x54, 0x43, 0x48 }; - #endregion + //#endregion - #region MD5 Block + //#region MD5 Block - /// - /// Human-readable signature - /// - public static readonly string Md5SignatureString = $"MD5_"; + ///// + ///// Human-readable signature + ///// + //public static readonly string Md5SignatureString = $"MD5_"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint Md5SignatureValue = 0x5F35444D; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint Md5SignatureValue = 0x5F35444D; - /// - /// Signature as a byte array - /// - public static readonly byte[] Md5SignatureBytes = new byte[] { 0x4D, 0x44, 0x35, 0x5F }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] Md5SignatureBytes = new byte[] { 0x4D, 0x44, 0x35, 0x5F }; - #endregion + //#endregion - #region XFRM Block + //#region XFRM Block - /// - /// Human-readable signature - /// - public static readonly string XFRMSignatureString = $"XFRM"; + ///// + ///// Human-readable signature + ///// + //public static readonly string XFRMSignatureString = $"XFRM"; - /// - /// Signature as an unsigned Int32 value - /// - public const uint XFRMSignatureValue = 0x4D524658; + ///// + ///// Signature as an unsigned Int32 value + ///// + //public const uint XFRMSignatureValue = 0x4D524658; - /// - /// Signature as a byte array - /// - public static readonly byte[] XFRMSignatureBytes = new byte[] { 0x58, 0x46, 0x52, 0x4D }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] XFRMSignatureBytes = new byte[] { 0x58, 0x46, 0x52, 0x4D }; - #endregion + //#endregion - #region BSDIFF Patch Type + //#region BSDIFF Patch Type - /// - /// Human-readable signature - /// - public static readonly string BSDIFF40SignatureString = $"BSDIFF40"; + ///// + ///// Human-readable signature + ///// + //public static readonly string BSDIFF40SignatureString = $"BSDIFF40"; - /// - /// Signature as an unsigned Int64 value - /// - public const ulong BSDIFF40SignatureValue = 0x3034464649445342; + ///// + ///// Signature as an unsigned Int64 value + ///// + //public const ulong BSDIFF40SignatureValue = 0x3034464649445342; - /// - /// Signature as a byte array - /// - public static readonly byte[] BSDIFF40SignatureBytes = new byte[] { 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30 }; + ///// + ///// Signature as a byte array + ///// + //public static readonly byte[] BSDIFF40SignatureBytes = new byte[] { 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30 }; - #endregion + //#endregion - #endregion + //#endregion - #endregion - - #region Properties - - #region PATCH Header - - /// - /// 'PTCH' - /// - public uint PatchSignature { get; private set; } - - /// - /// Size of the entire patch (decompressed) - /// - public int SizeOfPatchData { get; private set; } - - /// - /// Size of the file before patch - /// - public int SizeBeforePatch { get; private set; } - - /// - /// Size of file after patch - /// - public int SizeAfterPatch { get; private set; } - - #endregion - - #region MD5 Block - - /// - /// 'MD5_' - /// - public uint Md5Signature { get; private set; } - - /// - /// Size of the MD5 block, including the signature and size itself - /// - public int Md5BlockSize { get; private set; } - - /// - /// MD5 of the original (unpached) file - /// - public byte[] Md5BeforePatch { get; private set; } = new byte[0x10]; - - /// - /// MD5 of the patched file - /// - public byte[] Md5AfterPatch { get; private set; } = new byte[0x10]; - - #endregion - - #region XFRM Block - - /// - /// 'XFRM' - /// - public uint XfrmSignature { get; private set; } - - /// - /// Size of the XFRM block, includes XFRM header and patch data - /// - public int XfrmBlockSize { get; private set; } - - /// - /// Type of patch ('BSD0' or 'COPY') - /// - public MoPaQPatchType PatchType { get; private set; } - - #endregion - - #region Patch Data - BSD0 - - /// - /// 'BSDIFF40' signature - /// - public ulong BsdiffSignature { get; private set; } - - /// - /// Size of CTRL block (in bytes) - /// - public long CtrlBlockSize { get; private set; } - - /// - /// Size of DATA block (in bytes) - /// - public long DataBlockSize { get; private set; } - - /// - /// Size of file after applying the patch (in bytes) - /// - public long NewFileSize { get; private set; } - - // TODO: Fill rest of data from http://zezula.net/en/mpq/patchfiles.html - // CTRL block - // DATA block - // EXTRA block - - #endregion - - #region Patch Data - COPY - - /// - /// File data are simply replaced by the data in the patch. - /// - public byte[] PatchDataCopy { get; private set; } - - #endregion - - #endregion - } - - internal enum MoPaQPatchType : uint - { - /// - /// Blizzard-modified version of BSDIFF40 incremental patch - /// - BSD0 = 0x30445342, - - /// - /// Unknown - /// - BSDP = 0x50445342, - - /// - /// Plain replace - /// - COPY = 0x59504F43, - - /// - /// Unknown - /// - COUP = 0x50554F43, - - /// - /// Unknown - /// - CPOG = 0x474F5043, + //#endregion } #endregion