150 lines
4.2 KiB
C#
150 lines
4.2 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Buffers.Binary;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using UnityEngine;
|
|
//
|
|
public class ViBinaryPatch
|
|
{
|
|
public static void Merge(Stream input, Func<Stream> patch, Stream output)
|
|
{
|
|
const int headerSize = 32;
|
|
long controlLength, diffLength, newSize;
|
|
using (var patchStream = patch())
|
|
{
|
|
if (!patchStream.CanRead)
|
|
{
|
|
Debug.LogError("Patch stream must be readable.");
|
|
return;
|
|
}
|
|
if (!patchStream.CanSeek)
|
|
{
|
|
Debug.LogError("Patch stream must be seekable.");
|
|
return;
|
|
}
|
|
//
|
|
Span<byte> header = stackalloc byte[headerSize];
|
|
_ReadExactly(patchStream, header);
|
|
//
|
|
long signature = _ReadInt64(header);
|
|
if (signature != 0x3034464649445342L)
|
|
{
|
|
Debug.LogError("Patch file is broken.");
|
|
return;
|
|
}
|
|
//
|
|
controlLength = _ReadInt64(header[8..]);
|
|
diffLength = _ReadInt64(header[16..]);
|
|
newSize = _ReadInt64(header[24..]);
|
|
if (controlLength < 0 || diffLength < 0 || newSize < 0)
|
|
{
|
|
Debug.LogError("Patch file is broken.");
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
const int bufferSize = 1048576;
|
|
byte[] newData = new byte[bufferSize];
|
|
byte[] oldData = new byte[bufferSize];
|
|
//
|
|
using var compressedControlStream = patch();
|
|
using var compressedDiffStream = patch();
|
|
using var compressedExtraStream = patch();
|
|
//
|
|
compressedControlStream.Seek(headerSize, SeekOrigin.Current);
|
|
compressedDiffStream.Seek(headerSize + controlLength, SeekOrigin.Current);
|
|
compressedExtraStream.Seek(headerSize + controlLength + diffLength, SeekOrigin.Current);
|
|
//
|
|
using var controlStream = new GZipStream(compressedControlStream, CompressionMode.Decompress);
|
|
using var diffStream = new GZipStream(compressedDiffStream, CompressionMode.Decompress);
|
|
using var extraStream = new GZipStream(compressedExtraStream, CompressionMode.Decompress);
|
|
Span<long> control = stackalloc long[3];
|
|
Span<byte> buffer = stackalloc byte[8];
|
|
//
|
|
var oldPosition = 0;
|
|
var newPosition = 0;
|
|
while (newPosition < newSize)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
_ReadExactly(controlStream, buffer);
|
|
control[i] = _ReadInt64(buffer);
|
|
}
|
|
//
|
|
if (newPosition + control[0] > newSize)
|
|
{
|
|
Debug.LogError("Patch file is broken.");
|
|
return;
|
|
}
|
|
//
|
|
input.Position = oldPosition;
|
|
int bytesToCopy = (int)control[0];
|
|
while (bytesToCopy > 0)
|
|
{
|
|
int actualBytesToCopy = Math.Min(bytesToCopy, bufferSize);
|
|
_ReadExactly(diffStream, newData, 0, actualBytesToCopy);
|
|
//
|
|
int availableInputBytes = Math.Min(actualBytesToCopy, (int)(input.Length - input.Position));
|
|
_ReadExactly(input, oldData, 0, availableInputBytes);
|
|
for (int index = 0; index < availableInputBytes; index++)
|
|
{
|
|
newData[index] += oldData[index];
|
|
}
|
|
output.Write(newData, 0, actualBytesToCopy);
|
|
//
|
|
newPosition += actualBytesToCopy;
|
|
oldPosition += actualBytesToCopy;
|
|
bytesToCopy -= actualBytesToCopy;
|
|
}
|
|
//
|
|
if (newPosition + control[1] > newSize)
|
|
{
|
|
Debug.LogError("Patch file is broken.");
|
|
return;
|
|
}
|
|
//
|
|
bytesToCopy = (int)control[1];
|
|
while (bytesToCopy > 0)
|
|
{
|
|
int actualBytesToCopy = Math.Min(bytesToCopy, bufferSize);
|
|
_ReadExactly(extraStream, newData, 0, actualBytesToCopy);
|
|
output.Write(newData, 0, actualBytesToCopy);
|
|
newPosition += actualBytesToCopy;
|
|
bytesToCopy -= actualBytesToCopy;
|
|
}
|
|
//
|
|
oldPosition = (int)(oldPosition + control[2]);
|
|
}
|
|
}
|
|
//
|
|
static long _ReadInt64(ReadOnlySpan<byte> buffer)
|
|
{
|
|
var value = BinaryPrimitives.ReadInt64LittleEndian(buffer);
|
|
var mask = value >> 63;
|
|
return (~mask & value) | (((value & unchecked((long)0x8000_0000_0000_0000)) - value) & mask);
|
|
}
|
|
//
|
|
static void _ReadExactly(Stream stream, Span<byte> buffer)
|
|
{
|
|
var bytes = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
|
_ReadExactly(stream, bytes, 0, buffer.Length);
|
|
bytes.AsSpan(0, buffer.Length).CopyTo(buffer);
|
|
ArrayPool<byte>.Shared.Return(bytes);
|
|
}
|
|
|
|
static void _ReadExactly(Stream stream, byte[] buffer, int offset, int length)
|
|
{
|
|
int bytesCopied = 0;
|
|
while (bytesCopied < length)
|
|
{
|
|
int bytesRead = stream.Read(buffer, offset + bytesCopied, length - bytesCopied);
|
|
if (bytesRead == 0)
|
|
{
|
|
throw new EndOfStreamException();
|
|
}
|
|
bytesCopied += bytesRead;
|
|
}
|
|
}
|
|
}
|