diff --git a/BurnOutSharp.Builder/MoPaQ.cs b/BurnOutSharp.Builder/MoPaQ.cs
index 74dcf296..6bbc2556 100644
--- a/BurnOutSharp.Builder/MoPaQ.cs
+++ b/BurnOutSharp.Builder/MoPaQ.cs
@@ -10,198 +10,46 @@ namespace BurnOutSharp.Builder
{
#region Constants
- #region User Data
+ #region Encryption
- public const int UserDataSize = 0x10;
+ private const uint MPQ_HASH_KEY2_MIX = 0x400;
///
- /// Human-readable signature
+ /// Obtained by HashString("(block table)", MPQ_HASH_FILE_KEY)
///
- public static readonly string UserDataSignatureString = $"MPQ{(char)0x1B}";
+ private const uint MPQ_KEY_BLOCK_TABLE = 0xEC83B3A3;
///
- /// Signature as an unsigned Int32 value
+ /// Obtained by HashString("(hash table)", MPQ_HASH_FILE_KEY)
///
- public const uint UserDataSignatureValue = 0x1B51504D;
+ private const uint MPQ_KEY_HASH_TABLE = 0xC3AF3770;
- ///
- /// 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;
+ private const uint STORM_BUFFER_SIZE = 0x500;
#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
@@ -259,7 +107,7 @@ namespace BurnOutSharp.Builder
// Check for User Data
uint possibleSignature = data.ReadUInt32();
data.Seek(-4, SeekOrigin.Current);
- if (possibleSignature == UserDataSignatureValue)
+ if (possibleSignature == 0x1B51504D)
{
// Save the current position for offset correction
long basePtr = data.Position;
@@ -283,7 +131,7 @@ namespace BurnOutSharp.Builder
// Check for the Header
possibleSignature = data.ReadUInt32();
data.Seek(-4, SeekOrigin.Current);
- if (possibleSignature == ArchiveHeaderSignatureValue)
+ if (possibleSignature == 0x1A51504D)
{
// Try to parse the archive header
var archiveHeader = ParseArchiveHeader(data);
@@ -293,13 +141,19 @@ namespace BurnOutSharp.Builder
// Set the archive header
archive.ArchiveHeader = archiveHeader;
}
+ else
+ {
+ return null;
+ }
#endregion
#region Hash Table
+ // TODO: The hash table has to be be decrypted before reading
+
// Version 1
- if (archive.ArchiveHeader.FormatVersion == 0)
+ if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
{
// If we have a hash table
long hashTableOffset = archive.ArchiveHeader.HashTablePosition;
@@ -328,7 +182,7 @@ namespace BurnOutSharp.Builder
}
// Version 2 and 3
- else if (archive.ArchiveHeader.FormatVersion == 1 || archive.ArchiveHeader.FormatVersion == 2)
+ else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2 || archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
{
// If we have a hash table
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
@@ -357,7 +211,7 @@ namespace BurnOutSharp.Builder
}
// Version 4
- else if (archive.ArchiveHeader.FormatVersion == 3)
+ else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
{
// If we have a hash table
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
@@ -390,7 +244,7 @@ namespace BurnOutSharp.Builder
#region Block Table
// Version 1
- if (archive.ArchiveHeader.FormatVersion == 0)
+ if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
{
// If we have a block table
long blockTableOffset = archive.ArchiveHeader.BlockTablePosition;
@@ -419,7 +273,7 @@ namespace BurnOutSharp.Builder
}
// Version 2 and 3
- else if (archive.ArchiveHeader.FormatVersion == 1 || archive.ArchiveHeader.FormatVersion == 2)
+ else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2 || archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
{
// If we have a block table
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
@@ -448,7 +302,7 @@ namespace BurnOutSharp.Builder
}
// Version 4
- else if (archive.ArchiveHeader.FormatVersion == 3)
+ else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
{
// If we have a block table
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
@@ -481,9 +335,7 @@ namespace BurnOutSharp.Builder
#region Hi-Block Table
// Version 2, 3, and 4
- if (archive.ArchiveHeader.FormatVersion == 1
- || archive.ArchiveHeader.FormatVersion == 2
- || archive.ArchiveHeader.FormatVersion == 3)
+ if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format2)
{
// If we have a hi-block table
long hiBlockTableOffset = (long)archive.ArchiveHeader.HiBlockTablePosition;
@@ -510,7 +362,7 @@ namespace BurnOutSharp.Builder
#region BET Table
// Version 3 and 4
- if (archive.ArchiveHeader.FormatVersion == 2 || archive.ArchiveHeader.FormatVersion == 3)
+ if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
{
// If we have a BET table
long betTableOffset = (long)archive.ArchiveHeader.BetTablePosition;
@@ -533,7 +385,7 @@ namespace BurnOutSharp.Builder
#region HET Table
// Version 3 and 4
- if (archive.ArchiveHeader.FormatVersion == 2 || archive.ArchiveHeader.FormatVersion == 3)
+ if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
{
// If we have a HET table
long hetTableOffset = (long)archive.ArchiveHeader.HetTablePosition;
@@ -567,12 +419,12 @@ namespace BurnOutSharp.Builder
// V1 - Common
archiveHeader.Signature = data.ReadUInt32();
- if (archiveHeader.Signature != ArchiveHeaderSignatureValue)
+ if (archiveHeader.Signature != 0x1A51504D)
return null;
archiveHeader.HeaderSize = data.ReadUInt32();
archiveHeader.ArchiveSize = data.ReadUInt32();
- archiveHeader.FormatVersion = data.ReadUInt16();
+ archiveHeader.FormatVersion = (FormatVersion)data.ReadUInt16();
archiveHeader.BlockSize = data.ReadUInt16();
archiveHeader.HashTablePosition = data.ReadUInt32();
archiveHeader.BlockTablePosition = data.ReadUInt32();
@@ -580,7 +432,7 @@ namespace BurnOutSharp.Builder
archiveHeader.BlockTableSize = data.ReadUInt32();
// V2
- if (archiveHeader.FormatVersion >= 2 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion2Size)
+ if (archiveHeader.FormatVersion >= FormatVersion.Format2)
{
archiveHeader.HiBlockTablePosition = data.ReadUInt64();
archiveHeader.HashTablePositionHi = data.ReadUInt16();
@@ -588,7 +440,7 @@ namespace BurnOutSharp.Builder
}
// V3
- if (archiveHeader.FormatVersion >= 3 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion3Size)
+ if (archiveHeader.FormatVersion >= FormatVersion.Format3)
{
archiveHeader.ArchiveSizeLong = data.ReadUInt64();
archiveHeader.BetTablePosition = data.ReadUInt64();
@@ -596,7 +448,7 @@ namespace BurnOutSharp.Builder
}
// V4
- if (archiveHeader.FormatVersion >= 4 && archiveHeader.HeaderSize >= ArchiveHeaderHeaderVersion4Size)
+ if (archiveHeader.FormatVersion >= FormatVersion.Format4)
{
archiveHeader.HashTableSizeLong = data.ReadUInt64();
archiveHeader.BlockTableSizeLong = data.ReadUInt64();
@@ -626,7 +478,7 @@ namespace BurnOutSharp.Builder
UserData userData = new UserData();
userData.Signature = data.ReadUInt32();
- if (userData.Signature != UserDataSignatureValue)
+ if (userData.Signature != 0x1B51504D)
return null;
userData.UserDataSize = data.ReadUInt32();
@@ -647,7 +499,7 @@ namespace BurnOutSharp.Builder
// Common Headers
hetTable.Signature = data.ReadUInt32();
- if (hetTable.Signature != HetTableSignatureValue)
+ if (hetTable.Signature != 0x1A544548)
return null;
hetTable.Version = data.ReadUInt32();
@@ -680,7 +532,7 @@ namespace BurnOutSharp.Builder
// Common Headers
betTable.Signature = data.ReadUInt32();
- if (betTable.Signature != BetTableSignatureValue)
+ if (betTable.Signature != 0x1A544542)
return null;
betTable.Version = data.ReadUInt32();
@@ -777,5 +629,63 @@ namespace BurnOutSharp.Builder
}
#endregion
+
+ #region Helpers
+
+ ///
+ /// Buffer for encryption and decryption
+ ///
+ private uint[] _stormBuffer = new uint[STORM_BUFFER_SIZE];
+
+ ///
+ /// Prepare the encryption table
+ ///
+ private void PrepareCryptTable()
+ {
+ uint seed = 0x00100001;
+ for (uint index1 = 0; index1 < 0x100; index1++)
+ {
+ for (uint index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
+ {
+ seed = (seed * 125 + 3) % 0x2AAAAB;
+ uint temp1 = (seed & 0xFFFF) << 0x10;
+
+ seed = (seed * 125 + 3) % 0x2AAAAB;
+ uint temp2 = (seed & 0xFFFF);
+
+ _stormBuffer[index2] = (temp1 | temp2);
+ }
+ }
+ }
+
+ ///
+ /// Decrypt a single block of data
+ ///
+ private unsafe byte[] DecryptBlock(byte[] block, uint length, uint key)
+ {
+ uint seed = 0xEEEEEEEE;
+
+ uint[] castBlock = new uint[length / 4];
+ Buffer.BlockCopy(block, 0, castBlock, 0, (int)length);
+ int castBlockPtr = 0;
+
+ // Round to uints
+ length >>= 2;
+
+ while (length-- > 0)
+ {
+ seed += _stormBuffer[MPQ_HASH_KEY2_MIX + (key & 0xFF)];
+ uint ch = castBlock[castBlockPtr] ^ (key + seed);
+
+ key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
+ seed = ch + seed + (seed << 5) + 3;
+ castBlock[castBlockPtr++] = ch;
+ }
+
+ Buffer.BlockCopy(castBlock, 0, block, 0, (int)length);
+ return block;
+ }
+
+ #endregion
}
}
diff --git a/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs b/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs
index 4a1fe308..d97e722d 100644
--- a/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs
+++ b/BurnOutSharp.Models/MoPaQ/ArchiveHeader.cs
@@ -37,7 +37,7 @@ namespace BurnOutSharp.Models.MoPaQ
/// 2 = Format 3 (WoW - Cataclysm beta or newer)
/// 3 = Format 4 (WoW - Cataclysm beta or newer)
///
- public ushort FormatVersion;
+ public FormatVersion FormatVersion;
///
/// Power of two exponent specifying the number of 512-byte disk sectors in each logical sector
diff --git a/BurnOutSharp.Models/MoPaQ/Enums.cs b/BurnOutSharp.Models/MoPaQ/Enums.cs
index b157119e..753fd1b7 100644
--- a/BurnOutSharp.Models/MoPaQ/Enums.cs
+++ b/BurnOutSharp.Models/MoPaQ/Enums.cs
@@ -2,6 +2,58 @@
namespace BurnOutSharp.Models.MoPaQ
{
+ ///
+ /// Compression types for multiple compressions
+ ///
+ [Flags]
+ public enum CompressionType : uint
+ {
+ ///
+ /// Huffmann compression (used on WAVE files only)
+ ///
+ MPQ_COMPRESSION_HUFFMANN = 0x01,
+
+ ///
+ /// ZLIB compression
+ ///
+ MPQ_COMPRESSION_ZLIB = 0x02,
+
+ ///
+ /// PKWARE DCL compression
+ ///
+ MPQ_COMPRESSION_PKWARE = 0x08,
+
+ ///
+ /// BZIP2 compression (added in Warcraft III)
+ ///
+ MPQ_COMPRESSION_BZIP2 = 0x10,
+
+ ///
+ /// Sparse compression (added in Starcraft 2)
+ ///
+ MPQ_COMPRESSION_SPARSE = 0x20,
+
+ ///
+ /// IMA ADPCM compression (mono)
+ ///
+ MPQ_COMPRESSION_ADPCM_MONO = 0x40,
+
+ ///
+ /// IMA ADPCM compression (stereo)
+ ///
+ MPQ_COMPRESSION_ADPCM_STEREO = 0x80,
+
+ ///
+ /// LZMA compression. Added in Starcraft 2. This value is NOT a combination of flags.
+ ///
+ MPQ_COMPRESSION_LZMA = 0x12,
+
+ ///
+ /// Same compression
+ ///
+ MPQ_COMPRESSION_NEXT_SAME = 0xFFFFFFFF,
+ }
+
[Flags]
public enum FileFlags : uint
{
@@ -57,6 +109,29 @@ namespace BurnOutSharp.Models.MoPaQ
MPQ_FILE_EXISTS = 0x80000000,
}
+ public enum FormatVersion : ushort
+ {
+ ///
+ /// Format 1 (up to The Burning Crusade)
+ ///
+ Format1 = 0,
+
+ ///
+ /// Format 2 (The Burning Crusade and newer)
+ ///
+ Format2 = 1,
+
+ ///
+ /// Format 3 (WoW - Cataclysm beta or newer)
+ ///
+ Format3 = 2,
+
+ ///
+ /// Format 4 (WoW - Cataclysm beta or newer)
+ ///
+ Format4 = 3,
+ }
+
public enum Locale : short
{
Neutral = 0,