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