Skip to content

Instantly share code, notes, and snippets.

@Foorack
Created June 20, 2025 09:43
Show Gist options
  • Save Foorack/f06142c9c01bb3f499bb2e57346e6400 to your computer and use it in GitHub Desktop.
Save Foorack/f06142c9c01bb3f499bb2e57346e6400 to your computer and use it in GitHub Desktop.
sdefl to C# conversion with Copilot

Small Deflate (C# Version)

This is a C# port of the SDefl library - a small, bare bone lossless compression library that implements the Deflate (RFC 1951) compressed data format specification standard.

The original C library was created by Micha Mettke and this C# port maintains the same functionality without external dependencies.

Features

  • Pure C# implementation with no external dependencies
  • No use of System.IO.Compression
  • Small and concise implementation
  • 8 configurable compression levels
  • MIT license or public domain (same as the original)

Usage

Compression

// Create a deflater instance
SDefl deflater = new SDefl();

// Calculate maximum compressed size
int maxCompressedSize = SDefl.Bound(inputData.Length);
byte[] compressedData = new byte[maxCompressedSize];

// Compress the data
int compressedSize = deflater.Deflate(compressedData, inputData, inputData.Length, SDefl.LVL_DEF);

Decompression

// Create an inflater instance
SInfl inflater = new SInfl();

// Allocate output buffer (must be large enough for decompressed data)
byte[] decompressedData = new byte[expectedDecompressedSize];

// Decompress the data
int decompressedSize = inflater.Inflate(decompressedData, compressedData, compressedSize);

Building

The project requires .NET 6.0 or higher. To build, run:

dotnet build

To run the example program:

dotnet run

License

This software is available under 2 licenses -- choose whichever you prefer.

ALTERNATIVE A - MIT License
Copyright (c) 2020 Micha Mettke (Original C Version)
Copyright (c) 2025 (C# Port)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.

In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Text;
using System.Diagnostics;
namespace SmallDeflate
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Small Deflate C# Example");
Console.WriteLine("-----------------------");
// Example text to compress
string testString = "This is a test string that will be compressed and then decompressed to verify that everything is working correctly. " +
"The original C code was written as a small, portable implementation of the Deflate algorithm. " +
"This text will be repeated to provide a better compression example. " +
"This is a test string that will be compressed and then decompressed to verify that everything is working correctly. " +
"The original C code was written as a small, portable implementation of the Deflate algorithm. " +
"This text will be repeated to provide a better compression example.";
byte[] inputData = Encoding.UTF8.GetBytes(testString);
Console.WriteLine($"Original size: {inputData.Length} bytes");
// Compression
SDefl deflater = new SDefl();
int maxCompressedSize = SDefl.Bound(inputData.Length);
byte[] compressedData = new byte[maxCompressedSize];
Stopwatch sw = Stopwatch.StartNew();
int compressedSize = deflater.Deflate(compressedData, inputData, inputData.Length, SDefl.LVL_DEF);
sw.Stop();
Console.WriteLine($"Compressed size: {compressedSize} bytes");
Console.WriteLine($"Compression ratio: {(float)compressedSize / inputData.Length:F2}");
Console.WriteLine($"Compression time: {sw.ElapsedMilliseconds} ms");
// Decompression
SInfl inflater = new SInfl();
byte[] decompressedData = new byte[inputData.Length];
sw.Restart();
int decompressedSize = inflater.Inflate(decompressedData, compressedData, compressedSize);
sw.Stop();
Console.WriteLine($"Decompressed size: {decompressedSize} bytes");
Console.WriteLine($"Decompression time: {sw.ElapsedMilliseconds} ms");
// Verify result
string resultString = Encoding.UTF8.GetString(decompressedData, 0, decompressedSize);
bool isEqual = testString.Equals(resultString);
Console.WriteLine($"Decompressed data matches original: {isEqual}");
}
}
}
using System;
namespace SmallDeflate
{
/// <summary>
/// SDefl is a small bare bone lossless compression library that implements the Deflate (RFC 1951) standard.
/// </summary>
public class SDefl
{
// Constants that match the C version
private const int SDEFL_MAX_OFF = 1 << 15;
private const int SDEFL_WIN_SIZ = SDEFL_MAX_OFF;
private const int SDEFL_WIN_MSK = SDEFL_WIN_SIZ - 1;
private const int SDEFL_MIN_MATCH = 4;
private const int SDEFL_MAX_MATCH = 258;
private const int SDEFL_HASH_BITS = 19;
private const int SDEFL_HASH_SIZ = 1 << SDEFL_HASH_BITS;
private const int SDEFL_HASH_MSK = SDEFL_HASH_SIZ - 1;
private const int SDEFL_NIL = -1;
/// <summary>
/// Minimum compression level (0)
/// </summary>
public const int LVL_MIN = 0;
/// <summary>
/// Default compression level (5)
/// </summary>
public const int LVL_DEF = 5;
/// <summary>
/// Maximum compression level (8)
/// </summary>
public const int LVL_MAX = 8;
private int bits;
private int cnt;
private readonly int[] tbl;
private readonly int[] prv;
// Lookup table for bit reflection
private static readonly byte[] Mirror = {
#region Mirror Table
0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240,
8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241,
9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255
#endregion
};
/// <summary>
/// Initializes a new instance of the SDefl compression class.
/// </summary>
public SDefl()
{
tbl = new int[SDEFL_HASH_SIZ];
prv = new int[SDEFL_WIN_SIZ];
}
private static int NextPowerOfTwo(int n)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return ++n;
}
private static int Log2Integer(int n)
{
if (n == 0) return -1;
int log = 0;
if ((n & 0xFFFF0000) != 0) { log += 16; n >>= 16; }
if ((n & 0xFF00) != 0) { log += 8; n >>= 8; }
if ((n & 0xF0) != 0) { log += 4; n >>= 4; }
if ((n & 0xC) != 0) { log += 2; n >>= 2; }
if ((n & 0x2) != 0) { log += 1; }
return log;
}
private static uint ULoad32(byte[] data, int offset)
{
return BitConverter.IsLittleEndian
? BitConverter.ToUInt32(data, offset)
: (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24));
}
private static uint Hash32(byte[] data, int offset)
{
uint n = ULoad32(data, offset);
return (n * 0x9E377989) >> (32 - SDEFL_HASH_BITS);
}
private int Put(byte[] dst, ref int dstIndex, int code, int bitCount)
{
bits |= (code << cnt);
cnt += bitCount;
while (cnt >= 8)
{
dst[dstIndex++] = (byte)(bits & 0xFF);
bits >>= 8;
cnt -= 8;
}
return dstIndex;
}
private int EncodeLiteral(byte[] dst, ref int dstIndex, int c)
{
if (c <= 143)
{
dstIndex = Put(dst, ref dstIndex, Mirror[0x30 + c], 8);
}
else
{
dstIndex = Put(dst, ref dstIndex, 1 + 2 * Mirror[0x90 - 144 + c], 9);
}
return dstIndex;
}
private int EncodeMatch(byte[] dst, ref int dstIndex, int dist, int len)
{
// Length encoding tables
int[] lxmin = { 0, 11, 19, 35, 67, 131 };
int[] dxmax = { 0, 6, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576 };
int[] lmin = { 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227 };
int[] dmin = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257,
385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 };
// Length encoding
int lc = len;
int lx = Log2Integer(len - 3) - 2;
if ((lx = (lx < 0) ? 0 : lx) == 0)
{
lc += 254;
}
else if (len >= 258)
{
lx = 0;
lc = 285;
}
else
{
lc = ((lx - 1) << 2) + 265 + ((len - lxmin[lx]) >> lx);
}
if (lc <= 279)
{
dstIndex = Put(dst, ref dstIndex, Mirror[(lc - 256) << 1], 7);
}
else
{
dstIndex = Put(dst, ref dstIndex, Mirror[0xc0 - 280 + lc], 8);
}
if (lx > 0)
{
dstIndex = Put(dst, ref dstIndex, len - lmin[lc - 265], lx);
}
// Distance encoding
int dc = dist - 1;
int dx = Log2Integer(NextPowerOfTwo(dist) >> 2);
if ((dx = (dx < 0) ? 0 : dx) > 0)
{
dc = ((dx + 1) << 1) + (dist > dxmax[dx] ? 1 : 0);
}
dstIndex = Put(dst, ref dstIndex, Mirror[dc << 3], 5);
if (dx > 0)
{
dstIndex = Put(dst, ref dstIndex, dist - dmin[dc], dx);
}
return dstIndex;
}
/// <summary>
/// Calculates the maximum size of the compressed data based on the input length.
/// </summary>
/// <param name="inLen">Length of the input data</param>
/// <returns>Maximum size of the compressed output</returns>
public static int Bound(int inLen)
{
int a = 128 + (inLen * 110) / 100;
int b = 128 + inLen + ((inLen / (31 * 1024)) + 1) * 5;
return Math.Max(a, b);
}
/// <summary>
/// Compresses the input data using the Deflate algorithm.
/// </summary>
/// <param name="output">Output buffer to store compressed data</param>
/// <param name="input">Input data to compress</param>
/// <param name="inputLength">Length of the input data</param>
/// <param name="compressionLevel">Compression level (0-8)</param>
/// <returns>Size of the compressed data</returns>
public int Deflate(byte[] output, byte[] input, int inputLength, int compressionLevel)
{
int p = 0;
int maxChain = (compressionLevel < 8) ? (1 << (compressionLevel + 1)) : (1 << 13);
int dstIndex = 0;
bits = 0;
cnt = 0;
// Initialize hash table
for (p = 0; p < SDEFL_HASH_SIZ; ++p)
{
tbl[p] = SDEFL_NIL;
}
p = 0;
// Block header (static Huffman)
dstIndex = Put(output, ref dstIndex, 0x01, 1); // Block type: last block
dstIndex = Put(output, ref dstIndex, 0x01, 2); // Block type: static Huffman
while (p < inputLength)
{
int run;
int bestLen = 0;
int dist = 0;
int maxMatch = Math.Min(inputLength - p, SDEFL_MAX_MATCH);
if (maxMatch > SDEFL_MIN_MATCH)
{
int limit = (p - SDEFL_WIN_SIZ < SDEFL_NIL) ? SDEFL_NIL : (p - SDEFL_WIN_SIZ);
int chainLen = maxChain;
int i = (int)tbl[Hash32(input, p)];
while (i > limit)
{
if (input[i + bestLen] == input[p + bestLen] &&
ULoad32(input, i) == ULoad32(input, p))
{
int n = SDEFL_MIN_MATCH;
while (n < maxMatch && input[i + n] == input[p + n])
{
n++;
}
if (n > bestLen)
{
bestLen = n;
dist = p - i;
if (n == maxMatch)
{
break;
}
}
}
if (--chainLen == 0)
{
break;
}
i = prv[i & SDEFL_WIN_MSK];
}
}
// Look ahead to see if we can get a better match by skipping one byte
if (compressionLevel >= 5 && bestLen >= SDEFL_MIN_MATCH && bestLen < maxMatch)
{
int x = p + 1;
int targetLen = bestLen + 1;
int limit = (x - SDEFL_WIN_SIZ < SDEFL_NIL) ? SDEFL_NIL : (x - SDEFL_WIN_SIZ);
int chainLen = maxChain;
int i = (int)tbl[Hash32(input, x)];
while (i > limit)
{
if (input[i + bestLen] == input[x + bestLen] &&
ULoad32(input, i) == ULoad32(input, x))
{
int n = SDEFL_MIN_MATCH;
while (n < targetLen && input[i + n] == input[x + n])
{
n++;
}
if (n == targetLen)
{
bestLen = 0;
break;
}
}
if (--chainLen == 0)
{
break;
}
i = prv[i & SDEFL_WIN_MSK];
}
}
if (bestLen >= SDEFL_MIN_MATCH)
{
dstIndex = EncodeMatch(output, ref dstIndex, dist, bestLen);
run = bestLen;
}
else
{
dstIndex = EncodeLiteral(output, ref dstIndex, input[p]);
run = 1;
}
// Update hash table
while (run-- > 0)
{
uint h = Hash32(input, p);
prv[p & SDEFL_WIN_MSK] = tbl[h];
tbl[h] = p++;
}
}
// Flush remaining bits (partial flush)
dstIndex = Put(output, ref dstIndex, 0, 7);
dstIndex = Put(output, ref dstIndex, 2, 10);
dstIndex = Put(output, ref dstIndex, 2, 3);
return dstIndex;
}
}
}
using System;
namespace SmallDeflate
{
/// <summary>
/// SInfl is a small bare bone lossless decompression library that implements the Deflate (RFC 1951) standard.
/// </summary>
public class SInfl
{
// State for bit operations
private int bits;
private int bitCount;
// Huffman trees
private readonly uint[] literalTree = new uint[288];
private readonly uint[] distanceTree = new uint[32];
private readonly uint[] codeLengthTree = new uint[19];
private int tableLiteralSize;
private int tableDistanceSize;
private int tableCodeLengthSize;
// Lookup table for bit reflection
private static readonly byte[] Mirror = {
#region Mirror Table
0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240,
8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241,
9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255
#endregion
};
/// <summary>
/// Initializes a new instance of the SInfl decompression class.
/// </summary>
public SInfl()
{
}
// Extract bits from the input stream
private int GetBits(ref int inputIndex, byte[] input, int inputEnd, int n)
{
int value = bits & ((1 << n) - 1);
bits >>= n;
bitCount -= n;
if (bitCount < 0)
{
bitCount = 0;
}
while (bitCount < 16 && inputIndex < inputEnd)
{
bits |= input[inputIndex++] << bitCount;
bitCount += 8;
}
return value;
}
// Build a Huffman tree
private int BuildHuffmanTree(uint[] tree, byte[] lengths, int symbolCount)
{
int[] counts = new int[16];
int[] firstCodes = new int[16];
int[] codes = new int[16];
// Count occurrences of each code length
for (int i = 0; i < symbolCount; i++)
{
counts[lengths[i]]++;
}
counts[0] = firstCodes[0] = codes[0] = 0;
// Calculate first code for each length
for (int i = 1; i <= 15; i++)
{
codes[i] = (codes[i - 1] + counts[i - 1]) << 1;
firstCodes[i] = firstCodes[i - 1] + counts[i - 1];
}
// Assign codes to symbols
for (int i = 0; i < symbolCount; i++)
{
int len = lengths[i];
if (len != 0)
{
int code = codes[len]++;
int slot = firstCodes[len]++;
tree[slot] = (uint)((code << (32 - len)) | (i << 4) | len);
}
}
return firstCodes[15];
}
// Reverse 16 bits
private static uint Reverse16(int n)
{
return (uint)((Mirror[n & 0xff] << 8) | Mirror[(n >> 8) & 0xff]);
}
// Decode a symbol using a Huffman tree
private int DecodeSymbol(ref int inputIndex, byte[] input, int inputEnd, uint[] tree, int max)
{
// Binary search to find the code
uint search = (Reverse16(bits) << 16) | 0xffff;
uint key;
int lo = 0, hi = max;
while (lo < hi)
{
int mid = (lo + hi) / 2;
if (search < tree[mid])
{
hi = mid;
}
else
{
lo = mid + 1;
}
}
// Pull out and check the key
key = tree[lo - 1];
GetBits(ref inputIndex, input, inputEnd, (int)(key & 0x0f));
return (int)((key >> 4) & 0x0fff);
}
/// <summary>
/// Decompresses Deflate-compressed data.
/// </summary>
/// <param name="output">Output buffer for decompressed data</param>
/// <param name="input">Input buffer containing compressed data</param>
/// <param name="inputSize">Size of the input data</param>
/// <returns>Size of the decompressed data</returns>
public int Inflate(byte[] output, byte[] input, int inputSize)
{
// Order of code length codes
int[] order = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
// Base values and extra bits for distances
int[] distanceBases = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577
};
byte[] distanceExtraBits = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,
10, 10, 11, 11, 12, 12, 13, 13, 0, 0
};
// Base values and extra bits for lengths
int[] lengthBases = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35,
43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
};
byte[] lengthExtraBits = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4,
4, 4, 4, 5, 5, 5, 5, 0, 0, 0
};
int inputEnd = inputSize;
int inputIndex = 0;
int outputStart = 0;
int outputIndex = 0;
bool lastBlock = false;
// State machine states
enum InflateState { Header, Stored, Fixed, Dynamic, Block }
InflateState state = InflateState.Header;
bits = 0;
bitCount = 0;
// Buffer input
GetBits(ref inputIndex, input, inputEnd, 0);
while (inputIndex < inputEnd || bitCount > 0)
{
switch (state)
{
case InflateState.Header:
// Block header
lastBlock = GetBits(ref inputIndex, input, inputEnd, 1) == 1;
int blockType = GetBits(ref inputIndex, input, inputEnd, 2);
switch (blockType)
{
case 0x00: // No compression
state = InflateState.Stored;
break;
case 0x01: // Fixed Huffman
state = InflateState.Fixed;
break;
case 0x02: // Dynamic Huffman
state = InflateState.Dynamic;
break;
default: // Error or reserved
return outputIndex - outputStart;
}
break;
case InflateState.Stored:
// Uncompressed block
GetBits(ref inputIndex, input, inputEnd, bitCount & 7); // Align to byte
int blockLen = GetBits(ref inputIndex, input, inputEnd, 16);
int blockLenComplement = GetBits(ref inputIndex, input, inputEnd, 16);
inputIndex -= 2; // Backup as we consumed too much
bitCount = 0; // Reset bit buffer
if (blockLen > (inputEnd - inputIndex) || blockLen == 0)
{
return outputIndex - outputStart;
}
// Copy raw data
Array.Copy(input, inputIndex, output, outputIndex, blockLen);
inputIndex += blockLen;
outputIndex += blockLen;
state = InflateState.Header;
break;
case InflateState.Fixed:
// Fixed Huffman codes
byte[] fixedLengths = new byte[288 + 32];
// Set code lengths for fixed Huffman table
for (int n = 0; n <= 143; n++) fixedLengths[n] = 8;
for (int n = 144; n <= 255; n++) fixedLengths[n] = 9;
for (int n = 256; n <= 279; n++) fixedLengths[n] = 7;
for (int n = 280; n <= 287; n++) fixedLengths[n] = 8;
for (int n = 0; n < 32; n++) fixedLengths[288 + n] = 5;
// Build Huffman trees
tableLiteralSize = BuildHuffmanTree(literalTree, fixedLengths, 288);
tableDistanceSize = BuildHuffmanTree(distanceTree, fixedLengths, 288 + 32);
state = InflateState.Block;
break;
case InflateState.Dynamic:
// Dynamic Huffman codes
int literalCount = 257 + GetBits(ref inputIndex, input, inputEnd, 5);
int distanceCount = 1 + GetBits(ref inputIndex, input, inputEnd, 5);
int codeLengthCount = 4 + GetBits(ref inputIndex, input, inputEnd, 4);
byte[] codeLengths = new byte[19];
// Read code lengths for the code length alphabet
for (int i = 0; i < codeLengthCount; i++)
{
codeLengths[order[i]] = (byte)GetBits(ref inputIndex, input, inputEnd, 3);
}
// Build code length tree
tableCodeLengthSize = BuildHuffmanTree(codeLengthTree, codeLengths, 19);
// Read code lengths for literal/length and distance alphabets
byte[] treeLengths = new byte[288 + 32];
int codeIndex = 0;
while (codeIndex < literalCount + distanceCount)
{
int symbol = DecodeSymbol(ref inputIndex, input, inputEnd, codeLengthTree, tableCodeLengthSize);
switch (symbol)
{
case 16: // Copy previous code length 3-6 times
{
int repeat = 3 + GetBits(ref inputIndex, input, inputEnd, 2);
while (repeat-- > 0 && codeIndex < literalCount + distanceCount)
{
treeLengths[codeIndex] = treeLengths[codeIndex - 1];
codeIndex++;
}
break;
}
case 17: // Repeat code length 0 for 3-10 times
{
int repeat = 3 + GetBits(ref inputIndex, input, inputEnd, 3);
while (repeat-- > 0 && codeIndex < literalCount + distanceCount)
{
treeLengths[codeIndex++] = 0;
}
break;
}
case 18: // Repeat code length 0 for 11-138 times
{
int repeat = 11 + GetBits(ref inputIndex, input, inputEnd, 7);
while (repeat-- > 0 && codeIndex < literalCount + distanceCount)
{
treeLengths[codeIndex++] = 0;
}
break;
}
default: // Regular code length 0-15
treeLengths[codeIndex++] = (byte)symbol;
break;
}
}
// Build trees
tableLiteralSize = BuildHuffmanTree(literalTree, treeLengths, literalCount);
tableDistanceSize = BuildHuffmanTree(distanceTree, treeLengths.AsSpan(literalCount, distanceCount).ToArray(), distanceCount);
state = InflateState.Block;
break;
case InflateState.Block:
// Decompress block data
int sym = DecodeSymbol(ref inputIndex, input, inputEnd, literalTree, tableLiteralSize);
if (sym > 256) // Match symbol
{
sym -= 257;
int len = GetBits(ref inputIndex, input, inputEnd, lengthExtraBits[sym]) + lengthBases[sym];
int distSym = DecodeSymbol(ref inputIndex, input, inputEnd, distanceTree, tableDistanceSize);
int dist = GetBits(ref inputIndex, input, inputEnd, distanceExtraBits[distSym]) + distanceBases[distSym];
if (dist > outputIndex - outputStart)
{
return outputIndex - outputStart; // Invalid distance
}
// Copy match data
while (len-- > 0)
{
output[outputIndex] = output[outputIndex - dist];
outputIndex++;
}
}
else if (sym == 256) // End of block
{
if (lastBlock)
{
return outputIndex - outputStart;
}
state = InflateState.Header;
}
else // Literal byte
{
output[outputIndex++] = (byte)sym;
}
break;
}
}
return outputIndex - outputStart;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<RootNamespace>SmallDeflate</RootNamespace>
<AssemblyName>SmallDeflate</AssemblyName>
</PropertyGroup>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment