private static unsafe uint[] ReadBlockOffsets(MpqArchive archive, uint hash, long offset, int count) { int length = count * sizeof(uint); var sharedBuffer = CommonMethods.GetSharedBuffer(length); if (archive.ReadArchiveData(sharedBuffer, 0, offset, length) != length) { throw new EndOfStreamException(); } var offsets = new uint[count]; Buffer.BlockCopy(sharedBuffer, 0, offsets, 0, length); if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes(offsets); } // If hash is valid, decode the header if (hash != 0) { unchecked { CommonMethods.Decrypt(offsets, hash - 1); } } return(offsets); }
public static unsafe void DecryptWithEndianSwap(uint *data, uint hash, int length) { uint buffer, temp = 0xEEEEEEEE; for (int i = length; i-- != 0;) { unchecked { temp += encryptionTable[0x400 + (hash & 0xFF)]; buffer = CommonMethods.SwapBytes(*data) ^ (temp + hash); temp += buffer + (temp << 5) + 3; *data++ = CommonMethods.SwapBytes(buffer); hash = (hash >> 11) | (0x11111111 + ((hash ^ 0x7FF) << 21)); } } }
/// <summary>Reads the encrypted <see cref="System.UInt32"/> table at the specified offset in the archive.</summary> /// <remarks> /// This method will place the bytes in their native order. /// Reading must be done by pinning the buffer and accessing it as an <see cref="System.UInt32"/> buffer. /// The only purpose of this method is to share code between the hash table and block table reading methods. /// Because of its specific behavior, it should not be used anywhere else… /// </remarks> /// <param name="buffer">The destination buffer.</param> /// <param name="tableLength">Length of the table in units of 16 bytes.</param> /// <param name="tableOffset">The offset in the archive.</param> /// <param name="dataLength">Length of the data to read.</param> /// <param name="hash">The hash to use for decrypting the data.</param> /// <param name="compressedReadBuffer">The compressed read buffer to use for holding temporary data.</param> private unsafe void ReadEncryptedUInt32Table(byte[] buffer, long tableLength, long tableOffset, long dataLength, uint hash, byte[] compressedReadBuffer) { int uintCount = checked ((int)(tableLength << 2)); long realDataLength = checked (sizeof(uint) * uintCount); bool isCompressed = dataLength < realDataLength; // Stream.Read only takes an int length for now, and it is unlikely that the tables will ever exceed 2GB. // But anyway, if this ever happens in the future the overflow check should ensure us that the program will crash nicely. int dataLengthInt32 = checked ((int)dataLength); stream.Seek(archiveDataOffset + tableOffset, SeekOrigin.Begin); if (stream.Read(isCompressed ? compressedReadBuffer : buffer, 0, dataLengthInt32) != dataLengthInt32) { throw new EndOfStreamException(); // Throw an exception if we are not able to read as many bytes as we were told we could read… fixed(byte *bufferPointer = buffer) { uint *tablePointer = (uint *)bufferPointer; // If the data is compressed, we will have to swap endianness three times on big endian platforms, but it can't be helped. // (Endiannes swap is done by the Encryption.Decrypt method automatically when passing a byte buffer.) // However, if we don't have to decompress, endiannes swap is only done once, before decrypting. if (isCompressed) { if (hash != 0) { CommonMethods.Decrypt(compressedReadBuffer, hash, dataLengthInt32); // On big endian platforms : Read UInt32, Swap Bytes, Compute, Swap Bytes, Store UInt32 } if (CommonMethods.DecompressBlock(compressedReadBuffer, dataLengthInt32, buffer, true) != realDataLength) { throw new InvalidDataException(); // Only allow the exact amount of bytes as a result } } if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes(tablePointer, uintCount); } if (!isCompressed && hash != 0) { CommonMethods.Decrypt(tablePointer, hash, uintCount); } } }
private unsafe byte[] ApplyBsd0Patch(ref PatchInfoHeader patchInfoHeader, ref PatchHeader patchHeader, uint patchLength, byte[] originalData) { byte[] patchData; if (patchLength < patchHeader.PatchLength) { patchData = UnpackRle(); } else { patchData = new byte[patchLength]; if (Read(patchData, 0, checked ((int)patchLength)) != patchLength) { throw new EndOfStreamException(); } } fixed(byte *patchDataPointer = patchData) { var bsdiffHeader = (PatchBsdiff40Header *)patchDataPointer; if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes((ulong *)patchDataPointer, sizeof(PatchBsdiff40Header) >> 3); } if (bsdiffHeader->Signature != 0x3034464649445342 /* 'BSDIFF40' */) { throw new InvalidDataException(ErrorMessages.GetString("Bsd0PatchHeaderInvalidSignature")); } var controlBlock = (uint *)(patchDataPointer + sizeof(PatchBsdiff40Header)); var differenceBlock = (byte *)controlBlock + bsdiffHeader->ControlBlockLength; var extraBlock = differenceBlock + bsdiffHeader->DifferenceBlockLength; if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes(controlBlock, bsdiffHeader->ControlBlockLength >> 2); } var patchBuffer = new byte[bsdiffHeader->PatchedFileSize]; fixed(byte *originalDataPointer = originalData) fixed(byte *patchBufferPointer = patchBuffer) { var sourcePointer = originalDataPointer; var destinationPointer = patchBufferPointer; int sourceCount = originalData.Length; int destinationCount = patchBuffer.Length; while (destinationCount != 0) { uint differenceLength = *controlBlock++; uint extraLength = *controlBlock++; uint sourceOffset = *controlBlock++; if (differenceLength > destinationCount) { throw new InvalidDataException(ErrorMessages.GetString("Bsd0PatchInvalidData")); } destinationCount = (int)(destinationCount - differenceLength); // Apply the difference patch (Patched Data = Original data + Difference data) for (; differenceLength-- != 0; destinationPointer++, sourcePointer++) { *destinationPointer = *differenceBlock++; if (sourceCount > 0) { *destinationPointer += *sourcePointer; } } if (extraLength > destinationCount) { throw new InvalidDataException(ErrorMessages.GetString("Bsd0PatchInvalidData")); } destinationCount = (int)(destinationCount - extraLength); // Apply the extra data patch (New data) for (; extraLength-- != 0;) { *destinationPointer++ = *extraBlock++; } sourcePointer += (sourceOffset & 0x80000000) != 0 ? unchecked ((int)(0x80000000 - sourceOffset)) : (int)sourceOffset; } } return(patchBuffer); } }
private unsafe byte[] ApplyPatch(PatchInfoHeader patchInfoHeader, Stream baseStream) { PatchHeader patchHeader; Read((byte *)&patchHeader, sizeof(PatchHeader)); if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes((uint *)&patchHeader, sizeof(PatchHeader) >> 2); } if (patchHeader.Signature != 0x48435450 /* 'PTCH' */) { throw new InvalidDataException(ErrorMessages.GetString("PatchHeaderInvalidSignature")); } if (patchHeader.PatchedFileSize != file.Size) { throw new InvalidDataException(ErrorMessages.GetString("PatchHeaderInvalidFileSize")); } if (baseStream.Length != patchHeader.OriginalFileSize) { throw new InvalidDataException(ErrorMessages.GetString("PatchHeaderInvalidBaseFileSize")); } // Once the initial tests are passed, we can load the whole patch in memory. // This will take a big amount of memory, but will avoid having to unpack the file twice… var originalData = new byte[baseStream.Length]; if (baseStream.Read(originalData, 0, originalData.Length) != originalData.Length) { throw new EndOfStreamException(); } var md5 = CommonMethods.SharedMD5; var originalHash = md5.ComputeHash(originalData); PatchMD5ChunkData md5ChunkData; bool hasMD5 = false; while (true) { long chunkPosition = Position; var chunkHeader = stackalloc uint[2]; if (Read((byte *)chunkHeader, 8) != 8) { throw new EndOfStreamException(); } if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes(chunkHeader, 2); } if (chunkHeader[0] == 0x5F35444D /* 'MD5_' */) { if (Read((byte *)&md5ChunkData, sizeof(PatchMD5ChunkData)) != sizeof(PatchMD5ChunkData)) { throw new EndOfStreamException(); } if (!CommonMethods.CompareData(originalHash, md5ChunkData.OrginialFileMD5)) { throw new InvalidDataException(ErrorMessages.GetString("PatchBaseFileMD5Failed")); } hasMD5 = true; } else if (chunkHeader[0] == 0x4D524658 /* 'XFRM' */) { // This may not be a real problem, however, let's not handle this case for now… (May fail because of the stupid bogus patches…) if (chunkPosition + chunkHeader[1] != Length) { throw new InvalidDataException(ErrorMessages.GetString("PatchXfrmChunkError")); } uint patchType; if (Read((byte *)&patchType, 4) != 4) { throw new EndOfStreamException(); } if (!BitConverter.IsLittleEndian) { patchType = CommonMethods.SwapBytes(patchType); } uint patchLength = chunkHeader[1] - 12; byte[] patchedData; if (patchType == 0x59504F43 /* 'COPY' */) { patchedData = ApplyCopyPatch(ref patchInfoHeader, ref patchHeader, patchLength, originalData); } if (patchType == 0x30445342 /* 'BSD0' */) { patchedData = ApplyBsd0Patch(ref patchInfoHeader, ref patchHeader, patchLength, originalData); } else { throw new NotSupportedException("Unsupported patch type: '" + CommonMethods.FourCCToString(chunkHeader[0]) + "'"); } if (hasMD5) { var patchedHash = md5.ComputeHash(patchedData); if (!CommonMethods.CompareData(patchedHash, md5ChunkData.PatchedFileMD5)) { throw new InvalidDataException("PatchFinalFileMD5Failed"); } } return(patchedData); } else { throw new InvalidDataException(string.Format(ErrorMessages.GetString("PatchUnknownChunk"), CommonMethods.FourCCToString(chunkHeader[0]))); } Seek(chunkPosition + chunkHeader[1], SeekOrigin.Begin); } }
private unsafe byte[] ApplyBsd0Patch(ref PatchInfoHeader patchInfoHeader, ref PatchHeader patchHeader, uint patchLength, byte[] originalData) { byte[] patchData; if (patchLength < patchHeader.PatchLength) { patchData = UnpackRle(patchLength); } else { patchData = new byte[patchLength]; if (Read(patchData, 0, checked ((int)patchLength)) != patchLength) { throw new EndOfStreamException(); } } fixed(byte *patchDataPointer = patchData) { var bsdiffHeader = (PatchBsdiff40Header *)patchDataPointer; if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes((ulong *)patchDataPointer, sizeof(PatchBsdiff40Header) / sizeof(ulong)); } if (bsdiffHeader->Signature != 0x3034464649445342 /* 'BSDIFF40' */) { throw new InvalidDataException(ErrorMessages.GetString("Bsd0PatchHeaderInvalidSignature")); } var controlBlock = (uint *)(patchDataPointer + sizeof(PatchBsdiff40Header)); var differenceBlock = (byte *)controlBlock + bsdiffHeader->ControlBlockLength; var extraBlock = differenceBlock + bsdiffHeader->DifferenceBlockLength; if (!BitConverter.IsLittleEndian) { CommonMethods.SwapBytes(controlBlock, bsdiffHeader->ControlBlockLength / sizeof(uint)); } var patchedBuffer = new byte[bsdiffHeader->PatchedFileSize]; uint o = 0; uint n = 0; try { while (n < patchedBuffer.Length) { uint differenceLength = *controlBlock++; uint extraLength = *controlBlock++; uint sourceOffset = *controlBlock++; // Apply the difference patch (Patched Data = Original data + Difference data) for (uint i = 0; i < differenceLength; i++, n++, o++) { patchedBuffer[n] = differenceBlock[i]; if (o < originalData.Length) { patchedBuffer[n] += originalData[o]; } } differenceBlock += differenceLength; // Apply the extra data patch (New data) for (int e = 0; e < extraLength; e++) { patchedBuffer[n++] = extraBlock[e]; } extraBlock += extraLength; unchecked { o += (sourceOffset & 0x80000000) != 0 ? (0x80000000 - sourceOffset) : sourceOffset; } } } catch (IndexOutOfRangeException ex) { throw new InvalidDataException(ErrorMessages.GetString("Bsd0PatchInvalidData"), ex); } return(patchedBuffer); } }