diff --git a/src/VCDiff.Tests/ExternDiffTests.cs b/src/VCDiff.Tests/ExternDiffTests.cs index a56399f..5e2877c 100644 --- a/src/VCDiff.Tests/ExternDiffTests.cs +++ b/src/VCDiff.Tests/ExternDiffTests.cs @@ -20,6 +20,7 @@ public class ExternDiffTests [InlineData("sample.xdelta")] [InlineData("sample_nosmallstr.xdelta")] [InlineData("sample_appheader.xdelta")] + [InlineData("a-to-b-lzma-compression.xdelta")] public async Task ExternTest_ImplAsync(string patchfile) { @@ -53,6 +54,7 @@ public async Task ExternTest_ImplAsync(string patchfile) [InlineData("sample.xdelta")] [InlineData("sample_nosmallstr.xdelta")] [InlineData("sample_appheader.xdelta")] + [InlineData("a-to-b-lzma-compression.xdelta")] public void ExternTest_Impl(string patchfile) { diff --git a/src/VCDiff.Tests/patches/a-to-b-lzma-compression.xdelta b/src/VCDiff.Tests/patches/a-to-b-lzma-compression.xdelta new file mode 100644 index 0000000..3234e35 Binary files /dev/null and b/src/VCDiff.Tests/patches/a-to-b-lzma-compression.xdelta differ diff --git a/src/VCDiff/Compressors/ICompressor.cs b/src/VCDiff/Compressors/ICompressor.cs new file mode 100644 index 0000000..b455752 --- /dev/null +++ b/src/VCDiff/Compressors/ICompressor.cs @@ -0,0 +1,16 @@ +using VCDiff.Includes; +using VCDiff.Shared; + +namespace VCDiff.Compressors +{ + /// + /// A compression method for secondary compression, for use when is enabled in the header and the appropriate flag is enabled for the section in the window. + /// + /// + /// Implementations are stateful, and a single instance must be used for the entire file for a single operation (compression or decompression). + /// + internal interface ICompressor + { + PinnedArrayRental Decompress(WindowSectionType windowSectionType, PinnedArrayRental sectionData); + } +} diff --git a/src/VCDiff/Compressors/XzCompressor.cs b/src/VCDiff/Compressors/XzCompressor.cs new file mode 100644 index 0000000..c1f50da --- /dev/null +++ b/src/VCDiff/Compressors/XzCompressor.cs @@ -0,0 +1,82 @@ +using SharpCompress.Compressors.Xz; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using VCDiff.Shared; + +namespace VCDiff.Compressors +{ + internal class XzCompressor : ICompressor, IDisposable + { + public XzCompressor() + { + addRunCompressedBuffer = new(); + instructionsCompressedBuffer = new(); + addressesCompressedBuffer = new(); + + addRunDecompressor = new(addRunCompressedBuffer); + instructionsDecompressor = new(instructionsCompressedBuffer); + addressesDecompressor = new(addressesCompressedBuffer); + } + + private readonly MemoryStream addRunCompressedBuffer; + private readonly MemoryStream instructionsCompressedBuffer; + private readonly MemoryStream addressesCompressedBuffer; + private readonly XZStream addRunDecompressor; + private readonly XZStream instructionsDecompressor; + private readonly XZStream addressesDecompressor; + + public PinnedArrayRental Decompress(WindowSectionType windowSectionType, PinnedArrayRental sectionData) + { + if (sectionData.Data == null) + { + throw new ArgumentException("Cannot decompress null data"); + } + + MemoryStream memoryStream; + XZStream xzStream; + switch (windowSectionType) + { + case WindowSectionType.AddRunData: + memoryStream = addRunCompressedBuffer; + xzStream = addRunDecompressor; + break; + case WindowSectionType.InstructionsAndSizes: + memoryStream = instructionsCompressedBuffer; + xzStream = instructionsDecompressor; + break; + case WindowSectionType.AddressForCopy: + memoryStream = addressesCompressedBuffer; + xzStream = addressesDecompressor; + break; + default: + throw new ArgumentOutOfRangeException(nameof(windowSectionType)); + } + + var uncompressedLength = VarIntBE.ParseInt32(sectionData.AsSpan(), out int uncompressedLengthByteCount); + var compressedData = sectionData.AsSpan().Slice(uncompressedLengthByteCount); + + // Each section in a window uses the same compression stream throughout the file + // If this is not the first window, reuse the same stream from before, just using different data + memoryStream.SetLength(compressedData.Length); + memoryStream.Position = 0; + memoryStream.Write(compressedData); + memoryStream.Position = 0; + + var decompressedData = new PinnedArrayRental(uncompressedLength); + xzStream.ReadExactly(decompressedData.AsSpan()); + + return decompressedData; + } + public void Dispose() + { + addressesCompressedBuffer?.Dispose(); + instructionsCompressedBuffer?.Dispose(); + addressesCompressedBuffer?.Dispose(); + addRunDecompressor?.Dispose(); + instructionsDecompressor?.Dispose(); + addressesDecompressor?.Dispose(); + } + } +} diff --git a/src/VCDiff/Decoders/BodyDecoder.cs b/src/VCDiff/Decoders/BodyDecoder.cs index 98834aa..da00062 100644 --- a/src/VCDiff/Decoders/BodyDecoder.cs +++ b/src/VCDiff/Decoders/BodyDecoder.cs @@ -2,7 +2,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.IO; using VCDiff.Includes; using VCDiff.Shared; @@ -20,6 +19,7 @@ internal class BodyDecoderThe delta /// the out stream /// custom table if any. Default is null. - public BodyDecoder(WindowDecoder w, TSourceBuffer source, TDeltaBuffer delta, Stream decodedTarget, CustomCodeTableDecoder? customTable = null) + public BodyDecoder(WindowDecoder w, TSourceBuffer source, TDeltaBuffer delta, Stream decodedTarget, CustomCodeTableDecoder? customTable = null, bool disableChecksums = false) { if (customTable != null) { @@ -48,6 +48,7 @@ public BodyDecoder(WindowDecoder w, TSourceBuffer sour this.source = source; this.delta = delta; this.targetData = Pool.MemoryStreamManager.GetStream(nameof(BodyDecoder), (int) w.TargetWindowLength); + this.disableChecksums = disableChecksums; } private VCDiffResult DecodeInterleaveCore() @@ -254,7 +255,7 @@ private VCDiffResult DecodeCore() { uint adler = Checksum.ComputeGoogleAdler32(targetData.GetBuffer().AsSpan(0, (int)targetData.Length)); - if (adler != window.Checksum) + if (adler != window.Checksum && !disableChecksums) { result = VCDiffResult.ERROR; } @@ -263,7 +264,7 @@ private VCDiffResult DecodeCore() { uint adler = Checksum.ComputeXdelta3Adler32(targetData.GetBuffer().AsSpan(0, (int)targetData.Length)); - if (adler != window.Checksum) + if (adler != window.Checksum && !disableChecksums) { result = VCDiffResult.ERROR; } diff --git a/src/VCDiff/Decoders/SharedDecompressors.cs b/src/VCDiff/Decoders/SharedDecompressors.cs new file mode 100644 index 0000000..239cdc8 --- /dev/null +++ b/src/VCDiff/Decoders/SharedDecompressors.cs @@ -0,0 +1,16 @@ +using SharpCompress.Compressors.Xz; +using System.IO; + +namespace VCDiff.Decoders +{ + internal class SharedDecompressors + { + public XZStream? AddRunDecompressor; + public XZStream? InstructionsDecompressor; + public XZStream? AddressesDecompressor; + + public MemoryStream? AddRunCompressedBuffer; + public MemoryStream? InstructionsCompressedBuffer; + public MemoryStream? AddressesCompressedBuffer; + } +} diff --git a/src/VCDiff/Decoders/VcDecoderEx.cs b/src/VCDiff/Decoders/VcDecoderEx.cs index e7ca135..8f45f22 100644 --- a/src/VCDiff/Decoders/VcDecoderEx.cs +++ b/src/VCDiff/Decoders/VcDecoderEx.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Text; using System.Threading.Tasks; +using VCDiff.Compressors; using VCDiff.Includes; using VCDiff.Shared; @@ -13,7 +13,7 @@ namespace VCDiff.Decoders /// public class VcDecoder : VcDecoderEx, IDisposable { - private bool _ownsSources; + private readonly bool _ownsSources; private bool _disposed; /// @@ -23,13 +23,10 @@ public class VcDecoder : VcDecoderEx, IDispo /// The stream containing the VCDIFF delta. /// The stream to write the output in. /// The maximum target file size (and target window size) in bytes - public VcDecoder(Stream source, Stream delta, Stream outputStream, int maxTargetFileSize = WindowDecoderBase.DefaultMaxTargetFileSize) + /// Whether to disable checksums when applying the delta. This can be dangerous, but can be useful when the input file differs in ways that the delta does not reference. + public VcDecoder(Stream source, Stream delta, Stream outputStream, int maxTargetFileSize = WindowDecoderBase.DefaultMaxTargetFileSize, bool disableChecksums = false) + : base(new ByteStreamReader(source), new ByteStreamReader(delta), outputStream, maxTargetFileSize, disableChecksums) { - base.delta = new ByteStreamReader(delta); - base.source = new ByteStreamReader(source); - base.outputStream = outputStream; - base.maxTargetFileSize = maxTargetFileSize; - base.IsInitialized = false; _ownsSources = true; } @@ -60,6 +57,7 @@ public class VcDecoderEx : IDisposable where TSourc protected TDeltaBuffer delta; protected TSourceBuffer source; protected int maxTargetFileSize; + protected bool disableChecksums; private CustomCodeTableDecoder? customTable; protected static readonly byte[] MagicBytes = { 0xD6, 0xC3, 0xC4, 0x00, 0x00 }; @@ -68,13 +66,13 @@ public class VcDecoderEx : IDisposable where TSourc /// public bool IsSDCHFormat { get; private set; } + private byte SecondaryCompressorId { get; set; } + /// /// If the decoder has been initialized. /// protected bool IsInitialized { get; set; } - protected VcDecoderEx() { } - /// /// Creates a new VCDIFF decoder. /// @@ -82,12 +80,14 @@ protected VcDecoderEx() { } /// The stream containing the VCDIFF delta. /// The stream to write the output in. /// The maximum target file size (and target window size) in bytes - public VcDecoderEx(TSourceBuffer dict, TDeltaBuffer delta, Stream outputStream, int maxTargetFileSize = WindowDecoderBase.DefaultMaxTargetFileSize) + /// Whether to disable checksums when applying the delta. This can be dangerous, but can be useful when the input file differs in ways that the delta does not reference. + public VcDecoderEx(TSourceBuffer dict, TDeltaBuffer delta, Stream outputStream, int maxTargetFileSize = WindowDecoderBase.DefaultMaxTargetFileSize, bool disableChecksums = false) { this.delta = delta; this.source = dict; this.outputStream = outputStream; this.maxTargetFileSize = maxTargetFileSize; + this.disableChecksums = disableChecksums; this.IsInitialized = false; } @@ -139,10 +139,12 @@ private VCDiffResult Initialize() return VCDiffResult.ERROR; } - //compression not supported + // secondary compression if ((hdr & (int)VCDiffCodeFlags.VCDDECOMPRESS) != 0) { - return VCDiffResult.ERROR; + if (!delta.CanRead) return VCDiffResult.EOD; + + SecondaryCompressorId = delta.ReadByte(); } //custom code table! @@ -188,48 +190,62 @@ public VCDiffResult Decode(out long bytesWritten) if (!Decode_Init(out bytesWritten, out var result, out var decodeAsync)) return result; - while (delta.CanRead) + var secondaryCompressor = SecondaryCompressorId != 0 ? CreateCompressor(SecondaryCompressorId): null; + try { - //delta is streamed in order aka not random access - using var w = new WindowDecoder(source.Length, delta, maxTargetFileSize); + while (delta.CanRead) + { + //delta is streamed in order aka not random access + using var w = new WindowDecoder(source.Length, delta, secondaryCompressor, maxTargetFileSize); - if (!w.Decode(this.IsSDCHFormat)) - return (VCDiffResult)w.Result; + if (!w.Decode(this.IsSDCHFormat, this.SecondaryCompressorId)) + { + return (VCDiffResult)w.Result; + } - using var body = new BodyDecoder(w, source, delta, outputStream); - if (this.IsSDCHFormat && w.AddRunLength == 0 && w.AddressesForCopyLength == 0 && w.InstructionAndSizesLength > 0) - { - //interleaved - //decodedinterleave actually has an internal loop for waiting and streaming the incoming rest of the interleaved window - result = body.DecodeInterleave(); + using var body = new BodyDecoder(w, source, delta, outputStream, disableChecksums: disableChecksums); + if (this.IsSDCHFormat && w.AddRunLength == 0 && w.AddressesForCopyLength == 0 && w.InstructionAndSizesLength > 0) + { + //interleaved + //decodedinterleave actually has an internal loop for waiting and streaming the incoming rest of the interleaved window + result = body.DecodeInterleave(); - if (result != VCDiffResult.SUCCESS && result != VCDiffResult.EOD) - return result; + if (result != VCDiffResult.SUCCESS && result != VCDiffResult.EOD) + return result; - bytesWritten += body.TotalBytesDecoded; - } - //technically add could be 0 if it is all copy instructions - //so do an or check on those two - else if (!this.IsSDCHFormat || - (this.IsSDCHFormat && (w.AddRunLength > 0 || w.AddressesForCopyLength > 0) && - w.InstructionAndSizesLength > 0)) - { - //not interleaved - //expects the full window to be available - //in the stream - result = body.Decode(); - if (result != VCDiffResult.SUCCESS) - return result; - - bytesWritten += body.TotalBytesDecoded; + bytesWritten += body.TotalBytesDecoded; + } + //technically add could be 0 if it is all copy instructions + //so do an or check on those two + else if (!this.IsSDCHFormat || + (this.IsSDCHFormat && (w.AddRunLength > 0 || w.AddressesForCopyLength > 0) && + w.InstructionAndSizesLength > 0)) + { + //not interleaved + //expects the full window to be available + //in the stream + result = body.Decode(); + if (result != VCDiffResult.SUCCESS) + return result; + + bytesWritten += body.TotalBytesDecoded; + } + else + { + //invalid file + return VCDiffResult.ERROR; + } } - else + } + finally + { + if (secondaryCompressor is IDisposable secondaryCompressorDisposable) { - //invalid file - return VCDiffResult.ERROR; + secondaryCompressorDisposable.Dispose(); } } + return result; } @@ -243,49 +259,61 @@ public VCDiffResult Decode(out long bytesWritten) { if (!Decode_Init(out var bytesWritten, out var result, out var decodeAsync)) return decodeAsync; - while (delta.CanRead) - { - //delta is streamed in order aka not random access - using var w = new WindowDecoder(source.Length, delta, maxTargetFileSize); - if (w.Decode(this.IsSDCHFormat)) + var secondaryCompressor = SecondaryCompressorId != 0 ? CreateCompressor(SecondaryCompressorId) : null; + try + { + while (delta.CanRead) { - using var body = new BodyDecoder(w, source, delta, outputStream); - if (this.IsSDCHFormat && w.AddRunLength == 0 && w.AddressesForCopyLength == 0 && w.InstructionAndSizesLength > 0) - { - //interleaved - //decodedinterleave actually has an internal loop for waiting and streaming the incoming rest of the interleaved window - result = await body.DecodeInterleaveAsync(); - - if (result != VCDiffResult.SUCCESS && result != VCDiffResult.EOD) - return (result, bytesWritten); + //delta is streamed in order aka not random access + using var w = new WindowDecoder(source.Length, delta, secondaryCompressor, maxTargetFileSize); - bytesWritten += body.TotalBytesDecoded; - } - //technically add could be 0 if it is all copy instructions - //so do an or check on those two - else if (!this.IsSDCHFormat || (this.IsSDCHFormat && (w.AddRunLength > 0 || w.AddressesForCopyLength > 0) && - w.InstructionAndSizesLength > 0)) + if (w.Decode(this.IsSDCHFormat, this.SecondaryCompressorId)) { - //not interleaved - //expects the full window to be available - //in the stream - result = await body.DecodeAsync(); - - if (result != VCDiffResult.SUCCESS) - return (result, bytesWritten); - - bytesWritten += body.TotalBytesDecoded; + using var body = new BodyDecoder(w, source, delta, outputStream, disableChecksums: disableChecksums); + if (this.IsSDCHFormat && w.AddRunLength == 0 && w.AddressesForCopyLength == 0 && w.InstructionAndSizesLength > 0) + { + //interleaved + //decodedinterleave actually has an internal loop for waiting and streaming the incoming rest of the interleaved window + result = await body.DecodeInterleaveAsync(); + + if (result != VCDiffResult.SUCCESS && result != VCDiffResult.EOD) + return (result, bytesWritten); + + bytesWritten += body.TotalBytesDecoded; + } + //technically add could be 0 if it is all copy instructions + //so do an or check on those two + else if (!this.IsSDCHFormat || (this.IsSDCHFormat && (w.AddRunLength > 0 || w.AddressesForCopyLength > 0) && + w.InstructionAndSizesLength > 0)) + { + //not interleaved + //expects the full window to be available + //in the stream + result = await body.DecodeAsync(); + + if (result != VCDiffResult.SUCCESS) + return (result, bytesWritten); + + bytesWritten += body.TotalBytesDecoded; + } + else + { + //invalid file + return (VCDiffResult.ERROR, bytesWritten); + } } else { - //invalid file - return (VCDiffResult.ERROR, bytesWritten); + return ((VCDiffResult)w.Result, bytesWritten); } } - else + } + finally + { + if (secondaryCompressor is IDisposable secondaryCompressorDisposable) { - return ((VCDiffResult)w.Result, bytesWritten); + secondaryCompressorDisposable.Dispose(); } } @@ -317,6 +345,18 @@ private bool Decode_Init(out long bytesWritten, out VCDiffResult result, out (VC return true; } + private ICompressor? CreateCompressor(byte secondaryCompressorId) + { + return secondaryCompressorId switch + { + 0 => null, + // xdelta defines 1 to be "DJW static huffman" + 2 => new XzCompressor(), + // xdelta defines 16 to be "FGK adaptive huffman" but says it's non-standard + _ => throw new NotSupportedException($"Secondary compression id '{secondaryCompressorId}' is not supported.") + }; + } + /// /// Disposes the decoder /// diff --git a/src/VCDiff/Decoders/WindowDecoder.cs b/src/VCDiff/Decoders/WindowDecoder.cs index 8d71f8c..c367aec 100644 --- a/src/VCDiff/Decoders/WindowDecoder.cs +++ b/src/VCDiff/Decoders/WindowDecoder.cs @@ -1,5 +1,8 @@ -using System; +using SharpCompress.Compressors.Xz; +using System; using System.Diagnostics; +using System.IO; +using VCDiff.Compressors; using VCDiff.Includes; using VCDiff.Shared; @@ -31,7 +34,11 @@ internal class WindowDecoder : WindowDecoderBase, IDisposable where private long addRunLength; private long instructionAndSizesLength; private long addressForCopyLength; + private bool addRunCompressed; + private bool instructionsAndSizesCompressed; + private bool addressForCopyCompressed; private uint checksum; + private readonly ICompressor? secondaryCompressor; public PinnedArrayRental AddRunData; @@ -45,6 +52,12 @@ internal class WindowDecoder : WindowDecoderBase, IDisposable where public long AddressesForCopyLength => addressForCopyLength; + public bool AddRunCompressed => addRunCompressed; + + public bool InstructionAndSizesCompressed => instructionsAndSizesCompressed; + + public bool AddressesForCopyCompressed => addressForCopyCompressed; + public byte WinIndicator => winIndicator; public long SourceSegmentOffset => sourceSegmentOffset; @@ -65,10 +78,12 @@ internal class WindowDecoder : WindowDecoderBase, IDisposable where /// the dictionary size /// the buffer containing the incoming data /// The maximum target window size in bytes - public WindowDecoder(long dictionarySize, TByteBuffer buffer, int maxWindowSize = DefaultMaxTargetFileSize) + /// The secondary compressor that can decompress window sections, if applicable. + public WindowDecoder(long dictionarySize, TByteBuffer buffer, ICompressor? secondaryCompressor, int maxWindowSize = DefaultMaxTargetFileSize) { this.dictionarySize = dictionarySize; this.buffer = buffer; + this.secondaryCompressor = secondaryCompressor; chunk = new ParseableChunk(buffer.Position, buffer.Length); if (maxWindowSize < 0) @@ -87,8 +102,9 @@ public WindowDecoder(long dictionarySize, TByteBuffer buffer, int maxWindowSize /// Decodes the window header. /// /// If the delta uses SDCH extensions. + /// ID of the secondary compressor. /// - public bool Decode(bool isSdch) + public bool Decode(bool isSdch, byte secondaryCompressorId) { if (!ParseWindowIndicatorAndSegment(dictionarySize, 0, false, out winIndicator, out sourceSegmentLength, out sourceSegmentOffset)) { @@ -128,18 +144,45 @@ public bool Decode(bool isSdch) AddRunData = new PinnedArrayRental((int)addRunLength); Debug.Assert(addRunLength <= int.MaxValue); buffer.ReadBytesToSpan(AddRunData.AsSpan()); + if (AddRunCompressed && secondaryCompressorId != 0) + { + if (secondaryCompressor == null) + { + throw new InvalidOperationException("AddRunData is compressed but no compressor was provided to the WindowDecoder"); + } + + AddRunData = secondaryCompressor.Decompress(WindowSectionType.AddRunData, AddRunData); + } } if (buffer.CanRead) { InstructionsAndSizesData = new PinnedArrayRental((int)instructionAndSizesLength); Debug.Assert(instructionAndSizesLength <= int.MaxValue); buffer.ReadBytesToSpan(InstructionsAndSizesData.AsSpan()); + if (instructionsAndSizesCompressed && secondaryCompressorId != 0) + { + if (secondaryCompressor == null) + { + throw new InvalidOperationException("AddRunData is compressed but no compressor was provided to the WindowDecoder"); + } + + InstructionsAndSizesData = secondaryCompressor.Decompress(WindowSectionType.InstructionsAndSizes, InstructionsAndSizesData); + } } if (buffer.CanRead) { AddressesForCopyData = new PinnedArrayRental((int)addressForCopyLength); Debug.Assert(addressForCopyLength <= int.MaxValue); buffer.ReadBytesToSpan(AddressesForCopyData.AsSpan()); + if (addressForCopyCompressed && secondaryCompressorId != 0) + { + if (secondaryCompressor == null) + { + throw new InvalidOperationException("AddRunData is compressed but no compressor was provided to the WindowDecoder"); + } + + AddressesForCopyData = secondaryCompressor.Decompress(WindowSectionType.AddressForCopy, AddressesForCopyData); + } } return true; @@ -351,11 +394,11 @@ private bool ParseDeltaIndicator() returnCode = (int)VCDiffResult.ERROR; return false; } - if ((deltaIndicator & ((int)VCDiffCompressFlags.VCDDATACOMP | (int)VCDiffCompressFlags.VCDINSTCOMP | (int)VCDiffCompressFlags.VCDADDRCOMP)) > 0) - { - returnCode = (int)VCDiffResult.ERROR; - return false; - } + + addRunCompressed = (deltaIndicator & (int)VCDiffCompressFlags.VCDDATACOMP) != 0; + instructionsAndSizesCompressed = (deltaIndicator & (int)VCDiffCompressFlags.VCDINSTCOMP) != 0; + addressForCopyCompressed = (deltaIndicator & (int)VCDiffCompressFlags.VCDADDRCOMP) != 0; + return true; } diff --git a/src/VCDiff/Includes/Include.cs b/src/VCDiff/Includes/Include.cs index e163662..c59b569 100644 --- a/src/VCDiff/Includes/Include.cs +++ b/src/VCDiff/Includes/Include.cs @@ -117,8 +117,6 @@ internal enum VCDiffWindowFlags // compressor. The bit positions 0 (VCD_DATACOMP), 1 // (VCD_INSTCOMP), and 2 (VCD_ADDRCOMP) respectively indicate, if // non-zero, that the corresponding parts are compressed." - // [Secondary compressors are not supported, so open-vcdiff decoding will fail - // if these bits are not all zero.] internal enum VCDiffCompressFlags { VCDDATACOMP = 0x01, diff --git a/src/VCDiff/Shared/ByteBuffer.cs b/src/VCDiff/Shared/ByteBuffer.cs index 2722470..8381a50 100644 --- a/src/VCDiff/Shared/ByteBuffer.cs +++ b/src/VCDiff/Shared/ByteBuffer.cs @@ -137,6 +137,7 @@ public Span ReadBytesToSpan(Span data) { var result = PeekBytes(data.Length); result.CopyTo(data); + offset += result.Length; return result.Slice(0, result.Length); } diff --git a/src/VCDiff/Shared/VarIntBE.cs b/src/VCDiff/Shared/VarIntBE.cs index da86c49..3f59fa4 100644 --- a/src/VCDiff/Shared/VarIntBE.cs +++ b/src/VCDiff/Shared/VarIntBE.cs @@ -38,6 +38,36 @@ public static int ParseInt32(TByteBuffer sin) where TByteBuffer : I return (int)VCDiffResult.EOD; } + public static int ParseInt32(Span sin, out int bytesConsumed) + { + bytesConsumed = 0; + int result = 0; + int index = 0; + + while (index < sin.Length) + { + byte currentByte = sin[index]; + result += currentByte & 0x7f; + + if ((currentByte & 0x80) == 0) + { + bytesConsumed = index + 1; + return result; + } + + if (result > (int32MaxValue >> 7)) + { + bytesConsumed = index + 1; + return (int)VCDiffResult.ERROR; + } + + result = result << 7; + index++; + } + + return (int)VCDiffResult.EOD; + } + public static long ParseInt64(TByteBuffer sin) where TByteBuffer : IByteBuffer { long result = 0; diff --git a/src/VCDiff/Shared/WindowSectionType.cs b/src/VCDiff/Shared/WindowSectionType.cs new file mode 100644 index 0000000..d5d175c --- /dev/null +++ b/src/VCDiff/Shared/WindowSectionType.cs @@ -0,0 +1,10 @@ +namespace VCDiff.Shared +{ + public enum WindowSectionType + { + Unspecified = 0, + AddRunData, + InstructionsAndSizes, + AddressForCopy + } +} diff --git a/src/VCDiff/VCDiff.csproj b/src/VCDiff/VCDiff.csproj index 4b7a2b6..430767a 100644 --- a/src/VCDiff/VCDiff.csproj +++ b/src/VCDiff/VCDiff.csproj @@ -23,8 +23,9 @@ - - + + + all runtime; build; native; contentfiles; analyzers