Add MoPaQ builder (nw)

This commit is contained in:
Matt Nadareski
2022-12-14 16:29:07 -08:00
parent 65499d1f46
commit b793b74b32
5 changed files with 816 additions and 540 deletions

View File

@@ -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;
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string UserDataSignatureString = $"MPQ{(char)0x1B}";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint UserDataSignatureValue = 0x1B51504D;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] UserDataSignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1B };
#endregion
#region Archive Header
#region Signatures
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string ArchiveHeaderSignatureString = $"MPQ{(char)0x1A}";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint ArchiveHeaderSignatureValue = 0x1A51504D;
/// <summary>
/// Signature as a byte array
/// </summary>
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;
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string HetTableSignatureString = $"HET{(char)0x1A}";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint HetTableSignatureValue = 0x1A544548;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] HetTableSignatureBytes = new byte[] { 0x48, 0x45, 0x54, 0x1A };
#endregion
#region BET Table
public const int BetTableSize = 0x88;
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string BetTableSignatureString = $"BET{(char)0x1A}";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint BetTableSignatureValue = 0x1A544542;
/// <summary>
/// Signature as a byte array
/// </summary>
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
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string PatchSignatureString = $"PTCH";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint PatchSignatureValue = 0x48435450;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] PatchSignatureBytes = new byte[] { 0x50, 0x54, 0x43, 0x48 };
#endregion
#region MD5 Block
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string Md5SignatureString = $"MD5_";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint Md5SignatureValue = 0x5F35444D;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] Md5SignatureBytes = new byte[] { 0x4D, 0x44, 0x35, 0x5F };
#endregion
#region XFRM Block
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string XFRMSignatureString = $"XFRM";
/// <summary>
/// Signature as an unsigned Int32 value
/// </summary>
public const uint XFRMSignatureValue = 0x4D524658;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] XFRMSignatureBytes = new byte[] { 0x58, 0x46, 0x52, 0x4D };
#endregion
#region BSDIFF Patch Type
/// <summary>
/// Human-readable signature
/// </summary>
public static readonly string BSDIFF40SignatureString = $"BSDIFF40";
/// <summary>
/// Signature as an unsigned Int64 value
/// </summary>
public const ulong BSDIFF40SignatureValue = 0x3034464649445342;
/// <summary>
/// Signature as a byte array
/// </summary>
public static readonly byte[] BSDIFF40SignatureBytes = new byte[] { 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30 };
#endregion
#endregion
#endregion
#endregion
#region Byte Data
/// <summary>
/// Parse a byte array into a MoPaQ archive
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled archive on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a archive header
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled archive header on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a user data object
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled user data on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a HET table
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled HET table on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a BET table
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled BET table on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a hash entry
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled hash entry on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a block entry
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled block entry on success, null on error</returns>
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;
}
/// <summary>
/// Parse a byte array into a patch info
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled patch info on success, null on error</returns>
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
/// <summary>
/// Parse a Stream into a MoPaQ archive
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled archive on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a archive header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled archive header on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a user data object
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled user data on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a HET table
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled HET table on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a BET table
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled BET table on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a hash entry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled hash entry on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a block entry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled block entry on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a patch info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled patch info on success, null on error</returns>
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
}
}

View File

@@ -29,6 +29,6 @@ namespace BurnOutSharp.Models.MoPaQ
/// <summary>
/// Flags for the file.
/// </summary>
public MoPaQFileFlags Flags;
public FileFlags Flags;
}
}

View File

@@ -3,7 +3,7 @@
namespace BurnOutSharp.Models.MoPaQ
{
[Flags]
public enum MoPaQFileFlags : uint
public enum FileFlags : uint
{
/// <summary>
/// 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
{
/// <summary>
/// Blizzard-modified version of BSDIFF40 incremental patch

View File

@@ -73,7 +73,7 @@ namespace BurnOutSharp.Models.MoPaQ
/// <summary>
/// Type of patch ('BSD0' or 'COPY')
/// </summary>
public MoPaQPatchType PatchType { get; private set; }
public PatchType PatchType { get; private set; }
#endregion

View File

@@ -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
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQArchive object
///// </summary>
//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;
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string SignatureString = $"MPQ{(char)0x1B}";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint SignatureValue = 0x1B51504D;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x50, 0x51, 0x1B };
//#endregion
//#region Serialization
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQUserData object
///// </summary>
//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
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string SignatureString = $"MPQ{(char)0x1A}";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint SignatureValue = 0x1A51504D;
///// <summary>
///// Signature as a byte array
///// </summary>
//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
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQArchiveHeader object
///// </summary>
//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;
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string SignatureString = $"HET{(char)0x1A}";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint SignatureValue = 0x1A544548;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] SignatureBytes = new byte[] { 0x48, 0x45, 0x54, 0x1A };
//#endregion
//#region Serialization
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQHetTable object
///// </summary>
//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;
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string SignatureString = $"BET{(char)0x1A}";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint SignatureValue = 0x1A544542;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] SignatureBytes = new byte[] { 0x42, 0x45, 0x54, 0x1A };
//#endregion
//#region Serialization
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQBetTable object
///// </summary>
//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
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQHashEntry object
///// </summary>
//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
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQBlockEntry object
///// </summary>
//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
///// <summary>
///// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a new MoPaQPatchInfo object
///// </summary>
//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
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string PatchSignatureString = $"PTCH";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint PatchSignatureValue = 0x48435450;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] PatchSignatureBytes = new byte[] { 0x50, 0x54, 0x43, 0x48 };
//#endregion
//#region MD5 Block
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string Md5SignatureString = $"MD5_";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint Md5SignatureValue = 0x5F35444D;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] Md5SignatureBytes = new byte[] { 0x4D, 0x44, 0x35, 0x5F };
//#endregion
//#region XFRM Block
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string XFRMSignatureString = $"XFRM";
///// <summary>
///// Signature as an unsigned Int32 value
///// </summary>
//public const uint XFRMSignatureValue = 0x4D524658;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] XFRMSignatureBytes = new byte[] { 0x58, 0x46, 0x52, 0x4D };
//#endregion
//#region BSDIFF Patch Type
///// <summary>
///// Human-readable signature
///// </summary>
//public static readonly string BSDIFF40SignatureString = $"BSDIFF40";
///// <summary>
///// Signature as an unsigned Int64 value
///// </summary>
//public const ulong BSDIFF40SignatureValue = 0x3034464649445342;
///// <summary>
///// Signature as a byte array
///// </summary>
//public static readonly byte[] BSDIFF40SignatureBytes = new byte[] { 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30 };
//#endregion
//#endregion
//#endregion
}
#endregion
}
}