Example #1
0
        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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
            }
        }
Example #4
0
        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);
            }
        }
Example #5
0
        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);
            }
        }
Example #6
0
        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);
            }
        }
Example #7
0
        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;
        }
Example #8
0
        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;
            }
        }
Example #9
0
        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;
        }