private static unsafe PatchInfoHeader ReadPatchInfoHeader(MpqArchive archive, long offset) { // Always get a buffer big enough, even if the extra bytes are not present… // As of now (09/2011), the header should always be 28 bytes long, but this may change in the future… var sharedBuffer = CommonMethods.GetSharedBuffer(sizeof(PatchInfoHeader)); // No buffer should ever be smaller than 28 bytes… right ? if (archive.ReadArchiveData(sharedBuffer, 0, offset, 28) != 28) { throw new EndOfStreamException(ErrorMessages.GetString("PatchInfoHeaderEndOfStream")); // It's weird if we could not read the whole 28 bytes… (At worse, we should have read trash data) } var patchInfoHeader = new PatchInfoHeader(); patchInfoHeader.HeaderLength = (uint)sharedBuffer[0] | (uint)sharedBuffer[1] << 8 | (uint)sharedBuffer[2] << 16 | (uint)sharedBuffer[3] << 24; patchInfoHeader.Flags = (uint)sharedBuffer[4] | (uint)sharedBuffer[5] << 8 | (uint)sharedBuffer[6] << 16 | (uint)sharedBuffer[7] << 24; patchInfoHeader.PatchLength = (uint)sharedBuffer[8] | (uint)sharedBuffer[9] << 8 | (uint)sharedBuffer[10] << 16 | (uint)sharedBuffer[11] << 24; // Let's assume the MD5 is not mandatory… if (patchInfoHeader.HeaderLength >= 28) { for (int i = 0; i < 16; i++) { patchInfoHeader.PatchMD5[i] = sharedBuffer[12 + i]; } } return(patchInfoHeader); }
private byte[] ApplyCopyPatch(ref PatchInfoHeader patchInfoHeader, ref PatchHeader patchHeader, uint patchLength, byte[] originalData) { if (patchLength != patchHeader.PatchedFileSize) { throw new InvalidDataException("CopyPatchInvalidSize"); } var patchedData = patchLength == originalData.Length ? originalData : new byte[patchLength]; if (Read(patchedData, 0, patchedData.Length) != patchedData.Length) { throw new EndOfStreamException(); } return(patchedData); }
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); } }
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 byte[] ApplyCopyPatch(ref PatchInfoHeader patchInfoHeader, ref PatchHeader patchHeader, uint patchLength, byte[] originalData) { if (patchLength != patchHeader.PatchedFileSize) throw new InvalidDataException("CopyPatchInvalidSize"); var patchedData = patchLength == originalData.Length ? originalData : new byte[patchLength]; if (Read(patchedData, 0, patchedData.Length) != patchedData.Length) throw new EndOfStreamException(); return patchedData; }
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; } }
private static unsafe PatchInfoHeader ReadPatchInfoHeader(MpqArchive archive, long offset) { // Always get a buffer big enough, even if the extra bytes are not present… // As of now (09/2011), the header should always be 28 bytes long, but this may change in the future… var sharedBuffer = CommonMethods.GetSharedBuffer(sizeof(PatchInfoHeader)); // No buffer should ever be smaller than 28 bytes… right ? if (archive.ReadArchiveData(sharedBuffer, 0, offset, 28) != 28) throw new EndOfStreamException(ErrorMessages.GetString("PatchInfoHeaderEndOfStream")); // It's weird if we could not read the whole 28 bytes… (At worse, we should have read trash data) var patchInfoHeader = new PatchInfoHeader(); patchInfoHeader.HeaderLength = (uint)sharedBuffer[0] | (uint)sharedBuffer[1] << 8 | (uint)sharedBuffer[2] << 16 | (uint)sharedBuffer[3] << 24; patchInfoHeader.Flags = (uint)sharedBuffer[4] | (uint)sharedBuffer[5] << 8 | (uint)sharedBuffer[6] << 16 | (uint)sharedBuffer[7] << 24; patchInfoHeader.PatchLength = (uint)sharedBuffer[8] | (uint)sharedBuffer[9] << 8 | (uint)sharedBuffer[10] << 16 | (uint)sharedBuffer[11] << 24; // Let's assume the MD5 is not mandatory… if (patchInfoHeader.HeaderLength >= 28) for (int i = 0; i < 16; i++) patchInfoHeader.PatchMD5[i] = sharedBuffer[12 + i]; return patchInfoHeader; }