TH1/Unity/Assets/Plugins/ViSDK/ViSystem/ViBinaryPatch.cs
2025-08-20 02:24:48 +08:00

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;
}
}
}