diff --git a/BurnOutSharp.Builder/MoPaQ.cs b/BurnOutSharp.Builder/MoPaQ.cs new file mode 100644 index 00000000..32abcb43 --- /dev/null +++ b/BurnOutSharp.Builder/MoPaQ.cs @@ -0,0 +1,812 @@ +using System; +using System.IO; +using BurnOutSharp.Models.MoPaQ; + +namespace BurnOutSharp.Builder +{ + // TODO: Make Stream Data rely on Byte Data + public class MoPaQ + { + #region Constants + + #region User Data + + public const int UserDataSize = 0x10; + + /// + /// Human-readable signature + /// + public static readonly string UserDataSignatureString = $"MPQ{(char)0x1B}"; + + /// + /// Signature as an unsigned Int32 value + /// + public const uint UserDataSignatureValue = 0x1B51504D; + + /// + /// Signature as a byte array + /// + public static readonly byte[] UserDataSignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1B }; + + #endregion + + #region Archive Header + + #region Signatures + + /// + /// Human-readable signature + /// + public static readonly string ArchiveHeaderSignatureString = $"MPQ{(char)0x1A}"; + + /// + /// Signature as an unsigned Int32 value + /// + public const uint ArchiveHeaderSignatureValue = 0x1A51504D; + + /// + /// Signature as a byte array + /// + public static readonly byte[] ArchiveHeaderSignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1A }; + + #endregion + + #region Header Sizes + + public const int ArchiveHeaderHeaderVersion1Size = 0x20; + + public const int ArchiveHeaderHeaderVersion2Size = 0x2C; + + public const int ArchiveHeaderHeaderVersion3Size = 0x44; + + public const int ArchiveHeaderHeaderVersion4Size = 0xD0; + + #endregion + + #endregion + + #region HET Table + + public const int HetTableSize = 0x44; + + /// + /// Human-readable signature + /// + public static readonly string HetTableSignatureString = $"HET{(char)0x1A}"; + + /// + /// Signature as an unsigned Int32 value + /// + public const uint HetTableSignatureValue = 0x1A544548; + + /// + /// Signature as a byte array + /// + public static readonly byte[] HetTableSignatureBytes = new byte[] { 0x48, 0x45, 0x54, 0x1A }; + + #endregion + + #region BET Table + + public const int BetTableSize = 0x88; + + /// + /// Human-readable signature + /// + public static readonly string BetTableSignatureString = $"BET{(char)0x1A}"; + + /// + /// Signature as an unsigned Int32 value + /// + public const uint BetTableSignatureValue = 0x1A544542; + + /// + /// Signature as a byte array + /// + public static readonly byte[] BetTableSignatureBytes = new byte[] { 0x42, 0x45, 0x54, 0x1A }; + + #endregion + + #region Hash Entry + + public const int HashEntrySize = 0x10; + + #endregion + + #region Block Entry + + public const int BlockEntrySize = 0x10; + + #endregion + + #region Patch Header + + #region Signatures + + #region Patch Header + + /// + /// Human-readable signature + /// + public static readonly string PatchSignatureString = $"PTCH"; + + /// + /// 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 }; + + #endregion + + #region MD5 Block + + /// + /// Human-readable signature + /// + public static readonly string Md5SignatureString = $"MD5_"; + + /// + /// 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 }; + + #endregion + + #region XFRM Block + + /// + /// Human-readable signature + /// + public static readonly string XFRMSignatureString = $"XFRM"; + + /// + /// 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 }; + + #endregion + + #region BSDIFF Patch Type + + /// + /// Human-readable signature + /// + public static readonly string BSDIFF40SignatureString = $"BSDIFF40"; + + /// + /// 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 }; + + #endregion + + #endregion + + #endregion + + #endregion + + #region Byte Data + + /// + /// Parse a byte array into a MoPaQ archive + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled archive on success, null on error + public static Archive ParseArchive(byte[] data, int offset) + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Cache the current offset + int initialOffset = offset; + + // Create a new archive to fill + var archive = new Archive(); + + #region User Data + + // Check for User Data + uint possibleSignature = BitConverter.ToUInt32(data, offset); + if (possibleSignature == UserDataSignatureValue) + { + // Save the current position for offset correction + int basePtr = offset; + + // Deserialize the user data, returning null if invalid + var userData = ParseUserData(data, ref offset); + if (userData == null) + return null; + + // Set the user data + archive.UserData = userData; + + // Set the starting position according to the header offset + offset = basePtr + (int)archive.UserData.HeaderOffset; + } + + #endregion + + #region Archive Header + + // Check for the Header + possibleSignature = BitConverter.ToUInt32(data, offset); + if (possibleSignature == ArchiveHeaderSignatureValue) + { + // Try to parse the archive header + var archiveHeader = ParseArchiveHeader(data, ref offset); + if (archiveHeader == null) + return null; + + // Set the archive header + archive.ArchiveHeader = archiveHeader; + } + + #endregion + + // 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; + } + + /// + /// Parse a byte array into a archive header + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled archive header on success, null on error + private static ArchiveHeader ParseArchiveHeader(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + ArchiveHeader archiveHeader = new ArchiveHeader(); + + // V1 - Common + archiveHeader.Signature = data.ReadUInt32(ref offset); + if (archiveHeader.Signature != ArchiveHeaderSignatureValue) + return null; + + archiveHeader.HeaderSize = data.ReadUInt32(ref offset); + archiveHeader.ArchiveSize = data.ReadUInt32(ref offset); + archiveHeader.FormatVersion = data.ReadUInt16(ref offset); + archiveHeader.BlockSize = data.ReadUInt16(ref offset); + archiveHeader.HashTablePosition = data.ReadUInt32(ref offset); + archiveHeader.BlockTablePosition = data.ReadUInt32(ref offset); + archiveHeader.HashTableSize = data.ReadUInt32(ref offset); + archiveHeader.BlockTableSize = data.ReadUInt32(ref offset); + + // V2 + if (archiveHeader.FormatVersion >= 2 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion2Size) + { + archiveHeader.HiBlockTablePosition = data.ReadUInt64(ref offset); + archiveHeader.HashTablePositionHi = data.ReadUInt16(ref offset); + archiveHeader.BlockTablePositionHi = data.ReadUInt16(ref offset); + } + + // V3 + if (archiveHeader.FormatVersion >= 3 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion3Size) + { + archiveHeader.ArchiveSizeLong = data.ReadUInt64(ref offset); + archiveHeader.BetTablePosition = data.ReadUInt64(ref offset); + archiveHeader.HetTablePosition = data.ReadUInt64(ref offset); + } + + // V4 + if (archiveHeader.FormatVersion >= 4 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion4Size) + { + archiveHeader.HashTableSizeLong = data.ReadUInt64(ref offset); + archiveHeader.BlockTableSizeLong = data.ReadUInt64(ref offset); + archiveHeader.HiBlockTableSize = data.ReadUInt64(ref offset); + archiveHeader.HetTableSize = data.ReadUInt64(ref offset); + archiveHeader.BetTablesize = data.ReadUInt64(ref offset); + archiveHeader.RawChunkSize = data.ReadUInt32(ref offset); + + archiveHeader.BlockTableMD5 = data.ReadBytes(ref offset, 0x10); + archiveHeader.HashTableMD5 = data.ReadBytes(ref offset, 0x10); + archiveHeader.HiBlockTableMD5 = data.ReadBytes(ref offset, 0x10); + archiveHeader.BetTableMD5 = data.ReadBytes(ref offset, 0x10); + archiveHeader.HetTableMD5 = data.ReadBytes(ref offset, 0x10); + archiveHeader.HetTableMD5 = data.ReadBytes(ref offset, 0x10); + } + + return archiveHeader; + } + + /// + /// Parse a byte array into a user data object + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled user data on success, null on error + private static UserData ParseUserData(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + UserData userData = new UserData(); + + userData.Signature = data.ReadUInt32(ref offset); + if (userData.Signature != UserDataSignatureValue) + return null; + + userData.UserDataSize = data.ReadUInt32(ref offset); + userData.HeaderOffset = data.ReadUInt32(ref offset); + userData.UserDataHeaderSize = data.ReadUInt32(ref offset); + + return userData; + } + + /// + /// Parse a byte array into a HET table + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled HET table on success, null on error + private static HetTable ParseHetTable(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + HetTable hetTable = new HetTable(); + + // Common Headers + hetTable.Signature = data.ReadUInt32(ref offset); + if (hetTable.Signature != HetTableSignatureValue) + return null; + + hetTable.Version = data.ReadUInt32(ref offset); + hetTable.DataSize = data.ReadUInt32(ref offset); + + // HET-Specific + hetTable.TableSize = data.ReadUInt32(ref offset); + hetTable.MaxFileCount = data.ReadUInt32(ref offset); + hetTable.HashTableSize = data.ReadUInt32(ref offset); + hetTable.TotalIndexSize = data.ReadUInt32(ref offset); + hetTable.IndexSizeExtra = data.ReadUInt32(ref offset); + hetTable.IndexSize = data.ReadUInt32(ref offset); + hetTable.BlockTableSize = data.ReadUInt32(ref offset); + hetTable.HashTable = data.ReadBytes(ref offset, (int)hetTable.HashTableSize); + + // TODO: Populate the file indexes array + hetTable.FileIndexes = new byte[(int)hetTable.HashTableSize][]; + + return hetTable; + } + + /// + /// Parse a byte array into a BET table + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled BET table on success, null on error + private static BetTable ParseBetTable(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + BetTable betTable = new BetTable(); + + // Common Headers + betTable.Signature = data.ReadUInt32(ref offset); + if (betTable.Signature != BetTableSignatureValue) + return null; + + betTable.Version = data.ReadUInt32(ref offset); + betTable.DataSize = data.ReadUInt32(ref offset); + + // BET-Specific + betTable.TableSize = data.ReadUInt32(ref offset); + betTable.FileCount = data.ReadUInt32(ref offset); + betTable.Unknown = data.ReadUInt32(ref offset); + betTable.TableEntrySize = data.ReadUInt32(ref offset); + + betTable.FilePositionBitIndex = data.ReadUInt32(ref offset); + betTable.FileSizeBitIndex = data.ReadUInt32(ref offset); + betTable.CompressedSizeBitIndex = data.ReadUInt32(ref offset); + betTable.FlagIndexBitIndex = data.ReadUInt32(ref offset); + betTable.UnknownBitIndex = data.ReadUInt32(ref offset); + + betTable.FilePositionBitCount = data.ReadUInt32(ref offset); + betTable.FileSizeBitCount = data.ReadUInt32(ref offset); + betTable.CompressedSizeBitCount = data.ReadUInt32(ref offset); + betTable.FlagIndexBitCount = data.ReadUInt32(ref offset); + betTable.UnknownBitCount = data.ReadUInt32(ref offset); + + betTable.TotalBetHashSize = data.ReadUInt32(ref offset); + betTable.BetHashSizeExtra = data.ReadUInt32(ref offset); + betTable.BetHashSize = data.ReadUInt32(ref offset); + betTable.BetHashArraySize = data.ReadUInt32(ref offset); + betTable.FlagCount = data.ReadUInt32(ref offset); + + betTable.FlagsArray = new uint[betTable.FlagCount]; + Buffer.BlockCopy(data, offset, betTable.FlagsArray, 0, (int)betTable.FlagCount * 4); + offset += (int)betTable.FlagCount * 4; + + // TODO: Populate the file table + // TODO: Populate the hash table + + return betTable; + } + + /// + /// Parse a byte array into a hash entry + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled hash entry on success, null on error + private static HashEntry ParseHashEntry(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + HashEntry hashEntry = new HashEntry(); + + hashEntry.NameHashPartA = data.ReadUInt32(ref offset); + hashEntry.NameHashPartB = data.ReadUInt32(ref offset); + hashEntry.Locale = (Locale)data.ReadUInt16(ref offset); + hashEntry.Platform = data.ReadUInt16(ref offset); + hashEntry.BlockIndex = data.ReadUInt32(ref offset); + + return hashEntry; + } + + /// + /// Parse a byte array into a block entry + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled block entry on success, null on error + private static BlockEntry ParseBlockEntry(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + BlockEntry blockEntry = new BlockEntry(); + + blockEntry.FilePosition = data.ReadUInt32(ref offset); + blockEntry.CompressedSize = data.ReadUInt32(ref offset); + blockEntry.UncompressedSize = data.ReadUInt32(ref offset); + blockEntry.Flags = (FileFlags)data.ReadUInt32(ref offset); + + return blockEntry; + } + + /// + /// Parse a byte array into a patch info + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled patch info on success, null on error + private static PatchInfo ParsePatchInfo(byte[] data, ref int offset) + { + // TODO: Use marshalling here instead of building + PatchInfo patchInfo = new PatchInfo(); + + patchInfo.Length = data.ReadUInt32(ref offset); + patchInfo.Flags = data.ReadUInt32(ref offset); + patchInfo.DataSize = data.ReadUInt32(ref offset); + patchInfo.MD5 = data.ReadBytes(ref offset, 0x10); + + // TODO: Fill the sector offset table + + return patchInfo; + } + + #endregion + + #region Stream Data + + /// + /// Parse a Stream into a MoPaQ archive + /// + /// Stream to parse + /// Filled archive on success, null on error + public static Archive ParseCabinet(Stream data) + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new archive to fill + var archive = new Archive(); + + #region User Data + + // Check for User Data + uint possibleSignature = data.ReadUInt32(); + data.Seek(-4, SeekOrigin.Current); + if (possibleSignature == UserDataSignatureValue) + { + // Save the current position for offset correction + long basePtr = data.Position; + + // Deserialize the user data, returning null if invalid + var userData = ParseUserData(data); + if (userData == null) + return null; + + // Set the user data + archive.UserData = userData; + + // Set the starting position according to the header offset + data.Seek(basePtr + (int)archive.UserData.HeaderOffset, SeekOrigin.Begin); + } + + #endregion + + #region Archive Header + + // Check for the Header + possibleSignature = data.ReadUInt32(); + data.Seek(-4, SeekOrigin.Current); + if (possibleSignature == ArchiveHeaderSignatureValue) + { + // Try to parse the archive header + var archiveHeader = ParseArchiveHeader(data); + if (archiveHeader == null) + return null; + + // Set the archive header + archive.ArchiveHeader = archiveHeader; + } + + #endregion + + // 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; + } + + /// + /// Parse a Stream into a archive header + /// + /// Stream to parse + /// Filled archive header on success, null on error + private static ArchiveHeader ParseArchiveHeader(Stream data) + { + ArchiveHeader archiveHeader = new ArchiveHeader(); + + // V1 - Common + archiveHeader.Signature = data.ReadUInt32(); + if (archiveHeader.Signature != ArchiveHeaderSignatureValue) + return null; + + archiveHeader.HeaderSize = data.ReadUInt32(); + archiveHeader.ArchiveSize = data.ReadUInt32(); + archiveHeader.FormatVersion = data.ReadUInt16(); + archiveHeader.BlockSize = data.ReadUInt16(); + archiveHeader.HashTablePosition = data.ReadUInt32(); + archiveHeader.BlockTablePosition = data.ReadUInt32(); + archiveHeader.HashTableSize = data.ReadUInt32(); + archiveHeader.BlockTableSize = data.ReadUInt32(); + + // V2 + if (archiveHeader.FormatVersion >= 2 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion2Size) + { + archiveHeader.HiBlockTablePosition = data.ReadUInt64(); + archiveHeader.HashTablePositionHi = data.ReadUInt16(); + archiveHeader.BlockTablePositionHi = data.ReadUInt16(); + } + + // V3 + if (archiveHeader.FormatVersion >= 3 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion3Size) + { + archiveHeader.ArchiveSizeLong = data.ReadUInt64(); + archiveHeader.BetTablePosition = data.ReadUInt64(); + archiveHeader.HetTablePosition = data.ReadUInt64(); + } + + // V4 + if (archiveHeader.FormatVersion >= 4 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion4Size) + { + archiveHeader.HashTableSizeLong = data.ReadUInt64(); + archiveHeader.BlockTableSizeLong = data.ReadUInt64(); + archiveHeader.HiBlockTableSize = data.ReadUInt64(); + archiveHeader.HetTableSize = data.ReadUInt64(); + archiveHeader.BetTablesize = data.ReadUInt64(); + archiveHeader.RawChunkSize = data.ReadUInt32(); + + archiveHeader.BlockTableMD5 = data.ReadBytes(0x10); + archiveHeader.HashTableMD5 = data.ReadBytes(0x10); + archiveHeader.HiBlockTableMD5 = data.ReadBytes(0x10); + archiveHeader.BetTableMD5 = data.ReadBytes(0x10); + archiveHeader.HetTableMD5 = data.ReadBytes(0x10); + archiveHeader.HetTableMD5 = data.ReadBytes(0x10); + } + + return archiveHeader; + } + + /// + /// Parse a Stream into a user data object + /// + /// Stream to parse + /// Filled user data on success, null on error + private static UserData ParseUserData(Stream data) + { + UserData userData = new UserData(); + + userData.Signature = data.ReadUInt32(); + if (userData.Signature != UserDataSignatureValue) + return null; + + userData.UserDataSize = data.ReadUInt32(); + userData.HeaderOffset = data.ReadUInt32(); + userData.UserDataHeaderSize = data.ReadUInt32(); + + return userData; + } + + /// + /// Parse a Stream into a HET table + /// + /// Stream to parse + /// Filled HET table on success, null on error + private static HetTable ParseHetTable(Stream data) + { + HetTable hetTable = new HetTable(); + + // Common Headers + hetTable.Signature = data.ReadUInt32(); + if (hetTable.Signature != HetTableSignatureValue) + return null; + + hetTable.Version = data.ReadUInt32(); + hetTable.DataSize = data.ReadUInt32(); + + // HET-Specific + hetTable.TableSize = data.ReadUInt32(); + hetTable.MaxFileCount = data.ReadUInt32(); + hetTable.HashTableSize = data.ReadUInt32(); + hetTable.TotalIndexSize = data.ReadUInt32(); + hetTable.IndexSizeExtra = data.ReadUInt32(); + hetTable.IndexSize = data.ReadUInt32(); + hetTable.BlockTableSize = data.ReadUInt32(); + hetTable.HashTable = data.ReadBytes((int)hetTable.HashTableSize); + + // TODO: Populate the file indexes array + hetTable.FileIndexes = new byte[(int)hetTable.HashTableSize][]; + + return hetTable; + } + + /// + /// Parse a Stream into a BET table + /// + /// Stream to parse + /// Filled BET table on success, null on error + private static BetTable ParseBetTable(Stream data) + { + BetTable betTable = new BetTable(); + + // Common Headers + betTable.Signature = data.ReadUInt32(); + if (betTable.Signature != BetTableSignatureValue) + return null; + + betTable.Version = data.ReadUInt32(); + betTable.DataSize = data.ReadUInt32(); + + // BET-Specific + betTable.TableSize = data.ReadUInt32(); + betTable.FileCount = data.ReadUInt32(); + betTable.Unknown = data.ReadUInt32(); + betTable.TableEntrySize = data.ReadUInt32(); + + betTable.FilePositionBitIndex = data.ReadUInt32(); + betTable.FileSizeBitIndex = data.ReadUInt32(); + betTable.CompressedSizeBitIndex = data.ReadUInt32(); + betTable.FlagIndexBitIndex = data.ReadUInt32(); + betTable.UnknownBitIndex = data.ReadUInt32(); + + betTable.FilePositionBitCount = data.ReadUInt32(); + betTable.FileSizeBitCount = data.ReadUInt32(); + betTable.CompressedSizeBitCount = data.ReadUInt32(); + betTable.FlagIndexBitCount = data.ReadUInt32(); + betTable.UnknownBitCount = data.ReadUInt32(); + + betTable.TotalBetHashSize = data.ReadUInt32(); + betTable.BetHashSizeExtra = data.ReadUInt32(); + betTable.BetHashSize = data.ReadUInt32(); + betTable.BetHashArraySize = data.ReadUInt32(); + betTable.FlagCount = data.ReadUInt32(); + + betTable.FlagsArray = new uint[betTable.FlagCount]; + byte[] flagsArray = data.ReadBytes((int)betTable.FlagCount * 4); + Buffer.BlockCopy(flagsArray, 0, betTable.FlagsArray, 0, (int)betTable.FlagCount * 4); + + // TODO: Populate the file table + // TODO: Populate the hash table + + return betTable; + } + + /// + /// Parse a Stream into a hash entry + /// + /// Stream to parse + /// Filled hash entry on success, null on error + private static HashEntry ParseHashEntry(Stream data) + { + // TODO: Use marshalling here instead of building + HashEntry hashEntry = new HashEntry(); + + hashEntry.NameHashPartA = data.ReadUInt32(); + hashEntry.NameHashPartB = data.ReadUInt32(); + hashEntry.Locale = (Locale)data.ReadUInt16(); + hashEntry.Platform = data.ReadUInt16(); + hashEntry.BlockIndex = data.ReadUInt32(); + + return hashEntry; + } + + /// + /// Parse a Stream into a block entry + /// + /// Stream to parse + /// Filled block entry on success, null on error + private static BlockEntry ParseBlockEntry(Stream data) + { + BlockEntry blockEntry = new BlockEntry(); + + blockEntry.FilePosition = data.ReadUInt32(); + blockEntry.CompressedSize = data.ReadUInt32(); + blockEntry.UncompressedSize = data.ReadUInt32(); + blockEntry.Flags = (FileFlags)data.ReadUInt32(); + + return blockEntry; + } + + /// + /// Parse a Stream into a patch info + /// + /// Stream to parse + /// Offset into the byte array + /// Filled patch info on success, null on error + private static PatchInfo ParsePatchInfo(Stream data) + { + // TODO: Use marshalling here instead of building + PatchInfo patchInfo = new PatchInfo(); + + patchInfo.Length = data.ReadUInt32(); + patchInfo.Flags = data.ReadUInt32(); + patchInfo.DataSize = data.ReadUInt32(); + patchInfo.MD5 = data.ReadBytes(0x10); + + // TODO: Fill the sector offset table + + return patchInfo; + } + + #endregion + } +} diff --git a/BurnOutSharp.Models/MoPaQ/BlockEntry.cs b/BurnOutSharp.Models/MoPaQ/BlockEntry.cs index ff769260..38eb06d1 100644 --- a/BurnOutSharp.Models/MoPaQ/BlockEntry.cs +++ b/BurnOutSharp.Models/MoPaQ/BlockEntry.cs @@ -29,6 +29,6 @@ namespace BurnOutSharp.Models.MoPaQ /// /// Flags for the file. /// - public MoPaQFileFlags Flags; + public FileFlags Flags; } } diff --git a/BurnOutSharp.Models/MoPaQ/Enums.cs b/BurnOutSharp.Models/MoPaQ/Enums.cs index 3ccf683e..b157119e 100644 --- a/BurnOutSharp.Models/MoPaQ/Enums.cs +++ b/BurnOutSharp.Models/MoPaQ/Enums.cs @@ -3,7 +3,7 @@ namespace BurnOutSharp.Models.MoPaQ { [Flags] - public enum MoPaQFileFlags : uint + public enum FileFlags : uint { /// /// File is compressed using PKWARE Data compression library @@ -76,7 +76,7 @@ namespace BurnOutSharp.Models.MoPaQ EnglishUK = 0x809, } - public enum MoPaQPatchType : uint + public enum PatchType : uint { /// /// Blizzard-modified version of BSDIFF40 incremental patch diff --git a/BurnOutSharp.Models/MoPaQ/PatchHeader.cs b/BurnOutSharp.Models/MoPaQ/PatchHeader.cs index 09b5fff8..91d7b1a2 100644 --- a/BurnOutSharp.Models/MoPaQ/PatchHeader.cs +++ b/BurnOutSharp.Models/MoPaQ/PatchHeader.cs @@ -73,7 +73,7 @@ namespace BurnOutSharp.Models.MoPaQ /// /// Type of patch ('BSD0' or 'COPY') /// - public MoPaQPatchType PatchType { get; private set; } + public PatchType PatchType { get; private set; } #endregion diff --git a/BurnOutSharp/FileType/MPQ.cs b/BurnOutSharp/FileType/MPQ.cs index 43141f2d..61033068 100644 --- a/BurnOutSharp/FileType/MPQ.cs +++ b/BurnOutSharp/FileType/MPQ.cs @@ -95,541 +95,5 @@ namespace BurnOutSharp.FileType return null; } - - // http://zezula.net/en/mpq/mpqformat.html - #region TEMPORARY AREA FOR MPQ FORMAT - - internal class MoPaQArchive - { - //#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 - } - - internal class MoPaQUserData - { - //#region Constants - - //public const int Size = 0x10; - - ///// - ///// Human-readable signature - ///// - //public static readonly string SignatureString = $"MPQ{(char)0x1B}"; - - ///// - ///// 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 }; - - //#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 - } - - internal class MoPaQArchiveHeader - { - //#region Constants - - //#region Signatures - - ///// - ///// Human-readable signature - ///// - //public static readonly string SignatureString = $"MPQ{(char)0x1A}"; - - ///// - ///// 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 }; - - //#endregion - - //#region Header Sizes - - //public const int HeaderVersion1Size = 0x20; - - //public const int HeaderVersion2Size = 0x2C; - - //public const int HeaderVersion3Size = 0x44; - - //public const int HeaderVersion4Size = 0xD0; - - //#endregion - - //#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 - } - - internal class MoPaQHetTable - { - //#region Constants - - //public const int Size = 0x44; - - ///// - ///// Human-readable signature - ///// - //public static readonly string SignatureString = $"HET{(char)0x1A}"; - - ///// - ///// 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 }; - - //#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 - } - - internal class MoPaQBetTable - { - //#region Constants - - //public const int Size = 0x88; - - ///// - ///// Human-readable signature - ///// - //public static readonly string SignatureString = $"BET{(char)0x1A}"; - - ///// - ///// 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 }; - - //#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 - } - - internal class MoPaQHashEntry - { - //#region Constants - - //public const int Size = 0x10; - - //#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 - } - - internal class MoPaQBlockEntry - { - //#region Constants - - //public const int Size = 0x10; - - //#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 - } - - internal class MoPaQPatchInfo - { - //#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 - } - - internal class MoPaQPatchHeader - { - //#region Constants - - //#region Signatures - - //#region Patch Header - - ///// - ///// Human-readable signature - ///// - //public static readonly string PatchSignatureString = $"PTCH"; - - ///// - ///// 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 }; - - //#endregion - - //#region MD5 Block - - ///// - ///// Human-readable signature - ///// - //public static readonly string Md5SignatureString = $"MD5_"; - - ///// - ///// 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 }; - - //#endregion - - //#region XFRM Block - - ///// - ///// Human-readable signature - ///// - //public static readonly string XFRMSignatureString = $"XFRM"; - - ///// - ///// 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 }; - - //#endregion - - //#region BSDIFF Patch Type - - ///// - ///// Human-readable signature - ///// - //public static readonly string BSDIFF40SignatureString = $"BSDIFF40"; - - ///// - ///// 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 }; - - - //#endregion - - //#endregion - - //#endregion - } - - #endregion } }