A small LZ4 block decoder used in the editor tools, including support for a chained 64KiB dictionary (history) as used by binary KV3 blobs. It parses LZ4 tokens, copies literals, and performs back-reference matches into the current output or the provided history buffer.
// Bounded LZ4 block decoding, including the chained 64K dictionary used by binary KV3 blobs.
#nullable enable
using System;
using System.IO;
namespace HumanoidRetargeter.EditorTools.Embedded.Compression;
internal static class Lz4
{
internal static int Decode(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> history = default)
{
int read = 0, written = 0;
while (read < input.Length)
{
var token = input[read++];
var literals = Length(input, ref read, token >> 4);
if (literals > input.Length - read || literals > output.Length - written) throw new InvalidDataException("Invalid LZ4 literal length.");
input.Slice(read, literals).CopyTo(output[written..]); read += literals; written += literals;
if (read == input.Length) break;
if (input.Length - read < 2) throw new InvalidDataException("Truncated LZ4 match.");
var distance = input[read] | input[read + 1] << 8; read += 2;
var length = checked(Length(input, ref read, token & 15) + 4);
if (distance == 0 || distance > written + history.Length || length > output.Length - written) throw new InvalidDataException("Invalid LZ4 match.");
for (var i = 0; i < length; i++)
{
var from = written - distance;
output[written++] = from < 0 ? history[history.Length + from] : output[from];
}
}
return written;
}
static int Length(ReadOnlySpan<byte> input, ref int read, int value)
{
if (value != 15) return value;
byte next;
do
{
if (read == input.Length) throw new InvalidDataException("Truncated LZ4 length.");
next = input[read++]; value = checked(value + next);
} while (next == 255);
return value;
}
}