35 Commits
0.3.1 ... 0.5.2

Author SHA1 Message Date
Matt Nadareski
fa5084d067 Bump version 2024-10-01 13:43:37 -04:00
Matt Nadareski
d9e1748ec1 Update packages 2024-10-01 13:43:02 -04:00
Matt Nadareski
84a3e515c0 Update library versions 2024-09-25 10:52:34 -04:00
Matt Nadareski
e217f4109c Bump version 2024-04-26 21:42:53 -04:00
Matt Nadareski
882f2c5335 Update SabreTools.IO 2024-04-26 21:41:13 -04:00
Matt Nadareski
3f6c7dc0d6 Bump version 2024-04-24 10:11:31 -04:00
Matt Nadareski
bf413cbb85 Update SabreTools.Models 2024-04-24 10:08:00 -04:00
Matt Nadareski
7a403cf368 Make Linux publish script executable 2024-04-24 01:34:40 -04:00
Matt Nadareski
aec4611d14 Add publish scripts 2024-04-24 01:33:42 -04:00
Matt Nadareski
7052584cea Add incomplete compressions to table 2024-04-24 01:30:47 -04:00
Matt Nadareski
efe6c545b9 Add a couple of tags 2024-04-24 01:29:30 -04:00
Matt Nadareski
612a8b3c83 Use constants that were defined 2024-04-24 00:50:35 -04:00
Matt Nadareski
bc06cb5bdb Port extension code for zlib constant names from UnshieldSharp 2024-04-24 00:49:34 -04:00
Matt Nadareski
ae223a4589 Port Blast code from UnshieldSharp 2024-04-24 00:47:31 -04:00
Matt Nadareski
018fd01922 Port zlibConst from UnshieldSharp 2024-04-24 00:35:12 -04:00
Matt Nadareski
910b01b072 Update packages 2024-04-23 21:07:02 -04:00
Matt Nadareski
d239d9f09b Bump version 2024-04-18 12:08:56 -04:00
Matt Nadareski
0cf3e3e816 Update SabreTools.IO 2024-04-18 12:05:30 -04:00
Matt Nadareski
fe319b71f1 Bump version 2024-04-16 22:10:41 -04:00
Matt Nadareski
cd5bf99f21 File formatting changes 2024-04-16 22:09:11 -04:00
Matt Nadareski
f79b5353f7 Add empty test project 2024-04-16 22:06:54 -04:00
Matt Nadareski
5e184b03c5 Move project files to subfolder 2024-04-16 22:02:51 -04:00
Matt Nadareski
a1417d2f8a Minor syntax cleanup 2024-04-16 22:00:58 -04:00
Matt Nadareski
7580d49830 Migrate to ReadOnlyBitStream 2024-04-16 21:57:31 -04:00
Matt Nadareski
c04c7d438d Update SabreTools.IO 2024-04-16 21:55:02 -04:00
Matt Nadareski
a0f602ed6f Bump version the correct way 2024-04-16 12:07:15 -04:00
Matt Nadareski
4bc7f53c9f Bump version 2024-04-16 12:05:46 -04:00
Matt Nadareski
049a8cf499 Minor fixes and tweaks 2024-04-16 12:04:58 -04:00
Matt Nadareski
9a8875f1e0 Update SabreTools.IO 2024-04-16 12:03:05 -04:00
Matt Nadareski
a916cb9954 Bump version 2024-04-03 22:51:17 -04:00
Matt Nadareski
a6ef762a73 Update SabreTools.Models 2024-04-03 22:49:32 -04:00
Matt Nadareski
7aac1e0bed Bump version 2024-04-02 16:08:42 -04:00
Matt Nadareski
bc569964d8 Update packages 2024-04-02 16:08:17 -04:00
Matt Nadareski
a3ac98a9f4 Bump version 2024-03-24 23:00:41 -04:00
Matt Nadareski
57c0f9b747 Import zlib port, with apologies to Nanook 2024-03-24 22:51:55 -04:00
29 changed files with 8738 additions and 298 deletions

View File

@@ -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;
}
}
}
}

View File

@@ -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 |

View File

@@ -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

View 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;
}
}
}

View 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;
}
}

View 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;
}
};
}

View 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;
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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
};
];
}
}

View File

@@ -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);

View File

@@ -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>

View 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;
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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.

View 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.

View 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();
}
}
}

View 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();
}
}
}

File diff suppressed because one or more lines are too long

View 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
View 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
View 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
View 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
View 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
}