mirror of
https://github.com/SabreTools/SabreTools.Compression.git
synced 2026-02-04 05:36:03 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa5084d067 | ||
|
|
d9e1748ec1 | ||
|
|
84a3e515c0 | ||
|
|
e217f4109c | ||
|
|
882f2c5335 | ||
|
|
3f6c7dc0d6 | ||
|
|
bf413cbb85 | ||
|
|
7a403cf368 | ||
|
|
aec4611d14 | ||
|
|
7052584cea | ||
|
|
efe6c545b9 | ||
|
|
612a8b3c83 | ||
|
|
bc06cb5bdb | ||
|
|
ae223a4589 | ||
|
|
018fd01922 | ||
|
|
910b01b072 | ||
|
|
d239d9f09b | ||
|
|
0cf3e3e816 | ||
|
|
fe319b71f1 | ||
|
|
cd5bf99f21 | ||
|
|
f79b5353f7 | ||
|
|
5e184b03c5 | ||
|
|
a1417d2f8a | ||
|
|
7580d49830 | ||
|
|
c04c7d438d | ||
|
|
a0f602ed6f | ||
|
|
4bc7f53c9f | ||
|
|
049a8cf499 | ||
|
|
9a8875f1e0 | ||
|
|
a916cb9954 | ||
|
|
a6ef762a73 | ||
|
|
7aac1e0bed | ||
|
|
bc569964d8 | ||
|
|
a3ac98a9f4 | ||
|
|
57c0f9b747 |
238
BitStream.cs
238
BitStream.cs
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO;
|
||||
|
||||
namespace SabreTools.Compression
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper to allow reading bits from a source stream
|
||||
/// </summary>
|
||||
public class BitStream
|
||||
{
|
||||
/// <inheritdoc cref="Stream.Position"/>
|
||||
public long Position => _source.Position;
|
||||
|
||||
/// <inheritdoc cref="Stream.Length"/>
|
||||
public long Length => _source.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Original stream source
|
||||
/// </summary>
|
||||
private Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Last read byte value from the stream
|
||||
/// </summary>
|
||||
private byte? _bitBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Index in the byte of the current bit
|
||||
/// </summary>
|
||||
private int _bitIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BitStream from a source Stream
|
||||
/// </summary>
|
||||
public BitStream(Stream? source)
|
||||
{
|
||||
if (source == null || !source.CanRead || !source.CanSeek)
|
||||
throw new ArgumentException(nameof(source));
|
||||
|
||||
_source = source;
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard the current cached byte
|
||||
/// </summary>
|
||||
public void Discard()
|
||||
{
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single bit, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
|
||||
public byte? ReadBit()
|
||||
{
|
||||
// If we reached the end of the stream
|
||||
if (_source.Position >= _source.Length)
|
||||
return null;
|
||||
|
||||
// If we don't have a value cached
|
||||
if (_bitBuffer == null)
|
||||
{
|
||||
// Read the next byte, if possible
|
||||
_bitBuffer = ReadSourceByte();
|
||||
if (_bitBuffer == null)
|
||||
return null;
|
||||
|
||||
// Reset the bit index
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
// Get the value by bit-shifting
|
||||
int value = _bitBuffer.Value & 0x01;
|
||||
_bitBuffer = (byte?)(_bitBuffer >> 1);
|
||||
_bitIndex++;
|
||||
|
||||
// Reset the byte if we're at the end
|
||||
if (_bitIndex >= 8)
|
||||
Discard();
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in LSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsLSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value += (uint)(bitValue.Value << i);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in MSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsMSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value += (value << 1) + bitValue.Value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a byte, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next byte, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public byte? ReadByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt16, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt16, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ushort? ReadUInt16()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt16();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt32, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt32, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public uint? ReadUInt32()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt32();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt64, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt64, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ulong? ReadUInt64()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt64();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read <paramref name="bytes"/> bytes, if possible
|
||||
/// </summary>
|
||||
/// <param name="bytes">Number of bytes to read</param>
|
||||
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public byte[]? ReadBytes(int bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadBytes(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single byte from the underlying stream, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
|
||||
private byte? ReadSourceByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,16 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
|
||||
|
||||
| Compression Name | Decompress | Compress |
|
||||
| --- | --- | --- |
|
||||
| Blast | Yes | No |
|
||||
| LZ | Yes | No |
|
||||
| LZX | No | No |
|
||||
| MSZIP | Yes* | No |
|
||||
| Quantum | No | No |
|
||||
|
||||
**Note:** If something is marked with a `*` it means that it need testing.
|
||||
|
||||
## External Libraries
|
||||
|
||||
| Library Name | Use |
|
||||
| --- | ---|
|
||||
| [ZLibPort](https://github.com/Nanook/zlib-C-To-CSharp-Port) | Adds zlib code for internal and external use; minor edits have been made |
|
||||
|
||||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression\SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,5 +20,9 @@ Global
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
270
SabreTools.Compression/Blast/Blast.cs
Normal file
270
SabreTools.Compression/Blast/Blast.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
/* blast.c
|
||||
* Copyright (C) 2003, 2012, 2013 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in blast.h
|
||||
* version 1.3, 24 Aug 2013
|
||||
*
|
||||
* blast.c decompresses data compressed by the PKWare Compression Library.
|
||||
* This function provides functionality similar to the explode() function of
|
||||
* the PKWare library, hence the name "blast".
|
||||
*
|
||||
* This decompressor is based on the excellent format description provided by
|
||||
* Ben Rudiak-Gould in comp.compression on August 13, 2001. Interestingly, the
|
||||
* example Ben provided in the post is incorrect. The distance 110001 should
|
||||
* instead be 111000. When corrected, the example byte stream becomes:
|
||||
*
|
||||
* 00 04 82 24 25 8f 80 7f
|
||||
*
|
||||
* which decompresses to "AIAIAIAIAIAIA" (without the quotes).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Change history:
|
||||
*
|
||||
* 1.0 12 Feb 2003 - First version
|
||||
* 1.1 16 Feb 2003 - Fixed distance check for > 4 GB uncompressed data
|
||||
* 1.2 24 Oct 2012 - Add note about using binary mode in stdio
|
||||
* - Fix comparisons of differently signed integers
|
||||
* 1.3 24 Aug 2013 - Return unused input from blast()
|
||||
* - Fix test code to correctly report unused input
|
||||
* - Enable the provision of initial input to blast()
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
public unsafe static class BlastDecoder
|
||||
{
|
||||
#region Huffman Encoding
|
||||
|
||||
/// <summary>
|
||||
/// Literal code
|
||||
/// </summary>
|
||||
private static readonly Huffman litcode = new(MAXBITS + 1, 256);
|
||||
|
||||
/// <summary>
|
||||
/// Length code
|
||||
/// </summary>
|
||||
private static readonly Huffman lencode = new(MAXBITS + 1, 16);
|
||||
|
||||
/// <summary>
|
||||
/// Distance code
|
||||
/// </summary>
|
||||
private static readonly Huffman distcode = new(MAXBITS + 1, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Base for length codes
|
||||
/// </summary>
|
||||
private static readonly short[] baseLength =
|
||||
[
|
||||
3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Extra bits for length codes
|
||||
/// </summary>
|
||||
private static readonly byte[] extra =
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor
|
||||
/// </summary>
|
||||
static BlastDecoder()
|
||||
{
|
||||
// Repeated code lengths of literal codes
|
||||
byte[] litlen =
|
||||
[
|
||||
11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8,
|
||||
9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5,
|
||||
7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12,
|
||||
8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27,
|
||||
44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45,
|
||||
44, 173
|
||||
];
|
||||
litcode.Initialize(litlen);
|
||||
|
||||
// Repeated code lengths of length codes 0..15
|
||||
byte[] lenlen =
|
||||
[
|
||||
2, 35, 36, 53, 38, 23
|
||||
];
|
||||
lencode.Initialize(lenlen);
|
||||
|
||||
// Repeated code lengths of distance codes 0..63
|
||||
byte[] distlen =
|
||||
[
|
||||
2, 20, 53, 230, 247, 151, 248
|
||||
];
|
||||
distcode.Initialize(distlen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// blast() decompresses the PKWare Data Compression Library (DCL) compressed
|
||||
/// format. It provides the same functionality as the explode() function in
|
||||
/// that library. (Note: PKWare overused the "implode" verb, and the format
|
||||
/// used by their library implode() function is completely different and
|
||||
/// incompatible with the implode compression method supported by PKZIP.)
|
||||
///
|
||||
/// The binary mode for stdio functions should be used to assure that the
|
||||
/// compressed data is not corrupted when read or written. For example:
|
||||
/// fopen(..., "rb") and fopen(..., "wb").
|
||||
/// </summary>
|
||||
public static int Blast(byte[] inhow, List<byte> outhow)
|
||||
{
|
||||
// Input/output state
|
||||
var state = new State(inhow, outhow);
|
||||
|
||||
// Attempt to decompress using the above state
|
||||
int err;
|
||||
try
|
||||
{
|
||||
err = Decomp(state);
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
// This was originally a jump, which is bad form for C#
|
||||
err = 2;
|
||||
}
|
||||
|
||||
// Write any leftover output and update the error code if needed
|
||||
if (err != 1 && state.Next != 0 && !state.ProcessOutput() && err == 0)
|
||||
err = 1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode PKWare Compression Library stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// First byte is 0 if literals are uncoded or 1 if they are coded. Second
|
||||
/// byte is 4, 5, or 6 for the number of extra bits in the distance code.
|
||||
/// This is the base-2 logarithm of the dictionary size minus six.
|
||||
///
|
||||
/// Compressed data is a combination of literals and length/distance pairs
|
||||
/// terminated by an end code. Literals are either Huffman coded or
|
||||
/// uncoded bytes. A length/distance pair is a coded length followed by a
|
||||
/// coded distance to represent a string that occurs earlier in the
|
||||
/// uncompressed data that occurs again at the current location.
|
||||
///
|
||||
/// A bit preceding a literal or length/distance pair indicates which comes
|
||||
/// next, 0 for literals, 1 for length/distance.
|
||||
///
|
||||
/// If literals are uncoded, then the next eight bits are the literal, in the
|
||||
/// normal bit order in the stream, i.e. no bit-reversal is needed. Similarly,
|
||||
/// no bit reversal is needed for either the length extra bits or the distance
|
||||
/// extra bits.
|
||||
///
|
||||
/// Literal bytes are simply written to the output. A length/distance pair is
|
||||
/// an instruction to copy previously uncompressed bytes to the output. The
|
||||
/// copy is from distance bytes back in the output stream, copying for length
|
||||
/// bytes.
|
||||
///
|
||||
/// Distances pointing before the beginning of the output data are not
|
||||
/// permitted.
|
||||
///
|
||||
/// Overlapped copies, where the length is greater than the distance, are
|
||||
/// allowed and common. For example, a distance of one and a length of 518
|
||||
/// simply copies the last byte 518 times. A distance of four and a length of
|
||||
/// twelve copies the last four bytes three times. A simple forward copy
|
||||
/// ignoring whether the length is greater than the distance or not implements
|
||||
/// this correctly.
|
||||
/// </remarks>
|
||||
private static int Decomp(State state)
|
||||
{
|
||||
int symbol; // decoded symbol, extra bits for distance
|
||||
int len; // length for copy
|
||||
uint dist; // distance for copy
|
||||
int copy; // copy counter
|
||||
int from, to; // copy pointers
|
||||
|
||||
// Read header
|
||||
int lit = state.Bits(8); // true if literals are coded
|
||||
if (lit > 1)
|
||||
return -1;
|
||||
|
||||
int dict = state.Bits(8); // log2(dictionary size) - 6
|
||||
if (dict < 4 || dict > 6)
|
||||
return -2;
|
||||
|
||||
// Decode literals and length/distance pairs
|
||||
do
|
||||
{
|
||||
if (state.Bits(1) != 0)
|
||||
{
|
||||
// Get length
|
||||
symbol = lencode.Decode(state);
|
||||
len = baseLength[symbol] + state.Bits(extra[symbol]);
|
||||
if (len == 519)
|
||||
break; // end code
|
||||
|
||||
// Get distance
|
||||
symbol = len == 2 ? 2 : dict;
|
||||
dist = (uint)(distcode.Decode(state) << symbol);
|
||||
dist += (uint)state.Bits(symbol);
|
||||
dist++;
|
||||
if (state.First && dist > state.Next)
|
||||
return -3; //distance too far back
|
||||
|
||||
// Copy length bytes from distance bytes back
|
||||
do
|
||||
{
|
||||
to = (int)(state.OutputPtr + state.Next);
|
||||
from = (int)(to - dist);
|
||||
copy = MAXWIN;
|
||||
if (state.Next < dist)
|
||||
{
|
||||
from += copy;
|
||||
copy = (int)dist;
|
||||
}
|
||||
|
||||
copy -= (int)state.Next;
|
||||
if (copy > len)
|
||||
copy = len;
|
||||
|
||||
len -= copy;
|
||||
state.Next += (uint)copy;
|
||||
do
|
||||
{
|
||||
state.Output[to++] = state.Output[from++];
|
||||
}
|
||||
while (--copy != 0);
|
||||
|
||||
if (state.Next == MAXWIN)
|
||||
{
|
||||
if (!state.ProcessOutput())
|
||||
return 1;
|
||||
|
||||
state.Next = 0;
|
||||
state.First = false;
|
||||
}
|
||||
}
|
||||
while (len != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get literal and write it
|
||||
symbol = lit != 0 ? litcode.Decode(state) : state.Bits(8);
|
||||
state.Output[state.Next++] = (byte)symbol;
|
||||
if (state.Next == MAXWIN)
|
||||
{
|
||||
if (!state.ProcessOutput())
|
||||
return 1;
|
||||
|
||||
state.Next = 0;
|
||||
state.First = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
SabreTools.Compression/Blast/Constants.cs
Normal file
15
SabreTools.Compression/Blast/Constants.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum code length
|
||||
/// </summary>
|
||||
public const int MAXBITS = 13;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum window size
|
||||
/// </summary>
|
||||
public const int MAXWIN = 4096;
|
||||
}
|
||||
}
|
||||
207
SabreTools.Compression/Blast/Huffman.cs
Normal file
207
SabreTools.Compression/Blast/Huffman.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
/// <summary>
|
||||
/// Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
|
||||
/// each length, which for a canonical code are stepped through in order.
|
||||
/// symbol[] are the symbol values in canonical order, where the number of
|
||||
/// entries is the sum of the counts in count[]. The decoding process can be
|
||||
/// seen in the function decode() below.
|
||||
/// </summary>
|
||||
public class Huffman
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of symbols of each length
|
||||
/// </summary>
|
||||
public short[] Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to number of symbols of each length
|
||||
/// </summary>
|
||||
public int CountPtr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Canonically ordered symbols
|
||||
/// </summary>
|
||||
public short[] Symbol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="countLength">Length of the Count array</param>
|
||||
/// <param name="symbolLength">Length of the Symbol array</param>
|
||||
public Huffman(int countLength, int symbolLength)
|
||||
{
|
||||
Count = new short[countLength];
|
||||
Symbol = new short[symbolLength];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a list of repeated code lengths rep[0..n-1], where each byte is a
|
||||
/// count (high four bits + 1) and a code length (low four bits), generate the
|
||||
/// list of code lengths. This compaction reduces the size of the object code.
|
||||
/// Then given the list of code lengths length[0..n-1] representing a canonical
|
||||
/// Huffman code for n symbols, construct the tables required to decode those
|
||||
/// codes. Those tables are the number of codes of each length, and the symbols
|
||||
/// sorted by length, retaining their original order within each length. The
|
||||
/// return value is zero for a complete code set, negative for an over-
|
||||
/// subscribed code set, and positive for an incomplete code set. The tables
|
||||
/// can be used if the return value is zero or positive, but they cannot be used
|
||||
/// if the return value is negative. If the return value is zero, it is not
|
||||
/// possible for decode() using that table to return an error--any stream of
|
||||
/// enough bits will resolve to a symbol. If the return value is positive, then
|
||||
/// it is possible for decode() using that table to return an error for received
|
||||
/// codes past the end of the incomplete lengths.
|
||||
/// </summary>
|
||||
/// <param name="rep">Repeated code length array</param>
|
||||
public int Initialize(byte[] rep)
|
||||
{
|
||||
int n = rep.Length; // Length of the bit length array
|
||||
short symbol = 0; // Current symbol when stepping through length[]
|
||||
short len; // Current length when stepping through h.Count[]
|
||||
int left; // Number of possible codes left of current length
|
||||
short[] offs = new short[MAXBITS + 1]; // offsets in symbol table for each length
|
||||
short[] length = new short[256]; // Code lengths
|
||||
|
||||
// Convert compact repeat counts into symbol bit length list
|
||||
int repPtr = 0;
|
||||
do
|
||||
{
|
||||
len = rep[repPtr++];
|
||||
left = (len >> 4) + 1;
|
||||
len &= 15;
|
||||
do
|
||||
{
|
||||
length[symbol++] = len;
|
||||
}
|
||||
while (--left != 0);
|
||||
}
|
||||
while (--n != 0);
|
||||
|
||||
n = symbol;
|
||||
|
||||
// Count number of codes of each length
|
||||
for (len = 0; len <= MAXBITS; len++)
|
||||
{
|
||||
Count[len] = 0;
|
||||
}
|
||||
|
||||
// Assumes lengths are within bounds
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
{
|
||||
(Count[length[symbol]])++;
|
||||
}
|
||||
|
||||
// No codes! Complete, but decode() will fail
|
||||
if (Count[0] == n)
|
||||
return 0;
|
||||
|
||||
// Check for an over-subscribed or incomplete set of lengths
|
||||
left = 1; // One possible code of zero length
|
||||
for (len = 1; len <= MAXBITS; len++)
|
||||
{
|
||||
left <<= 1; // One more bit, double codes left
|
||||
left -= Count[len]; // Deduct count from possible codes
|
||||
if (left < 0)
|
||||
return left; // over-subscribed--return negative
|
||||
}
|
||||
|
||||
// Generate offsets into symbol table for each length for sorting
|
||||
offs[1] = 0;
|
||||
for (len = 1; len < MAXBITS; len++)
|
||||
{
|
||||
offs[len + 1] = (short)(offs[len] + Count[len]);
|
||||
}
|
||||
|
||||
// Put symbols in table sorted by length, by symbol order within each length
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
{
|
||||
if (length[symbol] != 0)
|
||||
Symbol[offs[length[symbol]]++] = symbol;
|
||||
}
|
||||
|
||||
// Return zero for complete set, positive for incomplete set
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a code from the stream s using huffman table h. Return the symbol or
|
||||
/// a negative value if there is an error. If all of the lengths are zero, i.e.
|
||||
/// an empty code, or if the code is incomplete and an invalid code is received,
|
||||
/// then -9 is returned after reading MAXBITS bits.
|
||||
/// </summary>
|
||||
/// <param name="state">Current input/output state to process</param>
|
||||
/// <remarks>
|
||||
/// The codes as stored in the compressed data are bit-reversed relative to
|
||||
/// a simple integer ordering of codes of the same lengths. Hence below the
|
||||
/// bits are pulled from the compressed data one at a time and used to
|
||||
/// build the code value reversed from what is in the stream in order to
|
||||
/// permit simple integer comparisons for decoding.
|
||||
///
|
||||
/// The first code for the shortest length is all ones. Subsequent codes of
|
||||
/// the same length are simply integer decrements of the previous code. When
|
||||
/// moving up a length, a one bit is appended to the code. For a complete
|
||||
/// code, the last code of the longest length will be all zeros. To support
|
||||
/// this ordering, the bits pulled during decoding are inverted to apply the
|
||||
/// more "natural" ordering starting with all zeros and incrementing.
|
||||
/// </remarks>
|
||||
public int Decode(State state)
|
||||
{
|
||||
int len = 1; // Current number of bits in code
|
||||
int code = 0; // len bits being decoded
|
||||
int first = 0; // First code of length len
|
||||
int count; // Number of codes of length len
|
||||
int index = 0; // Index of first code of length len in symbol table
|
||||
int bitbuf = state.BitBuf; // Bits from stream
|
||||
int left = state.BitCnt; // Bits left in next or left to process
|
||||
int nextPtr = CountPtr + 1; // Next number of codes
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (left-- != 0)
|
||||
{
|
||||
// Invert code
|
||||
code |= (bitbuf & 1) ^ 1;
|
||||
bitbuf >>= 1;
|
||||
count = Count[nextPtr++];
|
||||
|
||||
// If length len, return symbol
|
||||
if (code < first + count)
|
||||
{
|
||||
state.BitBuf = bitbuf;
|
||||
state.BitCnt = (state.BitCnt - len) & 7;
|
||||
return Symbol[index + (code - first)];
|
||||
}
|
||||
|
||||
// Else update for next length
|
||||
index += count;
|
||||
first += count;
|
||||
first <<= 1;
|
||||
code <<= 1;
|
||||
len++;
|
||||
}
|
||||
|
||||
left = (MAXBITS + 1) - len;
|
||||
if (left == 0)
|
||||
break;
|
||||
|
||||
if (state.Left == 0)
|
||||
{
|
||||
state.Left = state.ProcessInput();
|
||||
if (state.Left == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
bitbuf = state.Input[state.InputPtr++];
|
||||
state.Left--;
|
||||
if (left > 8)
|
||||
left = 8;
|
||||
}
|
||||
|
||||
// Ran out of codes
|
||||
return -9;
|
||||
}
|
||||
};
|
||||
}
|
||||
160
SabreTools.Compression/Blast/State.cs
Normal file
160
SabreTools.Compression/Blast/State.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
/// <summary>
|
||||
/// Input and output state
|
||||
/// </summary>
|
||||
public class State
|
||||
{
|
||||
#region Input State
|
||||
|
||||
/// <summary>
|
||||
/// Opaque information passed to InputFunction()
|
||||
/// </summary>
|
||||
public byte[] InHow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Next input location
|
||||
/// </summary>
|
||||
public List<byte> Input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next input location
|
||||
/// </summary>
|
||||
public int InputPtr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Available input at in
|
||||
/// </summary>
|
||||
public uint Left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit buffer
|
||||
/// </summary>
|
||||
public int BitBuf { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of bits in bit buffer
|
||||
/// </summary>
|
||||
public int BitCnt { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Output State
|
||||
|
||||
/// <summary>
|
||||
/// Opaque information passed to OutputFunction()
|
||||
/// </summary>
|
||||
public List<byte> OutHow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of next write location in out[]
|
||||
/// </summary>
|
||||
public uint Next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to check distances (for first 4K)
|
||||
/// </summary>
|
||||
public bool First { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output buffer and sliding window
|
||||
/// </summary>
|
||||
public byte[] Output { get; set; } = new byte[MAXWIN];
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next output location
|
||||
/// </summary>
|
||||
public int OutputPtr { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="inhow">Input byte array</param>
|
||||
/// <param name="outhow">Output byte list</param>
|
||||
public State(byte[] inhow, List<byte> outhow)
|
||||
{
|
||||
InHow = inhow;
|
||||
Input = new List<byte>();
|
||||
InputPtr = 0;
|
||||
Left = 0;
|
||||
BitBuf = 0;
|
||||
BitCnt = 0;
|
||||
|
||||
OutHow = outhow;
|
||||
Next = 0;
|
||||
First = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return need bits from the input stream. This always leaves less than
|
||||
/// eight bits in the buffer. bits() works properly for need == 0.
|
||||
/// </summary>
|
||||
/// <param name="need">Number of bits to read</param>
|
||||
/// <remarks>
|
||||
/// Bits are stored in bytes from the least significant bit to the most
|
||||
/// significant bit. Therefore bits are dropped from the bottom of the bit
|
||||
/// buffer, using shift right, and new bytes are appended to the top of the
|
||||
/// bit buffer, using shift left.
|
||||
/// </remarks>
|
||||
public int Bits(int need)
|
||||
{
|
||||
// Load at least need bits into val
|
||||
int val = BitBuf;
|
||||
while (BitCnt < need)
|
||||
{
|
||||
if (Left == 0)
|
||||
{
|
||||
Left = ProcessInput();
|
||||
if (Left == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
// Load eight bits
|
||||
val |= (int)(Input[InputPtr++]) << BitCnt;
|
||||
Left--;
|
||||
BitCnt += 8;
|
||||
}
|
||||
|
||||
// Drop need bits and update buffer, always zero to seven bits left
|
||||
BitBuf = val >> need;
|
||||
BitCnt -= need;
|
||||
|
||||
// Return need bits, zeroing the bits above that
|
||||
return val & ((1 << need) - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process input for the current state
|
||||
/// </summary>
|
||||
/// <returns>Amount of data in Input</returns>
|
||||
public uint ProcessInput()
|
||||
{
|
||||
Input = new List<byte>(InHow);
|
||||
return (uint)Input.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process output for the current state
|
||||
/// </summary>
|
||||
/// <returns>True if the output could be added, false otherwise</returns>
|
||||
public bool ProcessOutput()
|
||||
{
|
||||
try
|
||||
{
|
||||
OutHow.AddRange(Output.Take((int)Next));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Compression.LZ;
|
||||
using static SabreTools.Models.Compression.LZ.Constants;
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace SabreTools.Compression.LZ
|
||||
long read = lz.CopyTo(sourceState, destState, out LZERROR error);
|
||||
|
||||
// Copy the data to the buffer
|
||||
var decompressed = new byte[0];
|
||||
byte[]? decompressed;
|
||||
if (read == 0 || (error != LZERROR.LZERROR_OK && error != LZERROR.LZERROR_NOT_LZ))
|
||||
{
|
||||
decompressed = null;
|
||||
@@ -552,6 +552,6 @@ namespace SabreTools.Compression.LZ
|
||||
return fileHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Models.Compression.MSZIP;
|
||||
using static SabreTools.Models.Compression.MSZIP.Constants;
|
||||
|
||||
@@ -13,7 +14,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
/// <summary>
|
||||
/// Internal bitstream to use for decompression
|
||||
/// </summary>
|
||||
private BitStream _bitStream;
|
||||
private readonly ReadOnlyBitStream _bitStream;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Decompressor from a byte array
|
||||
@@ -28,8 +29,8 @@ namespace SabreTools.Compression.MSZIP
|
||||
// Create a memory stream to wrap
|
||||
var ms = new MemoryStream(input);
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(ms);
|
||||
// Wrap the stream in a ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(ms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,8 +43,8 @@ namespace SabreTools.Compression.MSZIP
|
||||
if (input == null || !input.CanRead || !input.CanSeek)
|
||||
throw new ArgumentException(nameof(input));
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(input);
|
||||
// Wrap the stream in a ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,22 +121,24 @@ namespace SabreTools.Compression.MSZIP
|
||||
/// <summary>
|
||||
/// Read a FixedHuffmanCompressedBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private (FixedCompressedDataHeader, uint, uint) RaadFixedCompressedDataHeader()
|
||||
private FixedCompressedDataHeader ReadFixedCompressedDataHeader(out uint numLiteral, out uint numDistance)
|
||||
{
|
||||
// Nothing needs to be read, all values are fixed
|
||||
return (new FixedCompressedDataHeader(), 288, 30);
|
||||
numLiteral = 288;
|
||||
numDistance = 30;
|
||||
return new FixedCompressedDataHeader();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DynamicHuffmanCompressedBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private (DynamicCompressedDataHeader, uint, uint) ReadDynamicCompressedDataHeader()
|
||||
private DynamicCompressedDataHeader ReadDynamicCompressedDataHeader(out uint numLiteral, out uint numDistance)
|
||||
{
|
||||
var header = new DynamicCompressedDataHeader();
|
||||
|
||||
// Setup the counts first
|
||||
uint numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
uint numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
uint numLength = 4 + _bitStream.ReadBitsLSB(4) ?? 0;
|
||||
|
||||
// Convert the alphabet based on lengths
|
||||
@@ -150,7 +153,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
}
|
||||
|
||||
// Make the lengths tree
|
||||
HuffmanDecoder lengthTree = new HuffmanDecoder(lengthLengths, 19);
|
||||
var lengthTree = new HuffmanDecoder(lengthLengths, 19);
|
||||
|
||||
// Setup the literal and distance lengths
|
||||
header.LiteralLengths = new uint[288];
|
||||
@@ -161,7 +164,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
uint leftover = ReadHuffmanLengths(lengthTree, header.LiteralLengths, numLiteral, 0, ref repeatCode);
|
||||
_ = ReadHuffmanLengths(lengthTree, header.DistanceCodes, numDistance, leftover, ref repeatCode);
|
||||
|
||||
return (header, numLiteral, numDistance);
|
||||
return header;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -181,7 +184,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
// If stored with no compression
|
||||
case CompressionType.NoCompression:
|
||||
(var header00, var bytes00) = ReadNoCompression();
|
||||
var header00 = ReadNoCompression(out byte[]? bytes00);
|
||||
if (header00 == null || bytes00 == null)
|
||||
return null;
|
||||
|
||||
@@ -191,7 +194,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
|
||||
// If compressed with fixed Huffman codes
|
||||
case CompressionType.FixedHuffman:
|
||||
(var header01, var bytes01) = ReadFixedHuffman();
|
||||
var header01 = ReadFixedHuffman(out byte[]? bytes01);
|
||||
if (header01 == null || bytes01 == null)
|
||||
return null;
|
||||
|
||||
@@ -201,7 +204,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
|
||||
// If compressed with dynamic Huffman codes
|
||||
case CompressionType.DynamicHuffman:
|
||||
(var header10, var bytes10) = ReadDynamicHuffman();
|
||||
var header10 = ReadDynamicHuffman(out byte[]? bytes10);
|
||||
if (header10 == null || bytes10 == null)
|
||||
return null;
|
||||
|
||||
@@ -221,7 +224,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block with no compression
|
||||
/// </summary>
|
||||
private (NonCompressedBlockHeader?, byte[]?) ReadNoCompression()
|
||||
private NonCompressedBlockHeader? ReadNoCompression(out byte[]? data)
|
||||
{
|
||||
// Skip any remaining bits in current partially processed byte
|
||||
_bitStream.Discard();
|
||||
@@ -229,44 +232,50 @@ namespace SabreTools.Compression.MSZIP
|
||||
// Read LEN and NLEN
|
||||
var header = ReadNonCompressedBlockHeader();
|
||||
if (header.LEN == 0 && header.NLEN == 0)
|
||||
return (null, null);
|
||||
{
|
||||
data = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Copy LEN bytes of data to output
|
||||
return (header, _bitStream.ReadBytes(header.LEN));
|
||||
data = _bitStream.ReadBytes(header.LEN);
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block with fixed Huffman compression
|
||||
/// </summary>
|
||||
private (FixedCompressedDataHeader, byte[]?) ReadFixedHuffman()
|
||||
private FixedCompressedDataHeader? ReadFixedHuffman(out byte[]? data)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
|
||||
// Get the fixed huffman header
|
||||
(var header, uint numLiteral, uint numDistance) = RaadFixedCompressedDataHeader();
|
||||
var header = ReadFixedCompressedDataHeader(out uint numLiteral, out uint numDistance);
|
||||
|
||||
// Make the literal and distance trees
|
||||
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
var distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
|
||||
// Now loop and decode
|
||||
return (header, ReadHuffmanBlock(literalTree, distanceTree));
|
||||
data = ReadHuffmanBlock(literalTree, distanceTree);
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block with dynamic Huffman compression
|
||||
/// </summary>
|
||||
private (DynamicCompressedDataHeader?, byte[]?) ReadDynamicHuffman()
|
||||
private DynamicCompressedDataHeader? ReadDynamicHuffman(out byte[]? data)
|
||||
{
|
||||
// Get the dynamic huffman header
|
||||
(var header, uint numLiteral, uint numDistance) = ReadDynamicCompressedDataHeader();
|
||||
var header = ReadDynamicCompressedDataHeader(out uint numLiteral, out uint numDistance);
|
||||
|
||||
// Make the literal and distance trees
|
||||
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
var distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
|
||||
// Now loop and decode
|
||||
return (header, ReadHuffmanBlock(literalTree, distanceTree));
|
||||
data = ReadHuffmanBlock(literalTree, distanceTree);
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.IO.Streams;
|
||||
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
@@ -83,7 +84,7 @@ namespace SabreTools.Compression.MSZIP
|
||||
/// </summary>
|
||||
/// <param name="input">BitStream representing the input</param>
|
||||
/// <returns>Value of the node described by the input</returns>
|
||||
public int Decode(BitStream input)
|
||||
public int Decode(ReadOnlyBitStream input)
|
||||
{
|
||||
// Start at the root of the tree
|
||||
var node = _root;
|
||||
@@ -4,48 +4,48 @@ namespace SabreTools.Compression.Quantum
|
||||
/// TODO: Remove this class when Models gets updated
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly int[] PositionSlot = new int[]
|
||||
{
|
||||
public static readonly int[] PositionSlot =
|
||||
[
|
||||
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
|
||||
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
|
||||
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
|
||||
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
|
||||
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
|
||||
0x100000, 0x180000
|
||||
};
|
||||
];
|
||||
|
||||
public static readonly int[] PositionExtraBits = new int[]
|
||||
{
|
||||
public static readonly int[] PositionExtraBits =
|
||||
[
|
||||
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, 14, 14,
|
||||
15, 15, 16, 16, 17, 17, 18, 18,
|
||||
19, 19
|
||||
};
|
||||
];
|
||||
|
||||
public static readonly int[] LengthSlot = new int[]
|
||||
{
|
||||
public static readonly int[] LengthSlot =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
|
||||
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
|
||||
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
|
||||
0xbe, 0xde, 0xfe
|
||||
};
|
||||
];
|
||||
|
||||
public static readonly int[] LengthExtraBits = new int[]
|
||||
{
|
||||
public static readonly int[] LengthExtraBits =
|
||||
[
|
||||
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
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Number of position slots for (tsize - 10)
|
||||
/// </summary>
|
||||
public static readonly int[] NumPositionSlots = new int[]
|
||||
{
|
||||
public static readonly int[] NumPositionSlots =
|
||||
[
|
||||
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Models.Compression.Quantum;
|
||||
using static SabreTools.Compression.Quantum.Constants;
|
||||
|
||||
@@ -12,7 +13,7 @@ namespace SabreTools.Compression.Quantum
|
||||
/// <summary>
|
||||
/// Internal bitstream to use for decompression
|
||||
/// </summary>
|
||||
private BitStream _bitStream;
|
||||
private readonly ReadOnlyBitStream _bitStream;
|
||||
|
||||
#region Models
|
||||
|
||||
@@ -100,8 +101,8 @@ namespace SabreTools.Compression.Quantum
|
||||
// Create a memory stream to wrap
|
||||
var ms = new MemoryStream(input);
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(ms);
|
||||
// Wrap the stream in a ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(ms);
|
||||
|
||||
// Initialize literal models
|
||||
this._model0 = CreateModel(0, 64);
|
||||
@@ -140,8 +141,8 @@ namespace SabreTools.Compression.Quantum
|
||||
if (windowBits < 10 || windowBits > 21)
|
||||
throw new ArgumentOutOfRangeException(nameof(windowBits));
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(input);
|
||||
// Wrap the stream in a ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(input);
|
||||
|
||||
// Initialize literal models
|
||||
this._model0 = CreateModel(0, 64);
|
||||
@@ -217,7 +218,7 @@ namespace SabreTools.Compression.Quantum
|
||||
offset = PositionSlot[model4sym] + model4extra + 1;
|
||||
length = 3;
|
||||
break;
|
||||
|
||||
|
||||
case 5:
|
||||
int model5sym = GetSymbol(_model5);
|
||||
int model5extra = (int)(_bitStream.ReadBitsMSB(PositionExtraBits[model5sym]) ?? 0);
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
@@ -8,7 +8,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>0.3.1</Version>
|
||||
<Version>0.5.2</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -18,17 +18,17 @@
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Compression</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>compression decompression lz mszip</PackageTags>
|
||||
<PackageTags>compression decompression lz mszip zlib blast</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.3.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.4.12" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
218
SabreTools.Compression/zlib/Hebron.Runtime/CRuntime.cs
Normal file
218
SabreTools.Compression/zlib/Hebron.Runtime/CRuntime.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public static unsafe class CRuntime
|
||||
{
|
||||
private static readonly string numbers = "0123456789";
|
||||
|
||||
public static void* malloc(ulong size)
|
||||
{
|
||||
return malloc((long)size);
|
||||
}
|
||||
|
||||
public static void* malloc(long size)
|
||||
{
|
||||
var ptr = Marshal.AllocHGlobal((int)size);
|
||||
|
||||
MemoryStats.Allocated();
|
||||
|
||||
return ptr.ToPointer();
|
||||
}
|
||||
|
||||
public static void free(void* a)
|
||||
{
|
||||
if (a == null)
|
||||
return;
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
MemoryStats.Freed();
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, long size)
|
||||
{
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*ap++ = *bp++;
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, ulong size)
|
||||
{
|
||||
memcpy(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, long size)
|
||||
{
|
||||
void* temp = null;
|
||||
|
||||
try
|
||||
{
|
||||
temp = malloc(size);
|
||||
memcpy(temp, b, size);
|
||||
memcpy(a, temp, size);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (temp != null)
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, ulong size)
|
||||
{
|
||||
memmove(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, long size)
|
||||
{
|
||||
var result = 0;
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
{
|
||||
if (*ap != *bp)
|
||||
result += 1;
|
||||
|
||||
ap++;
|
||||
bp++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, ulong size)
|
||||
{
|
||||
return memcmp(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(byte* a, byte[] b, ulong size)
|
||||
{
|
||||
fixed (void* bptr = b)
|
||||
{
|
||||
return memcmp(a, bptr, (long)size);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, long size)
|
||||
{
|
||||
var bptr = (byte*)ptr;
|
||||
var bval = (byte)value;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*bptr++ = bval;
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, ulong size)
|
||||
{
|
||||
memset(ptr, value, (long)size);
|
||||
}
|
||||
|
||||
public static uint _lrotl(uint x, int y)
|
||||
{
|
||||
return (x << y) | (x >> (32 - y));
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, long newSize)
|
||||
{
|
||||
if (a == null)
|
||||
return malloc(newSize);
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
var result = Marshal.ReAllocHGlobal(ptr, new IntPtr(newSize));
|
||||
|
||||
return result.ToPointer();
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, ulong newSize)
|
||||
{
|
||||
return realloc(a, (long)newSize);
|
||||
}
|
||||
|
||||
public static int abs(int v)
|
||||
{
|
||||
return Math.Abs(v);
|
||||
}
|
||||
|
||||
public static double pow(double a, double b)
|
||||
{
|
||||
return Math.Pow(a, b);
|
||||
}
|
||||
|
||||
public static void SetArray<T>(T[] data, T value)
|
||||
{
|
||||
for (var i = 0; i < data.Length; ++i)
|
||||
data[i] = value;
|
||||
}
|
||||
|
||||
public static double ldexp(double number, int exponent)
|
||||
{
|
||||
return number * Math.Pow(2, exponent);
|
||||
}
|
||||
|
||||
public static int strcmp(sbyte* src, string token)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < token.Length; ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int strncmp(sbyte* src, string token, ulong size)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < Math.Min(token.Length, (int)size); ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long strtol(sbyte* start, sbyte** end, int radix)
|
||||
{
|
||||
// First step - determine length
|
||||
var length = 0;
|
||||
sbyte* ptr = start;
|
||||
while (numbers.IndexOf((char)*ptr) != -1)
|
||||
{
|
||||
++ptr;
|
||||
++length;
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
|
||||
// Now build up the number
|
||||
ptr = start;
|
||||
while (length > 0)
|
||||
{
|
||||
long num = numbers.IndexOf((char)*ptr);
|
||||
long pow = (long)Math.Pow(10, length - 1);
|
||||
result += num * pow;
|
||||
|
||||
++ptr;
|
||||
--length;
|
||||
}
|
||||
|
||||
if (end != null)
|
||||
{
|
||||
*end = ptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
SabreTools.Compression/zlib/Hebron.Runtime/MemoryStats.cs
Normal file
28
SabreTools.Compression/zlib/Hebron.Runtime/MemoryStats.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe static class MemoryStats
|
||||
{
|
||||
private static int _allocations;
|
||||
|
||||
public static int Allocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allocations;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Allocated()
|
||||
{
|
||||
Interlocked.Increment(ref _allocations);
|
||||
}
|
||||
|
||||
internal static void Freed()
|
||||
{
|
||||
Interlocked.Decrement(ref _allocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
88
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray1D<T> where T : struct
|
||||
{
|
||||
private readonly T[] _data;
|
||||
private readonly GCHandle _pinHandle;
|
||||
public bool IsFreed { get; private set; }
|
||||
|
||||
internal GCHandle PinHandle => _pinHandle;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T this[uint index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Data => _data;
|
||||
|
||||
public UnsafeArray1D(int size)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(size));
|
||||
}
|
||||
|
||||
_data = new T[size];
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public UnsafeArray1D(T[] data, int sizeOf)
|
||||
{
|
||||
if (sizeOf <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOf));
|
||||
}
|
||||
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
if (!IsFreed)
|
||||
{
|
||||
_pinHandle.Free();
|
||||
IsFreed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UnsafeArray1D()
|
||||
{
|
||||
if (!IsFreed)
|
||||
_pinHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer()
|
||||
{
|
||||
return _pinHandle.AddrOfPinnedObject().ToPointer();
|
||||
}
|
||||
|
||||
public static implicit operator void*(UnsafeArray1D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
|
||||
public static void* operator +(UnsafeArray1D<T> array, int delta)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
45
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray2D<T> where T : struct
|
||||
{
|
||||
private readonly UnsafeArray1D<T>[] _data;
|
||||
private long[] _pinAddresses;
|
||||
private readonly GCHandle _pinAddressesHandle;
|
||||
|
||||
public UnsafeArray1D<T> this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UnsafeArray2D(int size1, int size2)
|
||||
{
|
||||
_data = new UnsafeArray1D<T>[size1];
|
||||
_pinAddresses = new long[size1];
|
||||
for (var i = 0; i < size1; ++i)
|
||||
{
|
||||
_data[i] = new UnsafeArray1D<T>(size2);
|
||||
_pinAddresses[i] = _data[i].PinHandle.AddrOfPinnedObject().ToInt64();
|
||||
}
|
||||
|
||||
_pinAddressesHandle = GCHandle.Alloc(_pinAddresses, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
~UnsafeArray2D()
|
||||
{
|
||||
_pinAddressesHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer() => _pinAddressesHandle.AddrOfPinnedObject().ToPointer();
|
||||
|
||||
public static implicit operator void*(UnsafeArray2D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SabreTools.Compression/zlib/LICENSE
Normal file
9
SabreTools.Compression/zlib/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Nanook
|
||||
|
||||
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.
|
||||
23
SabreTools.Compression/zlib/README.md
Normal file
23
SabreTools.Compression/zlib/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
Project to port ZLib from C to C# (CSharp).
|
||||
|
||||
Src zlib 1.2.12 2022-Mar-28 - https://github.com/madler/zlib
|
||||
|
||||
See the Stages folder
|
||||
|
||||
1_zlib.c - Created by running 1_zlib.c_Concat.ps1 Builds with Clang (used by hebron to convert)
|
||||
- Only deflate, inflate, crc32 and adler32 code at the moment. GZip might be added if required.
|
||||
- The only edits to these files are to remove any #includes that have been combined
|
||||
- The file list includes a 000_ to insert any #defines etc and 100_ for a main for debugging etc
|
||||
- Notice crc32.c and trees.c had to be split to allow the single file to build
|
||||
|
||||
2_zlib.cs_Converted - The converted output that Hebron produced - https://github.com/HebronFramework/Hebron
|
||||
- This is a little app that uses Clang to read the C code as DOM and write with Roslyn
|
||||
- It does a fairly decent job and removes a lot of complication
|
||||
|
||||
3_zlib.cs_Working - The fixed up and amended C# that actually runs and matches the C code output
|
||||
- It's had minimal change so is not the prettiest C# code
|
||||
- It's Unsafe in places
|
||||
|
||||
Deflate and Inflate streams have been added.
|
||||
178
SabreTools.Compression/zlib/ZlibDeflateStream.cs
Normal file
178
SabreTools.Compression/zlib/ZlibDeflateStream.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibDeflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream) : this(level, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream, bool leaveOpen) : this(level, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, bool headerless, Stream baseStream, bool leaveOpen) : this(level, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, int bufferSize, Stream baseStream, bool leaveOpen) : this(level, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.Level = level;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
|
||||
public int Level { get; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.deflateInit_(_s, this.Level, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
hdr = 2;
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
_s.avail_in = (uint)count;
|
||||
|
||||
fixed (byte* i = buffer, o = _b)
|
||||
{
|
||||
_s.next_in = i;
|
||||
_s.next_out = o + _s.total_out;
|
||||
|
||||
while (err >= 0 && _s.avail_in != 0) //process the buffer
|
||||
{
|
||||
if (_s.avail_out == 0) //get more data
|
||||
{
|
||||
if (_s.total_out != 0)
|
||||
{
|
||||
if (hdr != 0)
|
||||
{
|
||||
BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.total_out -= (uint)hdr;
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
BaseStream.Write(_b!, 0, (int)_s.total_out);
|
||||
}
|
||||
_p += _s.total_out;
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || _s.avail_out != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be written to the base stream. Call when write is finished with.
|
||||
/// Used for creating block seekable files. The caller must manage blocks, indexes and lengths
|
||||
/// </summary>
|
||||
unsafe public long BlockFlush()
|
||||
{
|
||||
//finish previous stream
|
||||
if (_s != null)
|
||||
{
|
||||
int err = 0;
|
||||
fixed (byte* o = _b)
|
||||
{
|
||||
_s.next_in = null;
|
||||
_s.avail_in = 0;
|
||||
_s.next_out = o + _s.total_out; //point to correct location
|
||||
|
||||
int hdr = _p == 0 && Headerless ? 2 : 0;
|
||||
while (err == 0 && (_s.total_out != 0 || _s.state!.pending != 0))
|
||||
{
|
||||
this.BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_p += _s.total_out - hdr;
|
||||
hdr = 0;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
if (_s.state!.pending != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
err = ZLib.deflate(_s, 4);
|
||||
|
||||
}
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsafe protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.BlockFlush();
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
SabreTools.Compression/zlib/ZlibInflateStream.cs
Normal file
170
SabreTools.Compression/zlib/ZlibInflateStream.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibInflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
private bool _complete;
|
||||
|
||||
public ZlibInflateStream(Stream baseStream) : this(0, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(Stream baseStream, bool leaveOpen) : this(0, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, Stream baseStream, bool leaveOpen) : this(0, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, int bufferSize, Stream baseStream, bool leaveOpen) : this(0, headerless, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(int bufferSize, Stream baseStream, bool leaveOpen) : this(0, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(long maxRead, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.MaxRead = maxRead == 0 ? int.MaxValue : maxRead;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
_complete = false;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
public long MaxRead { get; private set; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
if (_complete)
|
||||
return 0;
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.inflateInit_(_s, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
{
|
||||
_b![0] = 0x78;
|
||||
_b[1] = 0x9c; //da
|
||||
hdr = 2;
|
||||
}
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
int read;
|
||||
_s.avail_out = (uint)count;
|
||||
|
||||
fixed (byte* i = _b, o = buffer)
|
||||
{
|
||||
_s.next_in = i + _s.total_in;
|
||||
_s.next_out = o;
|
||||
|
||||
while (err == 0 && (_s.avail_out != 0 && !_complete)) //process the buffer
|
||||
{
|
||||
if (_s.avail_in == 0) //get more data
|
||||
{
|
||||
_s.total_in = 0;
|
||||
read = (int)Math.Min(this.MaxRead - _p, (long)_b!.Length);
|
||||
if (hdr != 0) //test once to save on the extra calculations
|
||||
{
|
||||
_s.avail_in = (uint)(hdr + (read = BaseStream.Read(_b, hdr, Math.Min(read, _b.Length - hdr))));
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
_s.avail_in = (uint)(read = BaseStream.Read(_b, 0, read));
|
||||
_complete = read == 0;
|
||||
_p += (long)read;
|
||||
_s.next_in = i;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || (!_complete && _s.total_out != 0))
|
||||
err = ZLib.inflate(_s, 2);
|
||||
}
|
||||
}
|
||||
|
||||
uint ret = _s.total_out;
|
||||
_s.total_out = 0u;
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be read from the base stream without overreading. Call when write is finished with.
|
||||
/// Used for reading block seekable files. The caller must manage blocks, indexes and lengths. Seek the BaseStream
|
||||
/// </summary>
|
||||
public long BlockFlush(int maxRead)
|
||||
{
|
||||
this.MaxRead = maxRead;
|
||||
if (_s != null)
|
||||
{
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
_complete = false;
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
BlockFlush(0);
|
||||
_complete = true;
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
7101
SabreTools.Compression/zlib/zlib.cs
Normal file
7101
SabreTools.Compression/zlib/zlib.cs
Normal file
File diff suppressed because one or more lines are too long
47
SabreTools.Compression/zlib/zlibConst.cs
Normal file
47
SabreTools.Compression/zlib/zlibConst.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public static class zlibConst
|
||||
{
|
||||
public const int Z_NO_FLUSH = 0;
|
||||
public const int Z_PARTIAL_FLUSH = 1;
|
||||
public const int Z_SYNC_FLUSH = 2;
|
||||
public const int Z_FULL_FLUSH = 3;
|
||||
public const int Z_FINISH = 4;
|
||||
public const int Z_BLOCK = 5;
|
||||
public const int Z_TREES = 6;
|
||||
|
||||
public const int Z_OK = 0;
|
||||
public const int Z_STREAM_END = 1;
|
||||
public const int Z_NEED_DICT = 2;
|
||||
public const int Z_ERRNO = (-1);
|
||||
public const int Z_STREAM_ERROR = (-2);
|
||||
public const int Z_DATA_ERROR = (-3);
|
||||
public const int Z_MEM_ERROR = (-4);
|
||||
public const int Z_BUF_ERROR = (-5);
|
||||
public const int Z_VERSION_ERROR = (-6);
|
||||
|
||||
/// <summary>
|
||||
/// Get the zlib result name from an integer
|
||||
/// </summary>
|
||||
/// <param name="result">Integer to translate to the result name</param>
|
||||
/// <returns>Name of the result, the integer as a string otherwise</returns>
|
||||
public static string ToZlibConstName(this int result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
Z_OK => "Z_OK",
|
||||
Z_STREAM_END => "Z_STREAM_END",
|
||||
Z_NEED_DICT => "Z_NEED_DICT",
|
||||
|
||||
Z_ERRNO => "Z_ERRNO",
|
||||
Z_STREAM_ERROR => "Z_STREAM_ERROR",
|
||||
Z_DATA_ERROR => "Z_DATA_ERROR",
|
||||
Z_MEM_ERROR => "Z_MEM_ERROR",
|
||||
Z_BUF_ERROR => "Z_BUF_ERROR",
|
||||
Z_VERSION_ERROR => "Z_VERSION_ERROR",
|
||||
|
||||
_ => result.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Test/Program.cs
Normal file
12
Test/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using SabreTools.Compression;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// No implementation, used for experimentation
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Test/Test.csproj
Normal file
19
Test/Test.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Compression\SabreTools.Compression.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
publish-nix.sh
Executable file
36
publish-nix.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
NO_BUILD=false
|
||||
while getopts "b" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
NO_BUILD=true
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option provided"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set the current directory as a variable
|
||||
BUILD_FOLDER=$PWD
|
||||
|
||||
# Only build if requested
|
||||
if [ $NO_BUILD = false ]
|
||||
then
|
||||
# Restore Nuget packages for all builds
|
||||
echo "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Compression/SabreTools.Compression.csproj --output $BUILD_FOLDER
|
||||
fi
|
||||
26
publish-win.ps1
Normal file
26
publish-win.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoBuild")]
|
||||
[switch]$NO_BUILD
|
||||
)
|
||||
|
||||
# Set the current directory as a variable
|
||||
$BUILD_FOLDER = $PSScriptRoot
|
||||
|
||||
# Only build if requested
|
||||
if (!$NO_BUILD.IsPresent)
|
||||
{
|
||||
# Restore Nuget packages for all builds
|
||||
Write-Host "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Compression\SabreTools.Compression.csproj --output $BUILD_FOLDER
|
||||
}
|
||||
Reference in New Issue
Block a user