mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-02-13 05:35:24 +00:00
652 lines
23 KiB
C#
652 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using BinaryObjectScanner.Models.MoPaQ;
|
|
using BinaryObjectScanner.Utilities;
|
|
using static BinaryObjectScanner.Models.MoPaQ.Constants;
|
|
|
|
namespace BinaryObjectScanner.Builders
|
|
{
|
|
public class MoPaQ
|
|
{
|
|
#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;
|
|
|
|
// Create a memory stream and parse that
|
|
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
|
return ParseArchive(dataStream);
|
|
}
|
|
|
|
#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 ParseArchive(Stream data)
|
|
{
|
|
// If the data is invalid
|
|
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
|
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 == 0x1B51504D)
|
|
{
|
|
// 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 == 0x1A51504D)
|
|
{
|
|
// Try to parse the archive header
|
|
var archiveHeader = ParseArchiveHeader(data);
|
|
if (archiveHeader == null)
|
|
return null;
|
|
|
|
// 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 == FormatVersion.Format1)
|
|
{
|
|
// If we have a hash table
|
|
long hashTableOffset = archive.ArchiveHeader.HashTablePosition;
|
|
if (hashTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
|
|
|
// Read in the hash table
|
|
var hashTable = new List<HashEntry>();
|
|
|
|
while (data.Position < hashTableEnd)
|
|
{
|
|
var hashEntry = ParseHashEntry(data);
|
|
if (hashEntry == null)
|
|
return null;
|
|
|
|
hashTable.Add(hashEntry);
|
|
}
|
|
|
|
archive.HashTable = hashTable.ToArray();
|
|
}
|
|
}
|
|
|
|
// Version 2 and 3
|
|
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;
|
|
if (hashTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
|
|
|
// Read in the hash table
|
|
var hashTable = new List<HashEntry>();
|
|
|
|
while (data.Position < hashTableEnd)
|
|
{
|
|
var hashEntry = ParseHashEntry(data);
|
|
if (hashEntry == null)
|
|
return null;
|
|
|
|
hashTable.Add(hashEntry);
|
|
}
|
|
|
|
archive.HashTable = hashTable.ToArray();
|
|
}
|
|
}
|
|
|
|
// Version 4
|
|
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
|
{
|
|
// If we have a hash table
|
|
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
|
|
if (hashTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long hashTableEnd = hashTableOffset + (long)archive.ArchiveHeader.HashTableSizeLong;
|
|
|
|
// Read in the hash table
|
|
var hashTable = new List<HashEntry>();
|
|
|
|
while (data.Position < hashTableEnd)
|
|
{
|
|
var hashEntry = ParseHashEntry(data);
|
|
if (hashEntry == null)
|
|
return null;
|
|
|
|
hashTable.Add(hashEntry);
|
|
}
|
|
|
|
archive.HashTable = hashTable.ToArray();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Block Table
|
|
|
|
// Version 1
|
|
if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
|
|
{
|
|
// If we have a block table
|
|
long blockTableOffset = archive.ArchiveHeader.BlockTablePosition;
|
|
if (blockTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
|
|
|
// Read in the block table
|
|
var blockTable = new List<BlockEntry>();
|
|
|
|
while (data.Position < blockTableEnd)
|
|
{
|
|
var blockEntry = ParseBlockEntry(data);
|
|
if (blockEntry == null)
|
|
return null;
|
|
|
|
blockTable.Add(blockEntry);
|
|
}
|
|
|
|
archive.BlockTable = blockTable.ToArray();
|
|
}
|
|
}
|
|
|
|
// Version 2 and 3
|
|
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;
|
|
if (blockTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
|
|
|
// Read in the block table
|
|
var blockTable = new List<BlockEntry>();
|
|
|
|
while (data.Position < blockTableEnd)
|
|
{
|
|
var blockEntry = ParseBlockEntry(data);
|
|
if (blockEntry == null)
|
|
return null;
|
|
|
|
blockTable.Add(blockEntry);
|
|
}
|
|
|
|
archive.BlockTable = blockTable.ToArray();
|
|
}
|
|
}
|
|
|
|
// Version 4
|
|
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
|
{
|
|
// If we have a block table
|
|
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
|
|
if (blockTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
|
|
|
// Find the ending offset based on size
|
|
long blockTableEnd = blockTableOffset + (long)archive.ArchiveHeader.BlockTableSizeLong;
|
|
|
|
// Read in the block table
|
|
var blockTable = new List<BlockEntry>();
|
|
|
|
while (data.Position < blockTableEnd)
|
|
{
|
|
var blockEntry = ParseBlockEntry(data);
|
|
if (blockEntry == null)
|
|
return null;
|
|
|
|
blockTable.Add(blockEntry);
|
|
}
|
|
|
|
archive.BlockTable = blockTable.ToArray();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Hi-Block Table
|
|
|
|
// Version 2, 3, and 4
|
|
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format2)
|
|
{
|
|
// If we have a hi-block table
|
|
long hiBlockTableOffset = (long)archive.ArchiveHeader.HiBlockTablePosition;
|
|
if (hiBlockTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(hiBlockTableOffset, SeekOrigin.Begin);
|
|
|
|
// Read in the hi-block table
|
|
var hiBlockTable = new List<short>();
|
|
|
|
for (int i = 0; i < archive.BlockTable.Length; i++)
|
|
{
|
|
short hiBlockEntry = data.ReadInt16();
|
|
hiBlockTable.Add(hiBlockEntry);
|
|
}
|
|
|
|
archive.HiBlockTable = hiBlockTable.ToArray();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BET Table
|
|
|
|
// Version 3 and 4
|
|
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
|
{
|
|
// If we have a BET table
|
|
long betTableOffset = (long)archive.ArchiveHeader.BetTablePosition;
|
|
if (betTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(betTableOffset, SeekOrigin.Begin);
|
|
|
|
// Read in the BET table
|
|
var betTable = ParseBetTable(data);
|
|
if (betTable != null)
|
|
return null;
|
|
|
|
archive.BetTable = betTable;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HET Table
|
|
|
|
// Version 3 and 4
|
|
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
|
{
|
|
// If we have a HET table
|
|
long hetTableOffset = (long)archive.ArchiveHeader.HetTablePosition;
|
|
if (hetTableOffset != 0)
|
|
{
|
|
// Seek to the offset
|
|
data.Seek(hetTableOffset, SeekOrigin.Begin);
|
|
|
|
// Read in the HET table
|
|
var hetTable = ParseHetTable(data);
|
|
if (hetTable != null)
|
|
return null;
|
|
|
|
archive.HetTable = hetTable;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
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
|
|
byte[] signature = data.ReadBytes(4);
|
|
archiveHeader.Signature = Encoding.ASCII.GetString(signature);
|
|
if (archiveHeader.Signature != ArchiveHeaderSignatureString)
|
|
return null;
|
|
|
|
archiveHeader.HeaderSize = data.ReadUInt32();
|
|
archiveHeader.ArchiveSize = data.ReadUInt32();
|
|
archiveHeader.FormatVersion = (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 >= FormatVersion.Format2)
|
|
{
|
|
archiveHeader.HiBlockTablePosition = data.ReadUInt64();
|
|
archiveHeader.HashTablePositionHi = data.ReadUInt16();
|
|
archiveHeader.BlockTablePositionHi = data.ReadUInt16();
|
|
}
|
|
|
|
// V3
|
|
if (archiveHeader.FormatVersion >= FormatVersion.Format3)
|
|
{
|
|
archiveHeader.ArchiveSizeLong = data.ReadUInt64();
|
|
archiveHeader.BetTablePosition = data.ReadUInt64();
|
|
archiveHeader.HetTablePosition = data.ReadUInt64();
|
|
}
|
|
|
|
// V4
|
|
if (archiveHeader.FormatVersion >= FormatVersion.Format4)
|
|
{
|
|
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();
|
|
|
|
byte[] signature = data.ReadBytes(4);
|
|
userData.Signature = Encoding.ASCII.GetString(signature);
|
|
if (userData.Signature != UserDataSignatureString)
|
|
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
|
|
byte[] signature = data.ReadBytes(4);
|
|
hetTable.Signature = Encoding.ASCII.GetString(signature);
|
|
if (hetTable.Signature != HetTableSignatureString)
|
|
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
|
|
byte[] signature = data.ReadBytes(4);
|
|
betTable.Signature = Encoding.ASCII.GetString(signature);
|
|
if (betTable.Signature != BetTableSignatureString)
|
|
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>
|
|
/// <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
|
|
|
|
#region Helpers
|
|
|
|
/// <summary>
|
|
/// Buffer for encryption and decryption
|
|
/// </summary>
|
|
private uint[] _stormBuffer = new uint[STORM_BUFFER_SIZE];
|
|
|
|
/// <summary>
|
|
/// Prepare the encryption table
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypt a single block of data
|
|
/// </summary>
|
|
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
|
|
}
|
|
}
|