91 Commits
0.1.0 ... 0.4.2

Author SHA1 Message Date
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
Matt Nadareski
38f3ea1c98 Bump version 2024-03-12 16:48:36 -04:00
Matt Nadareski
ab36802840 Update packages 2024-03-12 16:34:07 -04:00
Matt Nadareski
422bda1830 Update SabreTools.IO 2024-03-05 11:11:07 -05:00
Matt Nadareski
1c989985d9 Update copyright date 2024-02-27 19:08:56 -05:00
Matt Nadareski
62c6e79ad3 Add nuget package and PR workflows 2024-02-27 19:08:42 -05:00
Matt Nadareski
6bbf521828 Fix repository URL 2023-11-22 09:40:32 -05:00
Matt Nadareski
7a4e2f0ee0 Bump version 2023-11-22 09:39:29 -05:00
Matt Nadareski
1d1a6f5976 Support .NET Framework 2.0 2023-11-22 09:39:18 -05:00
Matt Nadareski
065b68124b Update SabreTools libraries 2023-11-22 09:36:36 -05:00
Matt Nadareski
07b50e8c46 Support ancient .NET 2023-11-14 14:53:50 -05:00
Matt Nadareski
8eb82384d6 Expand supported RIDs 2023-11-08 22:51:25 -05:00
Matt Nadareski
dd6cc0e2f3 Fix whitespace in project file 2023-11-08 10:59:30 -05:00
Matt Nadareski
58502e0362 Enable latest language version 2023-11-07 22:12:51 -05:00
Matt Nadareski
f3bf1082d3 Update Models version 2023-10-25 15:46:02 -04:00
Matt Nadareski
7958b24a36 Update Models version 2023-09-28 23:30:07 -04:00
Matt Nadareski
3010a0523c Use local constants until Models updated 2023-09-23 11:16:11 -04:00
Matt Nadareski
d7670ae685 Add a little more Quantum decoding 2023-09-23 00:32:08 -04:00
Matt Nadareski
2d09d9696a Add source position and length to BitStream 2023-09-23 00:26:52 -04:00
Matt Nadareski
d75883a6cf Start adding Quantum decompression 2023-09-23 00:13:08 -04:00
Matt Nadareski
8e5cf3ee2e Add byte array constructors 2023-09-22 23:52:14 -04:00
Matt Nadareski
e739fd6fd5 Create Process method, make it throw for now 2023-09-22 23:41:06 -04:00
Matt Nadareski
6b238df5dc Address some nullability warnings 2023-09-22 23:38:05 -04:00
Matt Nadareski
3e3a0e122b Add selector selector model 2023-09-22 23:26:07 -04:00
Matt Nadareski
cb6e157cb4 Fix one more location 2023-09-22 23:25:00 -04:00
Matt Nadareski
12466d7083 Fix build after recasting 2023-09-22 23:24:03 -04:00
Matt Nadareski
47cb06cf34 Add notes and init for coding state 2023-09-22 23:20:03 -04:00
Matt Nadareski
c152cba81d Add internal models and constructor 2023-09-22 23:17:14 -04:00
Matt Nadareski
4684a6612c Finish update implementation 2023-09-22 22:58:43 -04:00
Matt Nadareski
a58da1d8db Add one more partial method 2023-09-22 22:07:38 -04:00
Matt Nadareski
8f098a6669 Add some helper Quantum methods 2023-09-22 21:59:15 -04:00
Matt Nadareski
8c5482a59a Make things more model-based 2023-09-22 16:57:36 -04:00
Matt Nadareski
b1f1863e9a Fix build issues from package update 2023-09-22 16:47:14 -04:00
Matt Nadareski
8ab555d6fc Add new package tag 2023-09-22 16:18:51 -04:00
Matt Nadareski
32b2f6c443 Update Models version 2023-09-22 16:06:17 -04:00
Matt Nadareski
44f1544725 Fix nullability warnings 2023-09-22 15:58:06 -04:00
Matt Nadareski
471cbc5707 Fix build 2023-09-22 15:46:56 -04:00
Matt Nadareski
5b785fb28f Use constants from Models 2023-09-22 15:46:45 -04:00
Matt Nadareski
38dd2a5caf Update README 2023-09-22 14:50:08 -04:00
Matt Nadareski
5e21a09fd1 Make header reading consistent 2023-09-22 11:59:46 -04:00
Matt Nadareski
8174af616f Handle model issues, combine logic 2023-09-22 11:53:27 -04:00
Matt Nadareski
297fffe8d7 Add RFC1951 implementation 2023-09-22 11:27:07 -04:00
Matt Nadareski
bd9258d9fa Fix build 2023-09-21 23:43:48 -04:00
Matt Nadareski
b7a081824c Create a separate Huffman decoder 2023-09-21 23:43:32 -04:00
Matt Nadareski
9617e5c583 Simplify BitStream implementation 2023-09-21 23:18:43 -04:00
Matt Nadareski
ec40e759a9 Start clean MSZIP implementation (nw) 2023-09-21 22:59:46 -04:00
Matt Nadareski
15bf2001b5 Add multiple byte reading 2023-09-21 22:27:19 -04:00
Matt Nadareski
81eab984fb Add endian reads for bits 2023-09-21 22:20:57 -04:00
Matt Nadareski
47691d2034 Add shortcut implementations for BitStream 2023-09-21 22:10:19 -04:00
Matt Nadareski
dde90a852d Fix BitStream returns 2023-09-21 22:08:22 -04:00
Matt Nadareski
2ce175af39 Add BitStream skeleton 2023-09-21 22:06:42 -04:00
Matt Nadareski
3353264090 Update README 2023-09-21 21:31:59 -04:00
Matt Nadareski
5477afaf1e Remove partially ported libmspack code 2023-09-21 21:31:45 -04:00
Matt Nadareski
9229e1b5f7 Remove .old implementations 2023-09-21 21:26:40 -04:00
Matt Nadareski
82223f3ee4 Checkpoint (nw) 2023-09-20 14:26:09 -04:00
Matt Nadareski
a69d3a5bb2 Checkpoint (nw) 2023-09-20 11:50:12 -04:00
Matt Nadareski
67d49ac4c0 Checkpoint (nw) 2023-09-20 11:19:05 -04:00
Matt Nadareski
568d6f9e72 Checkpoint (nw) 2023-09-20 10:55:16 -04:00
Matt Nadareski
d014d57750 Checkpoint (nw) 2023-09-20 00:40:17 -04:00
Matt Nadareski
c71f73b109 Checkpoint (nw) 2023-09-20 00:11:58 -04:00
Matt Nadareski
d6d8b2d9de Checkpoint (nw) 2023-09-19 23:18:00 -04:00
Matt Nadareski
8227d9637c Checkpoint (nw) 2023-09-19 21:53:21 -04:00
Matt Nadareski
a7cfb47dbe Checkpoint (nw) 2023-09-19 17:12:11 -04:00
Matt Nadareski
f23078d792 Add everything from oab.c (nw) 2023-09-19 16:03:14 -04:00
Matt Nadareski
3f4de3ee67 Add everything from szddd.c 2023-09-19 15:34:32 -04:00
Matt Nadareski
9d9f03c283 Add everything from szddc.c 2023-09-19 12:40:26 -04:00
Matt Nadareski
b4650010e0 Add everything from qtm.h 2023-09-19 12:37:42 -04:00
Matt Nadareski
a3dae1b5e4 Add everything from oabc.c 2023-09-19 12:20:36 -04:00
Matt Nadareski
82ea2ca03c Add everything from oab.h 2023-09-19 12:19:42 -04:00
Matt Nadareski
0eb17b9e26 Add everything from mszip.h 2023-09-19 12:17:19 -04:00
Matt Nadareski
2413b5feeb Add everything from lzx.h 2023-09-19 11:36:47 -04:00
Matt Nadareski
48f8397c81 Add everything from lzss.h 2023-09-19 11:04:23 -04:00
Matt Nadareski
c77b7c0a5b Add everything from lit/litc/litd 2023-09-19 10:58:04 -04:00
Matt Nadareski
798eb64daa Add everything from kwajc.c 2023-09-19 10:56:04 -04:00
Matt Nadareski
dc467def95 Add everything from kwaj.h 2023-09-19 10:54:45 -04:00
Matt Nadareski
6dbe6dbcbc Add everything from hlpc/hlpd 2023-09-19 10:38:00 -04:00
Matt Nadareski
0f140d6a1f Add everything from hlp.h 2023-09-19 10:35:32 -04:00
Matt Nadareski
fe2ea2b102 Add everything from chm.h 2023-09-19 10:34:44 -04:00
Matt Nadareski
3fd69dc1b2 Add everything from chm.c 2023-09-19 01:44:34 -04:00
Matt Nadareski
472777a1cf Add everything from cabc.c 2023-09-19 01:43:54 -04:00
Matt Nadareski
f47434bc3b Add everything from cab.h 2023-09-19 01:33:34 -04:00
Matt Nadareski
04af8d3f79 Add everything from system 2023-09-19 01:14:53 -04:00
Matt Nadareski
327a97e523 Add everything from szdd.h 2023-09-19 00:38:38 -04:00
Matt Nadareski
80bd06f9e3 Add everything from crc32 2023-09-19 00:35:52 -04:00
Matt Nadareski
b561385b58 Add everything from mspack.h 2023-09-19 00:30:31 -04:00
Matt Nadareski
3e343211aa Rename original implementations to .old 2023-09-18 16:32:12 -04:00
27 changed files with 9161 additions and 2260 deletions

43
.github/workflows/build_nupkg.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Nuget Pack
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Pack
run: dotnet pack
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: 'Nuget Package'
path: 'bin/Release/*.nupkg'
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'bin/Release/*.nupkg'
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

17
.github/workflows/check_pr.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build

238
BitStream.cs Normal file
View File

@@ -0,0 +1,238 @@
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

@@ -17,11 +17,7 @@ namespace SabreTools.Compression.LZ
/// </summary>
/// <param name="compressed">Byte array representing the compressed data</param>
/// <returns>Decompressed data as a byte array, null on error</returns>
#if NET48
public static byte[] Decompress(byte[] compressed)
#else
public static byte[]? Decompress(byte[]? compressed)
#endif
{
// If we have and invalid input
if (compressed == null || compressed.Length == 0)
@@ -37,11 +33,7 @@ namespace SabreTools.Compression.LZ
/// </summary>
/// <param name="compressed">Stream representing the compressed data</param>
/// <returns>Decompressed data as a byte array, null on error</returns>
#if NET48
public static byte[] Decompress(Stream compressed)
#else
public static byte[]? Decompress(Stream? compressed)
#endif
{
// If we have and invalid input
if (compressed == null || compressed.Length == 0)
@@ -87,11 +79,7 @@ namespace SabreTools.Compression.LZ
/// <summary>
/// Reconstructs the full filename of the compressed file
/// </summary>
#if NET48
public static string GetExpandedName(string input, out LZERROR error)
#else
public static string? GetExpandedName(string input, out LZERROR error)
#endif
{
// Try to open the file as a compressed stream
var fileStream = File.Open(input, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
@@ -103,7 +91,7 @@ namespace SabreTools.Compression.LZ
string inputExtension = Path.GetExtension(input).TrimStart('.');
// If we have no extension
if (string.IsNullOrWhiteSpace(inputExtension))
if (string.IsNullOrEmpty(inputExtension))
return Path.GetFileNameWithoutExtension(input);
// If we have an extension of length 1
@@ -137,11 +125,7 @@ namespace SabreTools.Compression.LZ
/// <param name="error">Output representing the last error</param>
/// <returns>An initialized State, null on error</returns>
/// <remarks>Uncompressed streams are represented by a State with no buffer</remarks>
#if NET48
public State Open(Stream stream, out LZERROR error)
#else
public State? Open(Stream stream, out LZERROR error)
#endif
{
var lzs = Init(stream, out error);
if (error == LZERROR.LZERROR_OK || error == LZERROR.LZERROR_NOT_LZ)
@@ -170,11 +154,7 @@ namespace SabreTools.Compression.LZ
/// <param name="error">Output representing the last error</param>
/// <returns>An initialized State, null on error</returns>
/// <remarks>Uncompressed streams are represented by a State with no buffer</remarks>
#if NET48
public State Init(Stream source, out LZERROR error)
#else
public State? Init(Stream? source, out LZERROR error)
#endif
{
// If we have an invalid source
if (source == null)
@@ -540,11 +520,7 @@ namespace SabreTools.Compression.LZ
/// <param name="data">Stream to parse</param>
/// <param name="error">Output representing the last error</param>
/// <returns>Filled file header on success, null on error</returns>
#if NET48
private FileHeaader ParseFileHeader(Stream data, out LZERROR error)
#else
private FileHeaader? ParseFileHeader(Stream data, out LZERROR error)
#endif
{
error = LZERROR.LZERROR_OK;
var fileHeader = new FileHeaader();
@@ -576,6 +552,6 @@ namespace SabreTools.Compression.LZ
return fileHeader;
}
#endregion
#endregion
}
}

View File

@@ -1,12 +0,0 @@
namespace SabreTools.Compression.LZX
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
internal class Bits
{
public uint BitBuffer;
public int BitsLeft;
public int InputPosition; //byte*
}
}

View File

@@ -1,759 +0,0 @@
using System;
using SabreTools.Compression.LZX;
using static SabreTools.Models.Compression.LZX.Constants;
using static SabreTools.Models.MicrosoftCabinet.Constants;
namespace SabreTools.Compression.LZX
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/fdi.c"/>
internal class Decompressor
{
/// <summary>
/// Initialize an LZX decompressor state
/// </summary>
public static bool Init(int window, State state)
{
uint wndsize = (uint)(1 << window);
int posn_slots;
/* LZX supports window sizes of 2^15 (32Kb) through 2^21 (2Mb) */
/* if a previously allocated window is big enough, keep it */
if (window < 15 || window > 21)
return false;
if (state.actual_size < wndsize)
state.window = null;
if (state.window == null)
{
state.window = new byte[wndsize];
state.actual_size = wndsize;
}
state.window_size = wndsize;
/* calculate required position slots */
if (window == 20) posn_slots = 42;
else if (window == 21) posn_slots = 50;
else posn_slots = window << 1;
/*posn_slots=i=0; while (i < wndsize) i += 1 << CAB(extra_bits)[posn_slots++]; */
state.R0 = state.R1 = state.R2 = 1;
state.main_elements = (ushort)(LZX_NUM_CHARS + (posn_slots << 3));
state.header_read = 0;
state.frames_read = 0;
state.block_remaining = 0;
state.block_type = LZX_BLOCKTYPE_INVALID;
state.intel_curpos = 0;
state.intel_started = 0;
state.window_posn = 0;
/* initialize tables to 0 (because deltas will be applied to them) */
// memset(state.MAINTREE_len, 0, sizeof(state.MAINTREE_len));
// memset(state.LENGTH_len, 0, sizeof(state.LENGTH_len));
return true;
}
/// <summary>
/// Decompress a byte array using a given State
/// </summary>
public static bool Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
{
int inpos = 0; // inbuf[0];
int endinp = inpos + inlen;
int window = 0; // state.window[0];
int runsrc, rundest; // byte*
uint window_posn = state.window_posn;
uint window_size = state.window_size;
uint R0 = state.R0;
uint R1 = state.R1;
uint R2 = state.R2;
uint match_offset, i, j, k; /* ijk used in READ_HUFFSYM macro */
Bits lb = new Bits(); /* used in READ_LENGTHS macro */
int togo = outlen, this_run, main_element, aligned_bits;
int match_length, copy_length, length_footer, extra, verbatim_bits;
INIT_BITSTREAM(out int bitsleft, out uint bitbuf);
/* read header if necessary */
if (state.header_read == 0)
{
i = j = 0;
k = READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf);
if (k != 0)
{
i = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
j = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
}
state.intel_filesize = (int)((i << 16) | j); /* or 0 if not encoded */
state.header_read = 1;
}
/* main decoding loop */
while (togo > 0)
{
/* last block finished, new block expected */
if (state.block_remaining == 0)
{
if (state.block_type == LZX_BLOCKTYPE_UNCOMPRESSED)
{
if ((state.block_length & 1) != 0)
inpos++; /* realign bitstream to word */
INIT_BITSTREAM(out bitsleft, out bitbuf);
}
state.block_type = (ushort)READ_BITS(3, inbuf, ref inpos, ref bitsleft, ref bitbuf);
i = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
j = READ_BITS(8, inbuf, ref inpos, ref bitsleft, ref bitbuf);
state.block_remaining = state.block_length = (i << 8) | j;
switch (state.block_type)
{
case LZX_BLOCKTYPE_ALIGNED:
for (i = 0; i < 8; i++)
{
j = READ_BITS(3, inbuf, ref inpos, ref bitsleft, ref bitbuf);
state.tblALIGNED_len[i] = (byte)j;
}
make_decode_table(LZX_ALIGNED_MAXSYMBOLS, LZX_ALIGNED_TABLEBITS, state.tblALIGNED_len, state.tblALIGNED_table);
/* rest of aligned header is same as verbatim */
goto case LZX_BLOCKTYPE_VERBATIM;
case LZX_BLOCKTYPE_VERBATIM:
READ_LENGTHS(state.tblMAINTREE_len, 0, 256, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
READ_LENGTHS(state.tblMAINTREE_len, 256, state.main_elements, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
make_decode_table(LZX_MAINTREE_MAXSYMBOLS, LZX_MAINTREE_TABLEBITS, state.tblMAINTREE_len, state.tblMAINTREE_table);
if (state.tblMAINTREE_len[0xE8] != 0)
state.intel_started = 1;
READ_LENGTHS(state.tblLENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
make_decode_table(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, state.tblLENGTH_len, state.tblLENGTH_table);
break;
case LZX_BLOCKTYPE_UNCOMPRESSED:
state.intel_started = 1; /* because we can't assume otherwise */
ENSURE_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf); /* get up to 16 pad bits into the buffer */
/* and align the bitstream! */
if (bitsleft > 16)
inpos -= 2;
R0 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
R1 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
R2 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
break;
default:
return false;
}
}
/* buffer exhaustion check */
if (inpos > endinp)
{
/* it's possible to have a file where the next run is less than
* 16 bits in size. In this case, the READ_HUFFSYM() macro used
* in building the tables will exhaust the buffer, so we should
* allow for this, but not allow those accidentally read bits to
* be used (so we check that there are at least 16 bits
* remaining - in this boundary case they aren't really part of
* the compressed data)
*/
if (inpos > (endinp + 2) || bitsleft < 16)
return false;
}
while ((this_run = (int)state.block_remaining) > 0 && togo > 0)
{
if (this_run > togo) this_run = togo;
togo -= this_run;
state.block_remaining -= (uint)this_run;
/* apply 2^x-1 mask */
window_posn &= window_size - 1;
/* runs can't straddle the window wraparound */
if ((window_posn + this_run) > window_size)
return false;
switch (state.block_type)
{
case LZX_BLOCKTYPE_VERBATIM:
while (this_run > 0)
{
main_element = READ_HUFFSYM(state.tblMAINTREE_table, state.tblMAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
if (main_element < LZX_NUM_CHARS)
{
/* literal: 0 to LZX_NUM_CHARS-1 */
state.window[window + window_posn++] = (byte)main_element;
this_run--;
}
else
{
/* match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
main_element -= LZX_NUM_CHARS;
match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;
if (match_length == LZX_NUM_PRIMARY_LENGTHS)
{
length_footer = READ_HUFFSYM(state.tblLENGTH_table, state.tblLENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_length += length_footer;
}
match_length += LZX_MIN_MATCH;
match_offset = (uint)(main_element >> 3);
if (match_offset > 2)
{
/* not repeated offset */
if (match_offset != 3)
{
extra = state.ExtraBits[match_offset];
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_offset = (uint)(state.PositionSlotBases[match_offset] - 2 + verbatim_bits);
}
else
{
match_offset = 1;
}
/* update repeated offset LRU queue */
R2 = R1; R1 = R0; R0 = match_offset;
}
else if (match_offset == 0)
{
match_offset = R0;
}
else if (match_offset == 1)
{
match_offset = R1;
R1 = R0; R0 = match_offset;
}
else /* match_offset == 2 */
{
match_offset = R2;
R2 = R0; R0 = match_offset;
}
rundest = (int)(window + window_posn);
this_run -= match_length;
/* copy any wrapped around source data */
if (window_posn >= match_offset)
{
/* no wrap */
runsrc = (int)(rundest - match_offset);
}
else
{
runsrc = (int)(rundest + (window_size - match_offset));
copy_length = (int)(match_offset - window_posn);
if (copy_length < match_length)
{
match_length -= copy_length;
window_posn += (uint)copy_length;
while (copy_length-- > 0)
{
state.window[rundest++] = state.window[runsrc++];
}
runsrc = window;
}
}
window_posn += (uint)match_length;
/* copy match data - no worries about destination wraps */
while (match_length-- > 0)
{
state.window[rundest++] = state.window[runsrc++];
}
}
}
break;
case LZX_BLOCKTYPE_ALIGNED:
while (this_run > 0)
{
main_element = READ_HUFFSYM(state.tblMAINTREE_table, state.tblMAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
if (main_element < LZX_NUM_CHARS)
{
/* literal: 0 to LZX_NUM_CHARS-1 */
state.window[window + window_posn++] = (byte)main_element;
this_run--;
}
else
{
/* mverbatim_bitsatch: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
main_element -= LZX_NUM_CHARS;
match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;
if (match_length == LZX_NUM_PRIMARY_LENGTHS)
{
length_footer = READ_HUFFSYM(state.tblLENGTH_table, state.tblLENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_length += length_footer;
}
match_length += LZX_MIN_MATCH;
match_offset = (uint)(main_element >> 3);
if (match_offset > 2)
{
/* not repeated offset */
extra = state.ExtraBits[match_offset];
match_offset = state.PositionSlotBases[match_offset] - 2;
if (extra > 3)
{
/* verbatim and aligned bits */
extra -= 3;
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_offset += (uint)(verbatim_bits << 3);
aligned_bits = READ_HUFFSYM(state.tblALIGNED_table, state.tblALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_offset += (uint)aligned_bits;
}
else if (extra == 3)
{
/* aligned bits only */
aligned_bits = READ_HUFFSYM(state.tblALIGNED_table, state.tblALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_offset += (uint)aligned_bits;
}
else if (extra > 0)
{
/* extra==1, extra==2 */
/* verbatim bits only */
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
match_offset += (uint)verbatim_bits;
}
else /* extra == 0 */
{
/* ??? */
match_offset = 1;
}
/* update repeated offset LRU queue */
R2 = R1; R1 = R0; R0 = match_offset;
}
else if (match_offset == 0)
{
match_offset = R0;
}
else if (match_offset == 1)
{
match_offset = R1;
R1 = R0; R0 = match_offset;
}
else /* match_offset == 2 */
{
match_offset = R2;
R2 = R0; R0 = match_offset;
}
rundest = (int)(window + window_posn);
this_run -= match_length;
/* copy any wrapped around source data */
if (window_posn >= match_offset)
{
/* no wrap */
runsrc = (int)(rundest - match_offset);
}
else
{
runsrc = (int)(rundest + (window_size - match_offset));
copy_length = (int)(match_offset - window_posn);
if (copy_length < match_length)
{
match_length -= copy_length;
window_posn += (uint)copy_length;
while (copy_length-- > 0)
{
state.window[rundest++] = state.window[runsrc++];
}
runsrc = window;
}
}
window_posn += (uint)match_length;
/* copy match data - no worries about destination wraps */
while (match_length-- > 0)
{
state.window[rundest++] = state.window[runsrc++];
}
}
}
break;
case LZX_BLOCKTYPE_UNCOMPRESSED:
if ((inpos + this_run) > endinp)
return false;
Array.Copy(inbuf, inpos, state.window, window + window_posn, this_run);
inpos += this_run;
window_posn += (uint)this_run;
break;
default:
return false; /* might as well */
}
}
}
if (togo != 0)
return false;
Array.Copy(state.window, window + ((window_posn == 0) ? window_size : window_posn) - outlen, outbuf, 0, outlen);
state.window_posn = window_posn;
state.R0 = R0;
state.R1 = R1;
state.R2 = R2;
/* intel E8 decoding */
if ((state.frames_read++ < 32768) && state.intel_filesize != 0)
{
if (outlen <= 6 || state.intel_started == 0)
{
state.intel_curpos += outlen;
}
else
{
int data = 0; // outbuf[0];
int dataend = data + outlen - 10;
int curpos = state.intel_curpos;
int filesize = state.intel_filesize;
int abs_off, rel_off;
state.intel_curpos = curpos + outlen;
while (data < dataend)
{
if (outbuf[data++] != 0xE8)
{
curpos++;
continue;
}
abs_off = outbuf[data + 0] | (outbuf[data + 1] << 8) | (outbuf[data + 2] << 16) | (outbuf[data + 3] << 24);
if ((abs_off >= -curpos) && (abs_off < filesize))
{
rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;
outbuf[data + 0] = (byte)rel_off;
outbuf[data + 1] = (byte)(rel_off >> 8);
outbuf[data + 2] = (byte)(rel_off >> 16);
outbuf[data + 3] = (byte)(rel_off >> 24);
}
data += 4;
curpos += 5;
}
}
}
return true;
}
/// <summary>
/// Read and build the Huffman tree from the lengths
/// </summary>
private static int ReadLengths(byte[] lengths, uint first, uint last, Bits lb, State state, byte[] inbuf)
{
uint x, y;
uint bitbuf = lb.BitBuffer;
int bitsleft = lb.BitsLeft;
int inpos = lb.InputPosition;
for (x = 0; x < 20; x++)
{
y = READ_BITS(4, inbuf, ref inpos, ref bitsleft, ref bitbuf);
state.tblPRETREE_len[x] = (byte)y;
}
make_decode_table(LZX_PRETREE_MAXSYMBOLS, LZX_PRETREE_TABLEBITS, state.tblPRETREE_len, state.tblPRETREE_table);
for (x = first; x < last;)
{
int z = READ_HUFFSYM(state.tblPRETREE_table, state.tblPRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
if (z == 17)
{
y = READ_BITS(4, inbuf, ref inpos, ref bitsleft, ref bitbuf);
y += 4;
while (y-- > 0)
{
lengths[x++] = 0;
}
}
else if (z == 18)
{
y = READ_BITS(5, inbuf, ref inpos, ref bitsleft, ref bitbuf);
y += 20;
while (y-- > 0)
{
lengths[x++] = 0;
}
}
else if (z == 19)
{
y = READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf);
y += 4;
z = READ_HUFFSYM(state.tblPRETREE_table, state.tblPRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
z = lengths[x] - z;
if (z < 0)
z += 17;
while (y-- > 0)
{
lengths[x++] = (byte)z;
}
}
else
{
z = lengths[x] - z;
if (z < 0)
z += 17;
lengths[x++] = (byte)z;
}
}
lb.BitBuffer = bitbuf;
lb.BitsLeft = bitsleft;
lb.InputPosition = inpos;
return 0;
}
// Bitstream reading macros (LZX / intel little-endian byte order)
#region Bitstream Reading Macros
/*
* These bit access routines work by using the area beyond the MSB and the
* LSB as a free source of zeroes. This avoids having to mask any bits.
* So we have to know the bit width of the bitbuffer variable.
*/
/// <summary>
/// Should be used first to set up the system
/// </summary>
private static void INIT_BITSTREAM(out int bitsleft, out uint bitbuf)
{
bitsleft = 0;
bitbuf = 0;
}
/// <summary>
/// Ensures there are at least N bits in the bit buffer. It can guarantee
// up to 17 bits (i.e. it can read in 16 new bits when there is down to
/// 1 bit in the buffer, and it can read 32 bits when there are 0 bits in
/// the buffer).
/// </summary>
/// <remarks>Quantum reads bytes in normal order; LZX is little-endian order</remarks>
private static void ENSURE_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
while (bitsleft < n)
{
byte b0 = inpos + 0 < inbuf.Length ? inbuf[inpos + 0] : (byte)0;
byte b1 = inpos + 1 < inbuf.Length ? inbuf[inpos + 1] : (byte)0;
bitbuf |= (uint)(((b1 << 8) | b0) << (16 - bitsleft));
bitsleft += 16;
inpos += 2;
}
}
/// <summary>
/// Extracts (without removing) N bits from the bit buffer
/// </summary>
private static uint PEEK_BITS(int n, uint bitbuf)
{
return bitbuf >> (32 - n);
}
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
private static void REMOVE_BITS(int n, ref int bitsleft, ref uint bitbuf)
{
bitbuf <<= n;
bitsleft -= n;
}
/// <summary>
/// Takes N bits from the buffer and puts them in v.
/// </summary>
private static uint READ_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
uint v = 0;
if (n > 0)
{
ENSURE_BITS(n, inbuf, ref inpos, ref bitsleft, ref bitbuf);
v = PEEK_BITS(n, bitbuf);
REMOVE_BITS(n, ref bitsleft, ref bitbuf);
}
return v;
}
#endregion
#region Huffman Methods
/// <summary>
/// This function was coded by David Tritscher. It builds a fast huffman
/// decoding table out of just a canonical huffman code lengths table.
/// </summary>
/// <param name="nsyms">Total number of symbols in this huffman tree.</param>
/// <param name="nbits">
/// Any symbols with a code length of nbits or less can be decoded
/// in one lookup of the table.
/// </param>
/// <param name="length">A table to get code lengths from [0 to syms-1]</param>
/// <param name="table">The table to fill up with decoded symbols and pointers.</param>
/// <returns>
/// OK: 0
/// error: 1
/// </returns>
private static int make_decode_table(uint nsyms, uint nbits, byte[] length, ushort[] table)
{
ushort sym;
uint leaf;
byte bit_num = 1;
uint fill;
uint pos = 0; /* the current position in the decode table */
uint table_mask = (uint)(1 << (int)nbits);
uint bit_mask = table_mask >> 1; /* don't do 0 length codes */
uint next_symbol = bit_mask; /* base of allocation for long codes */
/* fill entries for codes short enough for a direct mapping */
while (bit_num <= nbits)
{
for (sym = 0; sym < nsyms; sym++)
{
if (length[sym] == bit_num)
{
leaf = pos;
if ((pos += bit_mask) > table_mask) return 1; /* table overrun */
/* fill all possible lookups of this symbol with the symbol itself */
fill = bit_mask;
while (fill-- > 0) table[leaf++] = sym;
}
}
bit_mask >>= 1;
bit_num++;
}
/* if there are any codes longer than nbits */
if (pos != table_mask)
{
/* clear the remainder of the table */
for (sym = (ushort)pos; sym < table_mask; sym++) table[sym] = 0;
/* give ourselves room for codes to grow by up to 16 more bits */
pos <<= 16;
table_mask <<= 16;
bit_mask = 1 << 15;
while (bit_num <= 16)
{
for (sym = 0; sym < nsyms; sym++)
{
if (length[sym] == bit_num)
{
leaf = pos >> 16;
for (fill = 0; fill < bit_num - nbits; fill++)
{
/* if this path hasn't been taken yet, 'allocate' two entries */
if (table[leaf] == 0)
{
table[(next_symbol << 1)] = 0;
table[(next_symbol << 1) + 1] = 0;
table[leaf] = (ushort)next_symbol++;
}
/* follow the path and select either left or right for next bit */
leaf = (uint)(table[leaf] << 1);
if (((pos >> (int)(15 - fill)) & 1) != 0) leaf++;
}
table[leaf] = sym;
if ((pos += bit_mask) > table_mask) return 1; /* table overflow */
}
}
bit_mask >>= 1;
bit_num++;
}
}
/* full table? */
if (pos == table_mask) return 0;
/* either erroneous table, or all elements are 0 - let's find out. */
for (sym = 0; sym < nsyms; sym++) if (length[sym] != 0) return 1;
return 0;
}
#endregion
// Huffman macros
#region Huffman Macros
/// <summary>
/// Decodes one huffman symbol from the bitstream using the stated table and
/// puts it in v.
/// </summary>
private static int READ_HUFFSYM(ushort[] hufftbl, byte[] lentable, int tablebits, int maxsymbols, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
int v = 0, i, j = 0;
ENSURE_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
if ((i = hufftbl[PEEK_BITS(tablebits, bitbuf)]) >= maxsymbols)
{
j = 1 << (32 - tablebits);
do
{
j >>= 1;
i <<= 1;
i |= (bitbuf & j) != 0 ? 1 : 0;
if (j == 0)
throw new System.Exception();
} while ((i = hufftbl[i]) >= maxsymbols);
}
j = lentable[v = i];
REMOVE_BITS(j, ref bitsleft, ref bitbuf);
return v;
}
/// <summary>
/// Reads in code lengths for symbols first to last in the given table. The
/// code lengths are stored in their own special LZX way.
/// </summary>
private static bool READ_LENGTHS(byte[] lentable, uint first, uint last, Bits lb, State state, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
lb.BitBuffer = bitbuf;
lb.BitsLeft = bitsleft;
lb.InputPosition = inpos;
if (ReadLengths(lentable, first, last, lb, state, inbuf) != 0)
return false;
bitbuf = lb.BitBuffer;
bitsleft = lb.BitsLeft;
inpos = lb.InputPosition;
return true;
}
#endregion
}
}

View File

@@ -1,119 +0,0 @@
using static SabreTools.Models.Compression.LZX.Constants;
namespace SabreTools.Compression.LZX
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
internal class State
{
/// <summary>
/// the actual decoding window
/// </summary>
public byte[] window;
/// <summary>
/// window size (32Kb through 2Mb)
/// </summary>
public uint window_size;
/// <summary>
/// window size when it was first allocated
/// </summary>
public uint actual_size;
/// <summary>
/// current offset within the window
/// </summary>
public uint window_posn;
/// <summary>
/// for the LRU offset system
/// </summary>
public uint R0, R1, R2;
/// <summary>
/// number of main tree elements
/// </summary>
public ushort main_elements;
/// <summary>
/// have we started decoding at all yet?
/// </summary>
public int header_read;
/// <summary>
/// type of this block
/// </summary>
public ushort block_type;
/// <summary>
/// uncompressed length of this block
/// </summary>
public uint block_length;
/// <summary>
/// uncompressed bytes still left to decode
/// </summary>
public uint block_remaining;
/// <summary>
/// the number of CFDATA blocks processed
/// </summary>
public uint frames_read;
/// <summary>
/// magic header value used for transform
/// </summary>
public int intel_filesize;
/// <summary>
/// current offset in transform space
/// </summary>
public int intel_curpos;
/// <summary>
/// have we seen any translatable data yet?
/// </summary>
public int intel_started;
public ushort[] tblPRETREE_table = new ushort[(1 << LZX_PRETREE_TABLEBITS) + (LZX_PRETREE_MAXSYMBOLS << 1)];
public byte[] tblPRETREE_len = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
public ushort[] tblMAINTREE_table = new ushort[(1 << LZX_MAINTREE_TABLEBITS) + (LZX_MAINTREE_MAXSYMBOLS << 1)];
public byte[] tblMAINTREE_len = new byte[LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
public ushort[] tblLENGTH_table = new ushort[(1 << LZX_LENGTH_TABLEBITS) + (LZX_LENGTH_MAXSYMBOLS << 1)];
public byte[] tblLENGTH_len = new byte[LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
public ushort[] tblALIGNED_table = new ushort[(1 << LZX_ALIGNED_TABLEBITS) + (LZX_ALIGNED_MAXSYMBOLS << 1)];
public byte[] tblALIGNED_len = new byte[LZX_ALIGNED_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
#region Decompression Tables
/// <summary>
/// An index to the position slot bases
/// </summary>
public uint[] PositionSlotBases = new uint[]
{
0, 1, 2, 3, 4, 6, 8, 12,
16, 24, 32, 48, 64, 96, 128, 192,
256, 384, 512, 768, 1024, 1536, 2048, 3072,
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152,
65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360,
786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
1835008, 1966080, 2097152
};
/// <summary>
/// How many bits of offset-from-base data is needed
/// </summary>
public byte[] ExtraBits = new byte[]
{
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, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17
};
#endregion
}
}

View File

@@ -1,637 +0,0 @@
using System;
using System.Runtime.InteropServices;
using SabreTools.Models.Compression.MSZIP;
using static SabreTools.Models.Compression.MSZIP.Constants;
namespace SabreTools.Compression.MSZIP
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/fdi.c"/>
internal unsafe class Decompressor
{
/// <summary>
/// Decompress a byte array using a given State
/// </summary>
public static bool Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
{
fixed (byte* inpos = inbuf)
{
state.inpos = inpos;
state.bb = state.bk = state.window_posn = 0;
if (outlen > ZIPWSIZE)
return false;
// CK = Chris Kirmse, official Microsoft purloiner
if (state.inpos[0] != 0x43 || state.inpos[1] != 0x4B)
return false;
state.inpos += 2;
int lastBlockFlag = 0;
do
{
if (InflateBlock(&lastBlockFlag, state, inbuf, outbuf) != 0)
return false;
} while (lastBlockFlag == 0);
// Return success
return true;
}
}
/// <summary>
/// Decompress a deflated block
/// </summary>
private static uint InflateBlock(int* e, State state, byte[] inbuf, byte[] outbuf)
{
// Make local bit buffer
uint b = state.bb;
uint k = state.bk;
// Read the deflate block header
var header = new DeflateBlockHeader();
// Read in last block bit
ZIPNEEDBITS(1, state, ref b, ref k);
header.BFINAL = (*e = (int)b & 1) != 0;
ZIPDUMPBITS(1, ref b, ref k);
// Read in block type
ZIPNEEDBITS(2, state, ref b, ref k);
header.BTYPE = (CompressionType)(b & 3);
ZIPDUMPBITS(2, ref b, ref k);
// Restore the global bit buffer
state.bb = b;
state.bk = k;
// Inflate that block type
switch (header.BTYPE)
{
case CompressionType.NoCompression:
return (uint)DecompressStored(state, inbuf, outbuf);
case CompressionType.FixedHuffman:
return (uint)DecompressFixed(state, inbuf, outbuf);
case CompressionType.DynamicHuffman:
return (uint)DecompressDynamic(state, inbuf, outbuf);
// Bad block type
case CompressionType.Reserved:
default:
return 2;
}
}
/// <summary>
/// "Decompress" a stored block
/// </summary>
private static int DecompressStored(State state, byte[] inbuf, byte[] outbuf)
{
// Make local copies of globals
uint b = state.bb;
uint k = state.bk;
uint w = state.window_posn;
// Go to byte boundary
int n = (int)(k & 7);
ZIPDUMPBITS(n, ref b, ref k);
// Read the stored block header
var header = new NonCompressedBlockHeader();
// Get the length and its compliment
ZIPNEEDBITS(16, state, ref b, ref k);
header.LEN = (ushort)(b & 0xffff);
ZIPDUMPBITS(16, ref b, ref k);
ZIPNEEDBITS(16, state, ref b, ref k);
header.NLEN = (ushort)(b & 0xffff);
if (header.LEN != (~header.NLEN & 0xffff))
return 1; // Error in compressed data
ZIPDUMPBITS(16, ref b, ref k);
// Read and output the compressed data
while (n-- > 0)
{
ZIPNEEDBITS(8, state, ref b, ref k);
outbuf[w++] = (byte)b;
ZIPDUMPBITS(8, ref b, ref k);
}
// Restore the globals from the locals
state.window_posn = w;
state.bb = b;
state.bk = k;
return 0;
}
/// <summary>
/// Decompress a block originally compressed with fixed Huffman codes
/// </summary>
private static int DecompressFixed(State state, byte[] inbuf, byte[] outbuf)
{
// Create the block header
FixedHuffmanCompressedBlockHeader header = new FixedHuffmanCompressedBlockHeader();
fixed (uint* l = state.ll)
fixed (ushort* Zipcplens = CopyLengths)
fixed (ushort* Zipcplext = LiteralExtraBits)
fixed (ushort* Zipcpdist = CopyOffsets)
fixed (ushort* Zipcpdext = DistanceExtraBits)
{
// Assign the literal lengths
state.ll = header.LiteralLengths;
HuffmanNode* fixed_tl;
int fixed_bl = 7;
// Build the literal length tree
int i = BuildHuffmanTree(l, 288, 257, Zipcplens, Zipcplext, &fixed_tl, &fixed_bl, state);
if (i != 0)
return i;
// Assign the distance codes
state.ll = header.DistanceCodes;
HuffmanNode* fixed_td;
int fixed_bd = 5;
// Build the distance code tree
i = BuildHuffmanTree(l, 30, 0, Zipcpdist, Zipcpdext, &fixed_td, &fixed_bd, state);
if (i != 0)
return i;
// Decompress until an end-of-block code
return InflateCodes(fixed_tl, fixed_td, fixed_bl, fixed_bd, state, inbuf, outbuf);
}
}
/// <summary>
/// Decompress a block originally compressed with dynamic Huffman codes
/// </summary>
private static int DecompressDynamic(State state, byte[] inbuf, byte[] outbuf)
{
int i; /* temporary variables */
uint j;
uint l; /* last length */
uint m; /* mask for bit lengths table */
uint n; /* number of lengths to get */
HuffmanNode* tl; /* literal/length code table */
HuffmanNode* td; /* distance code table */
int bl; /* lookup bits for tl */
int bd; /* lookup bits for td */
uint nb; /* number of bit length codes */
uint nl; /* number of literal/length codes */
uint nd; /* number of distance codes */
uint b; /* bit buffer */
uint k; /* number of bits in bit buffer */
/* make local bit buffer */
b = state.bb;
k = state.bk;
state.ll = new uint[288 + 32];
fixed (uint* ll = state.ll)
{
/* read in table lengths */
ZIPNEEDBITS(5, state, ref b, ref k);
nl = 257 + (b & 0x1f); /* number of literal/length codes */
ZIPDUMPBITS(5, ref b, ref k);
ZIPNEEDBITS(5, state, ref b, ref k);
nd = 1 + (b & 0x1f); /* number of distance codes */
ZIPDUMPBITS(5, ref b, ref k);
ZIPNEEDBITS(4, state, ref b, ref k);
nb = 4 + (b & 0xf); /* number of bit length codes */
ZIPDUMPBITS(4, ref b, ref k);
if (nl > 288 || nd > 32)
return 1; /* bad lengths */
/* read in bit-length-code lengths */
for (j = 0; j < nb; j++)
{
ZIPNEEDBITS(3, state, ref b, ref k);
state.ll[BitLengthOrder[j]] = b & 7;
ZIPDUMPBITS(3, ref b, ref k);
}
for (; j < 19; j++)
state.ll[BitLengthOrder[j]] = 0;
/* build decoding table for trees--single level, 7 bit lookup */
bl = 7;
if ((i = BuildHuffmanTree(ll, 19, 19, null, null, &tl, &bl, state)) != 0)
return i; /* incomplete code set */
/* read in literal and distance code lengths */
n = nl + nd;
m = BitMasks[bl];
i = (int)(l = 0);
while ((uint)i < n)
{
ZIPNEEDBITS(bl, state, ref b, ref k);
j = (td = tl + (b & m))->b;
ZIPDUMPBITS((int)j, ref b, ref k);
j = td->n;
if (j < 16) /* length of code in bits (0..15) */
{
state.ll[i++] = l = j; /* save last length in l */
}
else if (j == 16) /* repeat last length 3 to 6 times */
{
ZIPNEEDBITS(2, state, ref b, ref k);
j = 3 + (b & 3);
ZIPDUMPBITS(2, ref b, ref k);
if ((uint)i + j > n)
return 1;
while (j-- > 0)
{
state.ll[i++] = l;
}
}
else if (j == 17) /* 3 to 10 zero length codes */
{
ZIPNEEDBITS(3, state, ref b, ref k);
j = 3 + (b & 7);
ZIPDUMPBITS(3, ref b, ref k);
if ((uint)i + j > n)
return 1;
while (j-- > 0)
state.ll[i++] = 0;
l = 0;
}
else /* j == 18: 11 to 138 zero length codes */
{
ZIPNEEDBITS(7, state, ref b, ref k);
j = 11 + (b & 0x7f);
ZIPDUMPBITS(7, ref b, ref k);
if ((uint)i + j > n)
return 1;
while (j-- > 0)
state.ll[i++] = 0;
l = 0;
}
}
/* restore the global bit buffer */
state.bb = b;
state.bk = k;
fixed (ushort* Zipcplens = CopyLengths)
fixed (ushort* Zipcplext = LiteralExtraBits)
fixed (ushort* Zipcpdist = CopyOffsets)
fixed (ushort* Zipcpdext = DistanceExtraBits)
{
/* build the decoding tables for literal/length and distance codes */
bl = ZIPLBITS;
if ((i = BuildHuffmanTree(ll, nl, 257, Zipcplens, Zipcplext, &tl, &bl, state)) != 0)
{
return i; /* incomplete code set */
}
bd = ZIPDBITS;
BuildHuffmanTree(ll + nl, nd, 0, Zipcpdist, Zipcpdext, &td, &bd, state);
/* decompress until an end-of-block code */
if (InflateCodes(tl, td, bl, bd, state, inbuf, outbuf) != 0)
return 1;
return 0;
}
}
}
/// <summary>
/// Build a Huffman tree from a set of lengths
/// </summary>
private static int BuildHuffmanTree(uint* b, uint n, uint s, ushort* d, ushort* e, HuffmanNode** t, int* m, State state)
{
uint a; /* counter for codes of length k */
uint el; /* length of EOB code (value 256) */
uint f; /* i repeats in table every f entries */
int g; /* maximum code length */
int h; /* table level */
uint i; /* counter, current code */
uint j; /* counter */
int k; /* number of bits in current code */
int* l; /* stack of bits per table */
uint* p; /* pointer into state.c[],state.b[],state.v[] */
HuffmanNode* q; /* points to current table */
HuffmanNode r = new HuffmanNode(); /* table entry for structure assignment */
int w; /* bits before this table == (l * h) */
uint* xp; /* pointer into x */
int y; /* number of dummy codes added */
uint z; /* number of entries in current table */
fixed (int* state_lx_ptr = state.lx)
{
l = state_lx_ptr + 1;
/* Generate counts for each bit length */
el = n > 256 ? b[256] : ZIPBMAX; /* set length of EOB code, if any */
for (i = 0; i < ZIPBMAX + 1; ++i)
state.c[i] = 0;
p = b; i = n;
do
{
state.c[*p]++; p++; /* assume all entries <= ZIPBMAX */
} while (--i > 0);
if (state.c[0] == n) /* null input--all zero length codes */
{
*t = null;
*m = 0;
return 0;
}
/* Find minimum and maximum length, bound *m by those */
for (j = 1; j <= ZIPBMAX; j++)
{
if (state.c[j] > 0)
break;
}
k = (int)j; /* minimum code length */
if ((uint)*m < j)
*m = (int)j;
for (i = ZIPBMAX; i > 0; i--)
{
if (state.c[i] > 0)
break;
}
g = (int)i; /* maximum code length */
if ((uint)*m > i)
*m = (int)i;
/* Adjust last length count to fill out codes, if needed */
for (y = 1 << (int)j; j < i; j++, y <<= 1)
{
if ((y -= (int)state.c[j]) < 0)
return 2; /* bad input: more codes than bits */
}
if ((y -= (int)state.c[i]) < 0)
return 2;
state.c[i] += (uint)y;
/* Generate starting offsets LONGo the value table for each length */
state.x[1] = j = 0;
fixed (uint* state_c_ptr = state.c)
fixed (uint* state_x_ptr = state.x)
{
p = state_c_ptr + 1;
xp = state_x_ptr + 2;
while (--i > 0)
{
/* note that i == g from above */
*xp++ = (j += *p++);
}
}
/* Make a table of values in order of bit lengths */
p = b; i = 0;
do
{
if ((j = *p++) != 0)
state.v[state.x[j]++] = i;
} while (++i < n);
/* Generate the Huffman codes and for each, make the table entries */
state.x[0] = i = 0; /* first Huffman code is zero */
fixed (uint* state_v_ptr = state.v)
{
p = state_v_ptr; /* grab values in bit order */
h = -1; /* no tables yet--level -1 */
w = l[-1] = 0; /* no bits decoded yet */
state.u[0] = default; /* just to keep compilers happy */
q = null; /* ditto */
z = 0; /* ditto */
/* go through the bit lengths (k already is bits in shortest code) */
for (; k <= g; k++)
{
a = state.c[k];
while (a-- > 0)
{
/* here i is the Huffman code of length k bits for value *p */
/* make tables up to required level */
while (k > w + l[h])
{
w += l[h++]; /* add bits already decoded */
/* compute minimum size table less than or equal to *m bits */
if ((z = (uint)(g - w)) > (uint)*m) /* upper limit */
z = (uint)*m;
if ((f = (uint)(1 << (int)(j = (uint)(k - w)))) > a + 1) /* try a k-w bit table */
{ /* too few codes for k-w bit table */
f -= a + 1; /* deduct codes from patterns left */
fixed (uint* state_c_ptr = state.c)
{
xp = state_c_ptr + k;
while (++j < z) /* try smaller tables up to z bits */
{
if ((f <<= 1) <= *++xp)
break; /* enough codes to use up j bits */
f -= *xp; /* else deduct codes from patterns */
}
}
}
if ((uint)w + j > el && (uint)w < el)
j = (uint)(el - w); /* make EOB code end at table */
z = (uint)(1 << (int)j); /* table entries for j-bit table */
l[h] = (int)j; /* set table size in stack */
/* allocate and link in new table */
q = (HuffmanNode*)Marshal.AllocHGlobal((int)((z + 1) * sizeof(HuffmanNode)));
*t = q + 1; /* link to list for HuffmanNode_free() */
*(t = &(*q).t) = null;
state.u[h] = ++q; /* table starts after link */
/* connect to last table, if there is one */
if (h > 0)
{
state.x[h] = i; /* save pattern for backing up */
r.b = (byte)l[h - 1]; /* bits to dump before this table */
r.e = (byte)(16 + j); /* bits in this table */
r.t = q; /* pointer to this table */
j = (uint)((i & ((1 << w) - 1)) >> (w - l[h - 1]));
state.u[h - 1][j] = r; /* connect to last table */
}
}
/* set up table entry in r */
r.b = (byte)(k - w);
fixed (uint* state_v_ptr_comp = state.v)
{
if (p >= state_v_ptr_comp + n)
{
r.e = 99; /* out of values--invalid code */
}
else if (*p < s)
{
r.e = (byte)(*p < 256 ? 16 : 15); /* 256 is end-of-block code */
r.n = (ushort)*p++; /* simple code is just the value */
}
else
{
r.e = (byte)e[*p - s]; /* non-simple--look up in lists */
r.n = d[*p++ - s];
}
}
/* fill code-like entries with r */
f = (uint)(1 << (k - w));
for (j = i >> w; j < z; j += f)
{
q[j] = r;
}
/* backwards increment the k-bit code i */
for (j = (uint)(1 << (k - 1)); (i & j) != 0; j >>= 1)
{
i ^= j;
}
i ^= j;
/* backup over finished tables */
while ((i & ((1 << w) - 1)) != state.x[h])
w -= l[--h]; /* don't need to update q */
}
}
}
/* return actual size of base table */
*m = l[0];
}
/* Return true (1) if we were given an incomplete table */
return y != 0 && g != 1 ? 1 : 0;
}
/// <summary>
/// Inflate codes into Huffman trees
/// </summary>
private static int InflateCodes(HuffmanNode* tl, HuffmanNode* td, int bl, int bd, State state, byte[] inbuf, byte[] outbuf)
{
uint e; /* table entry flag/number of extra bits */
uint n, d; /* length and index for copy */
uint w; /* current window position */
HuffmanNode* t; /* pointer to table entry */
uint ml, md; /* masks for bl and bd bits */
uint b; /* bit buffer */
uint k; /* number of bits in bit buffer */
/* make local copies of globals */
b = state.bb; /* initialize bit buffer */
k = state.bk;
w = state.window_posn; /* initialize window position */
/* inflate the coded data */
ml = BitMasks[bl]; /* precompute masks for speed */
md = BitMasks[bd];
for (; ; )
{
ZIPNEEDBITS(bl, state, ref b, ref k);
if ((e = (t = tl + (b & ml))->e) > 16)
{
do
{
if (e == 99)
return 1;
ZIPDUMPBITS(t->b, ref b, ref k);
e -= 16;
ZIPNEEDBITS((int)e, state, ref b, ref k);
} while ((e = (*(t = t->t + (b & BitMasks[e]))).e) > 16);
}
ZIPDUMPBITS(t->b, ref b, ref k);
if (e == 16) /* then it's a literal */
{
outbuf[w++] = (byte)t->n;
}
else /* it's an EOB or a length */
{
/* exit if end of block */
if (e == 15)
break;
/* get length of block to copy */
ZIPNEEDBITS((int)e, state, ref b, ref k);
n = t->n + (b & BitMasks[e]);
ZIPDUMPBITS((int)e, ref b, ref k);
/* decode distance of block to copy */
ZIPNEEDBITS(bd, state, ref b, ref k);
if ((e = (*(t = td + (b & md))).e) > 16)
do
{
if (e == 99)
return 1;
ZIPDUMPBITS(t->b, ref b, ref k);
e -= 16;
ZIPNEEDBITS((int)e, state, ref b, ref k);
} while ((e = (*(t = t->t + (b & BitMasks[e]))).e) > 16);
ZIPDUMPBITS(t->b, ref b, ref k);
ZIPNEEDBITS((int)e, state, ref b, ref k);
d = w - t->n - (b & BitMasks[e]);
ZIPDUMPBITS((int)e, ref b, ref k);
do
{
d &= ZIPWSIZE - 1;
e = ZIPWSIZE - Math.Max(d, w);
e = Math.Min(e, n);
n -= e;
do
{
outbuf[w++] = outbuf[d++];
} while (--e > 0);
} while (n > 0);
}
}
/* restore the globals from the locals */
state.window_posn = w; /* restore global window pointer */
state.bb = b; /* restore global bit buffer */
state.bk = k;
/* done */
return 0;
}
#region Macros
private static void ZIPNEEDBITS(int n, State state, ref uint bitBuffer, ref uint bitCount)
{
while (bitCount < n)
{
int c = *state.inpos++;
bitBuffer |= (uint)(c << (int)bitCount);
bitCount += 8;
}
}
private static void ZIPDUMPBITS(int n, ref uint bitBuffer, ref uint bitCount)
{
bitBuffer >>= n;
bitCount -= (uint)n;
}
#endregion
}
}

View File

@@ -0,0 +1,380 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.Models.Compression.MSZIP;
using static SabreTools.Models.Compression.MSZIP.Constants;
namespace SabreTools.Compression.MSZIP
{
/// <see href="https://www.rfc-editor.org/rfc/rfc1951"/>
public class DeflateDecompressor
{
/// <summary>
/// Internal bitstream to use for decompression
/// </summary>
private BitStream _bitStream;
/// <summary>
/// Create a new Decompressor from a byte array
/// </summary>
/// <param name="input">Byte array to decompress</param>
public DeflateDecompressor(byte[]? input)
{
// If we have an invalid stream
if (input == null || input.Length == 0)
throw new ArgumentException(nameof(input));
// Create a memory stream to wrap
var ms = new MemoryStream(input);
// Wrap the stream in a BitStream
_bitStream = new BitStream(ms);
}
/// <summary>
/// Create a new Decompressor from a Stream
/// </summary>
/// <param name="input">Stream to decompress</param>
public DeflateDecompressor(Stream? input)
{
// If we have an invalid stream
if (input == null || !input.CanRead || !input.CanSeek)
throw new ArgumentException(nameof(input));
// Wrap the stream in a BitStream
_bitStream = new BitStream(input);
}
/// <summary>
/// Decompress a stream into a <see cref="Block"/>
/// </summary>
/// <returns>Block containing the decompressed data on success, null on error</returns>
public Block? Process()
{
// Create a new block
var block = new Block();
// Try to read the header
block.BlockHeader = ReadBlockHeader();
if (block.BlockHeader.Signature != 0x4B43)
return null;
// Loop and read the deflate blocks
var deflateBlocks = new List<DeflateBlock>();
while (true)
{
// Try to read the deflate block
var deflateBlock = ReadDeflateBlock();
if (deflateBlock == null)
return null;
// Add the deflate block to the set
deflateBlocks.Add(deflateBlock);
// If we're at the final block, exit out of the loop
if (deflateBlock.Header!.BFINAL)
break;
}
// Assign the deflate blocks to the block and return
block.CompressedBlocks = deflateBlocks.ToArray();
return block;
}
#region Headers
/// <summary>
/// Read a BlockHeader from the input stream
/// </summary>
private BlockHeader ReadBlockHeader()
{
var header = new BlockHeader();
header.Signature = _bitStream.ReadUInt16() ?? 0;
return header;
}
/// <summary>
/// Read a DeflateBlockHeader from the input stream
/// </summary>
private DeflateBlockHeader ReadDeflateBlockHeader()
{
var header = new DeflateBlockHeader();
header.BFINAL = _bitStream.ReadBit() != 0x01;
uint? btype = _bitStream.ReadBitsLSB(2) ?? 0b11;
header.BTYPE = (CompressionType)btype;
return header;
}
/// <summary>
/// Read a NonCompressedBlockHeader from the input stream
/// </summary>
private NonCompressedBlockHeader ReadNonCompressedBlockHeader()
{
var header = new NonCompressedBlockHeader();
header.LEN = _bitStream.ReadUInt16() ?? 0;
header.NLEN = _bitStream.ReadUInt16() ?? 0;
return header;
}
/// <summary>
/// Read a FixedHuffmanCompressedBlockHeader from the input stream
/// </summary>
private (FixedCompressedDataHeader, uint, uint) RaadFixedCompressedDataHeader()
{
// Nothing needs to be read, all values are fixed
return (new FixedCompressedDataHeader(), 288, 30);
}
/// <summary>
/// Read a DynamicHuffmanCompressedBlockHeader from the input stream
/// </summary>
private (DynamicCompressedDataHeader, uint, uint) ReadDynamicCompressedDataHeader()
{
var header = new DynamicCompressedDataHeader();
// Setup the counts first
uint numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
uint numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
uint numLength = 4 + _bitStream.ReadBitsLSB(4) ?? 0;
// Convert the alphabet based on lengths
uint[] lengthLengths = new uint[19];
for (int i = 0; i < numLength; i++)
{
lengthLengths[BitLengthOrder[i]] = (byte)(_bitStream.ReadBitsLSB(3) ?? 0);
}
for (int i = (int)numLength; i < 19; i++)
{
lengthLengths[BitLengthOrder[i]] = 0;
}
// Make the lengths tree
HuffmanDecoder lengthTree = new HuffmanDecoder(lengthLengths, 19);
// Setup the literal and distance lengths
header.LiteralLengths = new uint[288];
header.DistanceCodes = new uint[32];
// Read the literal and distance codes
int repeatCode = 1;
uint leftover = ReadHuffmanLengths(lengthTree, header.LiteralLengths, numLiteral, 0, ref repeatCode);
_ = ReadHuffmanLengths(lengthTree, header.DistanceCodes, numDistance, leftover, ref repeatCode);
return (header, numLiteral, numDistance);
}
#endregion
#region Data
/// <summary>
/// Read an RFC1951 block
/// </summary>
private DeflateBlock? ReadDeflateBlock()
{
var deflateBlock = new DeflateBlock();
// Try to read the deflate block header
deflateBlock.Header = ReadDeflateBlockHeader();
switch (deflateBlock.Header.BTYPE)
{
// If stored with no compression
case CompressionType.NoCompression:
(var header00, var bytes00) = ReadNoCompression();
if (header00 == null || bytes00 == null)
return null;
deflateBlock.DataHeader = header00;
deflateBlock.Data = bytes00;
break;
// If compressed with fixed Huffman codes
case CompressionType.FixedHuffman:
(var header01, var bytes01) = ReadFixedHuffman();
if (header01 == null || bytes01 == null)
return null;
deflateBlock.DataHeader = header01;
deflateBlock.Data = bytes01;
break;
// If compressed with dynamic Huffman codes
case CompressionType.DynamicHuffman:
(var header10, var bytes10) = ReadDynamicHuffman();
if (header10 == null || bytes10 == null)
return null;
deflateBlock.DataHeader = header10;
deflateBlock.Data = bytes10;
break;
// Reserved is not allowed and is treated as an error
case CompressionType.Reserved:
default:
return null;
}
return deflateBlock;
}
/// <summary>
/// Read an RFC1951 block with no compression
/// </summary>
private (NonCompressedBlockHeader?, byte[]?) ReadNoCompression()
{
// Skip any remaining bits in current partially processed byte
_bitStream.Discard();
// Read LEN and NLEN
var header = ReadNonCompressedBlockHeader();
if (header.LEN == 0 && header.NLEN == 0)
return (null, null);
// Copy LEN bytes of data to output
return (header, _bitStream.ReadBytes(header.LEN));
}
/// <summary>
/// Read an RFC1951 block with fixed Huffman compression
/// </summary>
private (FixedCompressedDataHeader, byte[]?) ReadFixedHuffman()
{
var bytes = new List<byte>();
// Get the fixed huffman header
(var header, uint numLiteral, uint numDistance) = RaadFixedCompressedDataHeader();
// Make the literal and distance trees
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
// Now loop and decode
return (header, ReadHuffmanBlock(literalTree, distanceTree));
}
/// <summary>
/// Read an RFC1951 block with dynamic Huffman compression
/// </summary>
private (DynamicCompressedDataHeader?, byte[]?) ReadDynamicHuffman()
{
// Get the dynamic huffman header
(var header, uint numLiteral, uint numDistance) = ReadDynamicCompressedDataHeader();
// Make the literal and distance trees
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
// Now loop and decode
return (header, ReadHuffmanBlock(literalTree, distanceTree));
}
/// <summary>
/// Read an RFC1951 block with Huffman compression
/// </summary>
private byte[]? ReadHuffmanBlock(HuffmanDecoder literalTree, HuffmanDecoder distanceTree)
{
// Now loop and decode
var bytes = new List<byte>();
while (true)
{
// Decode the next literal value
int sym = literalTree.Decode(_bitStream);
// If we have an immediate symbol
if (sym < 256)
{
bytes.Add((byte)sym);
}
// If we have the ending symbol
else if (sym == 256)
{
break;
}
// If we have a length/distance pair
else
{
sym -= 257;
uint? length = CopyLengths[sym] + _bitStream.ReadBitsLSB(LiteralExtraBits[sym]);
if (length == null)
return null;
int distanceCode = distanceTree.Decode(_bitStream);
uint? distance = CopyOffsets[distanceCode] + _bitStream.ReadBitsLSB(DistanceExtraBits[distanceCode]);
if (distance == null)
return null;
byte[] arr = bytes.Skip(bytes.Count - (int)distance).Take((int)length).ToArray();
bytes.AddRange(arr);
}
}
// Return the decoded array
return bytes.ToArray();
}
/// <summary>
/// Read the huffman lengths
/// </summary>
private uint ReadHuffmanLengths(HuffmanDecoder lengthTree, uint[] lengths, uint numCodes, uint repeat, ref int repeatCode)
{
int i = 0;
// First fill in any repeat codes
while (repeat > 0)
{
lengths[i++] = (byte)repeatCode;
repeat--;
}
// Then process the rest of the table
while (i < numCodes)
{
// Get the next length encoding from the stream
int lengthEncoding = lengthTree.Decode(_bitStream);
// Values less than 16 are encoded directly
if (lengthEncoding < 16)
{
lengths[i++] = (byte)lengthEncoding;
repeatCode = lengthEncoding;
}
// Otherwise, the repeat count is based on the next values
else
{
// Determine the repeat count and code from the encoding
if (lengthEncoding == 16)
{
repeat = 3 + _bitStream.ReadBitsLSB(2) ?? 0;
}
else if (lengthEncoding == 17)
{
repeat = 3 + _bitStream.ReadBitsLSB(3) ?? 0;
repeatCode = 0;
}
else if (lengthEncoding == 18)
{
repeat = 11 + _bitStream.ReadBitsLSB(7) ?? 0;
repeatCode = 0;
}
// Read in the expected lengths
while (i < numCodes && repeat > 0)
{
lengths[i++] = (byte)repeatCode;
repeat--;
}
}
}
// Return any repeat value we have left over
return repeat;
}
#endregion
}
}

142
MSZIP/HuffmanDecoder.cs Normal file
View File

@@ -0,0 +1,142 @@
using System;
using System.IO;
using System.Linq;
namespace SabreTools.Compression.MSZIP
{
public class HuffmanDecoder
{
/// <summary>
/// Root Huffman node for the tree
/// </summary>
private HuffmanNode _root;
/// <summary>
/// Create a Huffman tree to decode with
/// </summary>
/// <param name="lengths">Array representing the number of bits for each value</param>
/// <param name="numCodes">Number of Huffman codes encoded</param>
public HuffmanDecoder(uint[]? lengths, uint numCodes)
{
// Ensure we have lengths
if (lengths == null)
throw new ArgumentNullException(nameof(lengths));
// Set the root to null for now
HuffmanNode? root = null;
// Determine the value for max_bits
uint max_bits = lengths.Max();
// Count the number of codes for each code length
int[] bl_count = new int[max_bits + 1];
for (int i = 0; i < numCodes; i++)
{
uint length = lengths[i];
bl_count[length]++;
}
// Find the numerical value of the smalles code for each code length
int[] next_code = new int[max_bits + 1];
int code = 0;
bl_count[0] = 0;
for (int bits = 1; bits <= max_bits; bits++)
{
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
// Assign numerical values to all codes, using consecutive
// values for all codes of the same length with the base
// values determined at step 2. Codes that are never used
// (which have a bit length of zero) must not be assigned a value.
int[] tree = new int[numCodes];
for (int i = 0; i < numCodes; i++)
{
uint len = lengths[i];
if (len == 0)
continue;
// Set the value in the tree
tree[i] = next_code[len];
next_code[len]++;
}
// Now insert the values into the structure
for (int i = 0; i < numCodes; i++)
{
// If we have a 0-length code
uint len = lengths[i];
if (len == 0)
continue;
// Insert the value starting at the root
_root = Insert(_root, i, len, tree[i]);
}
// Assign the root value
_root = root!;
}
/// <summary>
/// Decode the next value from the stream as a Huffman-encoded value
/// </summary>
/// <param name="input">BitStream representing the input</param>
/// <returns>Value of the node described by the input</returns>
public int Decode(BitStream input)
{
// Start at the root of the tree
var node = _root;
while (node?.Left != null)
{
// Read the next bit to determine direction
byte? nextBit = input.ReadBit();
if (nextBit == null)
throw new EndOfStreamException();
// Left == 0, Right == 1
if (nextBit == 0)
node = node.Left;
else
node = node.Right;
}
// We traversed to the bottom of the branch
return node?.Value ?? 0;
}
/// <summary>
/// Insert a value based on an existing Huffman node
/// </summary>
/// <param name="node">Existing node to append to, or null if root</param>
/// <param name="value">Value to append to the tree</param>
/// <param name="length">Length of the current encoding</param>
/// <param name="code">Encoding of the value to traverse</param>
/// <returns>New instance of the node with value appended</returns>
private static HuffmanNode Insert(HuffmanNode? node, int value, uint length, int code)
{
// If no node is provided, create a new one
if (node == null)
node = new HuffmanNode();
// If we're at the correct location, insert the value
if (length == 0)
{
node.Value = value;
return node;
}
// Otherwise, get the next bit from the code
byte nextBit = (byte)(code >> (int)(length - 1) & 1);
// Left == 0, Right == 1
if (nextBit == 0)
node.Left = Insert(node.Left, value, length - 1, code);
else
node.Right = Insert(node.Right, value, length - 1, code);
// Now return the node
return node;
}
}
}

View File

@@ -1,29 +1,23 @@
namespace SabreTools.Compression.MSZIP
{
internal unsafe struct HuffmanNode
/// <summary>
/// Represents a single node in a Huffman tree
/// </summary>
public class HuffmanNode
{
/// <summary>
/// Number of extra bits or operation
/// Left child of the current node
/// </summary>
public byte e;
public HuffmanNode? Left { get; set; }
/// <summary>
/// Number of bits in this code or subcode
/// Right child of the current node
/// </summary>
public byte b;
#region v
public HuffmanNode? Right { get; set; }
/// <summary>
/// Literal, length base, or distance base
/// Value of the current node
/// </summary>
public ushort n;
/// <summary>
/// Pointer to next level of table
/// </summary>
public HuffmanNode* t;
#endregion
public int Value { get; set; }
}
}

View File

@@ -1,56 +0,0 @@
using static SabreTools.Models.Compression.MSZIP.Constants;
namespace SabreTools.Compression.MSZIP
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
internal unsafe class State
{
/// <summary>
/// Current offset within the window
/// </summary>
public uint window_posn;
/// <summary>
/// Bit buffer
/// </summary>
public uint bb;
/// <summary>
/// Bits in bit buffer
/// </summary>
public uint bk;
/// <summary>
/// Literal/length and distance code lengths
/// </summary>
public uint[] ll = new uint[288 + 32];
/// <summary>
/// Bit length count table
/// </summary>
public uint[] c = new uint[ZIPBMAX + 1];
/// <summary>
/// Memory for l[-1..ZIPBMAX-1]
/// </summary>
public int[] lx = new int[ZIPBMAX + 1];
/// <summary>
/// Table stack
/// </summary>
public HuffmanNode*[] u = new HuffmanNode*[ZIPBMAX];
/// <summary>
/// Values in order of bit length
/// </summary>
public uint[] v = new uint[ZIPN_MAX];
/// <summary>
/// Bit offsets, then code stack
/// </summary>
public uint[] x = new uint[ZIPBMAX + 1];
/// <remarks>byte*</remarks>
public byte* inpos;
}
}

50
OldDotNet.cs Normal file
View File

@@ -0,0 +1,50 @@
#if NET20 || NET35
using System;
using System.IO;
namespace SabreTools.Compression
{
/// <summary>
/// Derived from the mscorlib code from .NET Framework 4.0
/// </summary>
internal static class OldDotNet
{
public static void CopyTo(this Stream source, Stream destination)
{
if (destination == null)
{
throw new ArgumentNullException("destination");
}
if (!source.CanRead && !source.CanWrite)
{
throw new ObjectDisposedException(null);
}
if (!destination.CanRead && !destination.CanWrite)
{
throw new ObjectDisposedException("destination");
}
if (!source.CanRead)
{
throw new NotSupportedException();
}
if (!destination.CanWrite)
{
throw new NotSupportedException();
}
byte[] array = new byte[81920];
int count;
while ((count = source.Read(array, 0, array.Length)) != 0)
{
destination.Write(array, 0, count);
}
}
}
}
#endif

51
Quantum/Constants.cs Normal file
View File

@@ -0,0 +1,51 @@
namespace SabreTools.Compression.Quantum
{
/// <see href="www.russotto.net/quantumcomp.html"/>
/// TODO: Remove this class when Models gets updated
public static class Constants
{
public static readonly int[] PositionSlot = new int[]
{
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[]
{
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[]
{
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[]
{
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[]
{
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
};
}
}

View File

@@ -1,325 +1,393 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using SabreTools.Models.Compression.Quantum;
using SabreTools.Models.MicrosoftCabinet;
using static SabreTools.Compression.Quantum.Constants;
namespace SabreTools.Compression.Quantum
{
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/fdi.c"/>
/// <see href="https://github.com/wine-mirror/wine/blob/master/include/fdi.h"/>
/// <see href="http://www.russotto.net/quantumcomp.html"/>
internal static class Decompressor
/// <see href="www.russotto.net/quantumcomp.html"/>
public class Decompressor
{
/// <summary>
/// Decompress a byte array using a given State
/// Internal bitstream to use for decompression
/// </summary>
public static int Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
private BitStream _bitStream;
#region Models
/// <summary>
/// Selector 0: literal, 64 entries, starting symbol 0
/// </summary>
private Model _model0;
/// <summary>
/// Selector 1: literal, 64 entries, starting symbol 64
/// </summary>
private Model _model1;
/// <summary>
/// Selector 2: literal, 64 entries, starting symbol 128
/// </summary>
private Model _model2;
/// <summary>
/// Selector 3: literal, 64 entries, starting symbol 192
/// </summary>
private Model _model3;
/// <summary>
/// Selector 4: LZ, 3 character matches
/// </summary>
private Model _model4;
/// <summary>
/// Selector 5: LZ, 4 character matches
/// </summary>
private Model _model5;
/// <summary>
/// Selector 6: LZ, 5+ character matches
/// </summary>
private Model _model6;
/// <summary>
/// Selector 6 length model
/// </summary>
private Model _model6len;
/// <summary>
/// Selector selector model
/// </summary>
private Model _selector;
#endregion
#region Coding State
/// <summary>
/// Artihmetic coding state: high
/// </summary>
private ushort CS_H;
/// <summary>
/// Artihmetic coding state: low
/// </summary>
private ushort CS_L;
/// <summary>
/// Artihmetic coding state: current
/// </summary>
private ushort CS_C;
#endregion
/// <summary>
/// Create a new Decompressor from a byte array
/// </summary>
/// <param name="input">Byte array to decompress</param>
/// <param name="windowBits">Number of bits in the sliding window</param>
public Decompressor(byte[]? input, uint windowBits)
{
int inpos = 0, outpos = 0; // inbuf[0], outbuf[0]
int window = 0; // state.Window[0]
int runsrc, rundest;
uint windowPosition = state.WindowPosition;
uint windowSize = state.WindowSize;
// If we have an invalid stream
if (input == null || input.Length == 0)
throw new ArgumentException(nameof(input));
int extra, togo = outlen, matchLength = 0, copyLength;
byte selector, sym;
uint matchOffset = 0;
// If we have an invalid value for the window bits
if (windowBits < 10 || windowBits > 21)
throw new ArgumentOutOfRangeException(nameof(windowBits));
// Make local copies of state variables
uint bitBuffer = state.BitBuffer;
int bitsLeft = state.BitsLeft;
// Create a memory stream to wrap
var ms = new MemoryStream(input);
ushort H = 0xFFFF, L = 0;
// Wrap the stream in a BitStream
_bitStream = new BitStream(ms);
// Read initial value of C
ushort C = (ushort)Q_READ_BITS(16, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
// Initialize literal models
this._model0 = CreateModel(0, 64);
this._model1 = CreateModel(64, 64);
this._model2 = CreateModel(128, 64);
this._model3 = CreateModel(192, 64);
// Apply 2^x-1 mask
windowPosition &= windowSize - 1;
// Initialize LZ models
int maxBitLength = (int)(windowBits * 2);
this._model4 = CreateModel(0, maxBitLength > 24 ? 24 : maxBitLength);
this._model5 = CreateModel(0, maxBitLength > 36 ? 36 : maxBitLength);
this._model6 = CreateModel(0, maxBitLength);
this._model6len = CreateModel(0, 27);
while (togo > 0)
{
selector = (byte)GET_SYMBOL(state.SelectorModel, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
switch (selector)
{
// Selector 0 = literal model, 64 entries, 0x00-0x3F
case 0:
sym = (byte)GET_SYMBOL(state.Model0, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Initialze the selector model
this._selector = CreateModel(0, 7);
// Selector 1 = literal model, 64 entries, 0x40-0x7F
case 1:
sym = (byte)GET_SYMBOL(state.Model1, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 2 = literal model, 64 entries, 0x80-0xBF
case 2:
sym = (byte)GET_SYMBOL(state.Model2, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 3 = literal model, 64 entries, 0xC0-0xFF
case 3:
sym = (byte)GET_SYMBOL(state.Model3, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
state.Window[window + windowPosition++] = sym;
togo--;
break;
// Selector 4 = fixed length of 3
case 4:
sym = (byte)GET_SYMBOL(state.Model4, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
matchLength = 3;
break;
// Selector 5 = fixed length of 4
case 5:
sym = (byte)GET_SYMBOL(state.Model5, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
matchLength = 4;
break;
// Selector 6 = variable length
case 6:
sym = (byte)GET_SYMBOL(state.Model6Length, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
extra = (int)Q_READ_BITS(state.LengthExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchLength = state.LengthBases[sym] + extra + 5;
sym = (byte)GET_SYMBOL(state.Model6Position, ref H, ref L, ref C, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
extra = (int)Q_READ_BITS(state.ExtraBits[sym], inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
matchOffset = (uint)(state.PositionSlotBases[sym] + extra + 1);
break;
default:
return inpos;
}
// If this is a match
if (selector >= 4)
{
rundest = (int)(window + windowPosition);
togo -= matchLength;
// Copy any wrapped around source data
if (windowPosition >= matchOffset)
{
// No wrap
runsrc = (int)(rundest - matchOffset);
}
else
{
runsrc = (int)(rundest + (windowSize - matchOffset));
copyLength = (int)(matchOffset - windowPosition);
if (copyLength < matchLength)
{
matchLength -= copyLength;
windowPosition += (uint)copyLength;
while (copyLength-- > 0)
{
state.Window[rundest++] = state.Window[rundest++];
}
runsrc = window;
}
}
windowPosition += (uint)matchLength;
// Copy match data - no worries about destination wraps
while (matchLength-- > 0)
{
state.Window[rundest++] = state.Window[runsrc++];
// Handle wraparounds that aren't supposed to happen
if (rundest >= state.Window.Length)
rundest = 0;
if (runsrc >= state.Window.Length)
runsrc = 0;
}
}
// If we hit the end of the window, copy to the output and wrap
if (windowPosition >= state.Window.Length)
{
Array.Copy(state.Window, 0, outbuf, outpos, Math.Min(windowSize, outlen));
outpos += (int)Math.Min(windowSize, outlen);
outlen -= (int)Math.Min(windowSize, outlen);
windowPosition = 0;
}
}
if (togo > 0)
return inpos;
if (outlen > 0)
{
int sourceIndex = (int)((windowPosition == 0 ? windowSize : windowPosition) - outlen);
Array.Copy(state.Window, sourceIndex, outbuf, outpos, outlen);
}
// Cache the decompression state variables
state.BitBuffer = bitBuffer;
state.BitsLeft = bitsLeft;
state.WindowPosition = windowPosition;
return inpos;
// Initialize coding state
this.CS_H = 0;
this.CS_L = 0;
this.CS_C = 0;
}
/// <summary>
/// Initialize a Quantum decompressor state
/// Create a new Decompressor from a Stream
/// </summary>
public static bool InitState(State state, CFFOLDER folder)
/// <param name="input">Stream to decompress</param>
/// <param name="windowBits">Number of bits in the sliding window</param>
public Decompressor(Stream? input, uint windowBits)
{
int window = ((ushort)folder.CompressionType >> 8) & 0x1f;
int level = ((ushort)folder.CompressionType >> 4) & 0xF;
return InitState(state, window, level);
// If we have an invalid stream
if (input == null || !input.CanRead || !input.CanSeek)
throw new ArgumentException(nameof(input));
// If we have an invalid value for the window bits
if (windowBits < 10 || windowBits > 21)
throw new ArgumentOutOfRangeException(nameof(windowBits));
// Wrap the stream in a BitStream
_bitStream = new BitStream(input);
// Initialize literal models
this._model0 = CreateModel(0, 64);
this._model1 = CreateModel(64, 64);
this._model2 = CreateModel(128, 64);
this._model3 = CreateModel(192, 64);
// Initialize LZ models
int maxBitLength = (int)(windowBits * 2);
this._model4 = CreateModel(0, maxBitLength > 24 ? 24 : maxBitLength);
this._model5 = CreateModel(0, maxBitLength > 36 ? 36 : maxBitLength);
this._model6 = CreateModel(0, maxBitLength);
this._model6len = CreateModel(0, 27);
// Initialze the selector model
this._selector = CreateModel(0, 7);
// Initialize coding state
this.CS_H = 0;
this.CS_L = 0;
this.CS_C = 0;
}
/// <summary>
/// Initialize a Quantum decompressor state
/// Process the stream and return the decompressed output
/// </summary>
public static bool InitState(State state, int window, int level)
/// <returns>Byte array representing the decompressed data, null on error</returns>
public byte[] Process()
{
uint windowSize = (uint)(1 << window);
int maxSize = window * 2;
// Initialize the coding state
CS_H = 0xffff;
CS_L = 0x0000;
CS_C = (ushort)(_bitStream.ReadBitsMSB(16) ?? 0);
// QTM supports window sizes of 2^10 (1Kb) through 2^21 (2Mb)
// If a previously allocated window is big enough, keep it
if (window < 10 || window > 21)
return false;
// If we don't have the proper window size
if (state.ActualSize < windowSize)
state.Window = null;
// If we have no window
if (state.Window == null)
// Loop until the end of the stream
var bytes = new List<byte>();
while (_bitStream.Position < _bitStream.Length)
{
state.Window = new byte[windowSize];
state.ActualSize = windowSize;
// Determine the selector to use
int selector = GetSymbol(_selector);
// Handle literal selectors
if (selector < 4)
{
switch (selector)
{
case 0:
bytes.Add((byte)GetSymbol(_model0));
break;
case 1:
bytes.Add((byte)GetSymbol(_model1));
break;
case 2:
bytes.Add((byte)GetSymbol(_model2));
break;
case 3:
bytes.Add((byte)GetSymbol(_model3));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
// Handle LZ selectors
else
{
int offset, length;
switch (selector)
{
case 4:
int model4sym = GetSymbol(_model4);
int model4extra = (int)(_bitStream.ReadBitsMSB(PositionExtraBits[model4sym]) ?? 0);
offset = PositionSlot[model4sym] + model4extra + 1;
length = 3;
break;
case 5:
int model5sym = GetSymbol(_model5);
int model5extra = (int)(_bitStream.ReadBitsMSB(PositionExtraBits[model5sym]) ?? 0);
offset = PositionSlot[model5sym] + model5extra + 1;
length = 4;
break;
case 6:
int lengthSym = GetSymbol(_model6len);
int lengthExtra = (int)(_bitStream.ReadBitsMSB(LengthExtraBits[lengthSym]) ?? 0);
length = LengthSlot[lengthSym] + lengthExtra + 5;
int model6sym = GetSymbol(_model6);
int model6extra = (int)(_bitStream.ReadBitsMSB(PositionExtraBits[model6sym]) ?? 0);
offset = PositionSlot[model6sym] + model6extra + 1;
break;
default:
throw new ArgumentOutOfRangeException();
}
// Copy the previous data
int copyIndex = bytes.Count - offset;
while (length-- > 0)
{
bytes.Add(bytes[copyIndex++]);
}
// TODO: Add MS-CAB specific padding
// TODO: Add Cinematronics specific checksum
}
}
// Set the window size and position
state.WindowSize = windowSize;
state.WindowPosition = 0;
// Initialize arithmetic coding models
state.SelectorModel = CreateModel(state.SelectorModelSymbols, 7, 0);
state.Model0 = CreateModel(state.Model0Symbols, 0x40, 0x00);
state.Model1 = CreateModel(state.Model1Symbols, 0x40, 0x40);
state.Model2 = CreateModel(state.Model2Symbols, 0x40, 0x80);
state.Model3 = CreateModel(state.Model3Symbols, 0x40, 0xC0);
// Model 4 depends on table size, ranges from 20 to 24
state.Model4 = CreateModel(state.Model4Symbols, (maxSize < 24) ? maxSize : 24, 0);
// Model 5 depends on table size, ranges from 20 to 36
state.Model5 = CreateModel(symbols: state.Model5Symbols, (maxSize < 36) ? maxSize : 36, 0);
// Model 6 Position depends on table size, ranges from 20 to 42
state.Model6Position = CreateModel(state.Model6PositionSymbols, (maxSize < 42) ? maxSize : 42, 0);
state.Model6Length = CreateModel(state.Model6LengthSymbols, 27, 0);
return true;
return bytes.ToArray();
}
/// <summary>
/// Initialize a Quantum model that decodes symbols from s to (s + n - 1)
/// Create and initialize a model base on the start symbol and length
/// </summary>
private static Model CreateModel(ModelSymbol[] symbols, int entryCount, int initialSymbol)
private Model CreateModel(ushort start, int length)
{
// Set the basic values
Model model = new Model
// Create the model
var model = new Model
{
Entries = length,
Symbols = new ModelSymbol[length],
TimeToReorder = 4,
Entries = entryCount,
Symbols = symbols,
};
// Clear out the look-up table
model.LookupTable = Enumerable.Repeat<ushort>(0xFFFF, model.LookupTable.Length).ToArray();
// Loop through and build the look-up table
for (ushort i = 0; i < entryCount; i++)
// Populate the symbol array
for (int i = 0; i < length; i++)
{
// Set up a look-up entry for symbol
model.LookupTable[i + initialSymbol] = i;
// Create the symbol in the table
model.Symbols[i] = new ModelSymbol
{
Symbol = (ushort)(i + initialSymbol),
CumulativeFrequency = (ushort)(entryCount - i),
Symbol = (ushort)(start + i),
CumulativeFrequency = (ushort)(length - 1),
};
}
// Set the last symbol frequency to 0
model.Symbols[entryCount] = new ModelSymbol { CumulativeFrequency = 0 };
return model;
}
/// <summary>
/// Update the Quantum model for a particular symbol
/// Get the next symbol from a model
/// </summary>
private static void UpdateModel(Model model, int symbol)
private int GetSymbol(Model model)
{
// Update the cumulative frequency for all symbols less than the provided
for (int i = 0; i < symbol; i++)
int freq = GetFrequency(model.Symbols![0]!.CumulativeFrequency);
int i;
for (i = 1; i < model.Entries; i++)
{
model.Symbols[i].CumulativeFrequency += 8;
if (model.Symbols[i]!.CumulativeFrequency <= freq)
break;
}
// If the first symbol still has a cumulative frequency under 3800
if (model.Symbols[0].CumulativeFrequency <= 3800)
return;
int sym = model.Symbols![i - 1]!.Symbol;
// If we have more than 1 shift left in the model
if (--model.TimeToReorder != 0)
GetCode(model.Symbols![i - 1]!.CumulativeFrequency,
model.Symbols![i]!.CumulativeFrequency,
model.Symbols![0]!.CumulativeFrequency);
UpdateModel(model, i);
return sym;
}
/// <summary>
/// Get the next code based on the frequencies
/// </summary>
private void GetCode(int prevFrequency, int currentFrequency, int totalFrequency)
{
uint range = (ushort)((CS_H - CS_L) + 1);
CS_H = (ushort)(CS_L + (prevFrequency * range) / totalFrequency - 1);
CS_L = (ushort)(CS_L + (currentFrequency * range) / totalFrequency);
while (true)
{
// Loop through the entries from highest to lowest,
// performing the shift on the cumulative frequencies
if ((CS_L & 0x8000) != (CS_H & 0x8000))
{
if ((CS_L & 0x4000) != 0 && (CS_H & 0x4000) == 0)
{
// Underflow case
CS_C ^= 0x4000;
CS_L &= 0x3FFF;
CS_H |= 0x4000;
}
else
{
break;
}
}
CS_L <<= 1;
CS_H = (ushort)((CS_H << 1) | 1);
CS_C = (ushort)((CS_C << 1) | _bitStream.ReadBit() ?? 0);
}
}
/// <summary>
/// Update the model after an encode or decode step
/// </summary>
private void UpdateModel(Model model, int lastUpdated)
{
// Update cumulative frequencies
for (int i = 0; i < lastUpdated; i++)
{
var sym = model.Symbols![i]!;
sym.CumulativeFrequency += 8;
}
// Decrement reordering time, if needed
if (model.Symbols![0]!.CumulativeFrequency > 3800)
model.TimeToReorder--;
// If we haven't hit the reordering time
if (model.TimeToReorder > 0)
{
// Update the cumulative frequencies
for (int i = model.Entries - 1; i >= 0; i--)
{
// -1, not -2; the 0 entry saves this
model.Symbols[i].CumulativeFrequency >>= 1;
if (model.Symbols[i].CumulativeFrequency <= model.Symbols[i + 1].CumulativeFrequency)
model.Symbols[i].CumulativeFrequency = (ushort)(model.Symbols[i + 1].CumulativeFrequency + 1);
// Divide with truncation by 2
var sym = model.Symbols![i]!;
sym.CumulativeFrequency >>= 1;
// If we are lower the next frequency
if (i != 0 && sym.CumulativeFrequency <= model.Symbols![i + 1]!.CumulativeFrequency)
sym.CumulativeFrequency = (ushort)(model.Symbols![i + 1]!.CumulativeFrequency + 1);
}
}
// If we have no shifts left in the model
// If we hit the reordering time
else
{
// Reset the shifts left value to 50
model.TimeToReorder = 50;
// Loop through the entries setting the cumulative frequencies
// Calculate frequencies from cumulative frequencies
for (int i = 0; i < model.Entries; i++)
{
// No -1, want to include the 0 entry
// This converts cumfreqs into frequencies, then shifts right
model.Symbols[i].CumulativeFrequency -= model.Symbols[i + 1].CumulativeFrequency;
model.Symbols[i].CumulativeFrequency++; // Avoid losing things entirely
model.Symbols[i].CumulativeFrequency >>= 1;
if (i != model.Entries - 1)
model.Symbols![i]!.CumulativeFrequency -= model.Symbols![i + 1]!.CumulativeFrequency;
model.Symbols![i]!.CumulativeFrequency++;
model.Symbols![i]!.CumulativeFrequency >>= 1;
}
// Now sort by frequencies, decreasing order -- this must be an
// inplace selection sort, or a sort with the same (in)stability
// characteristics
for (int i = 0; i < model.Entries - 1; i++)
// Sort frequencies in decreasing order
for (int i = 0; i < model.Entries; i++)
{
for (int j = i + 1; j < model.Entries; j++)
{
if (model.Symbols[i].CumulativeFrequency < model.Symbols[j].CumulativeFrequency)
if (model.Symbols![i]!.CumulativeFrequency < model.Symbols![j]!.CumulativeFrequency)
{
var temp = model.Symbols[i];
model.Symbols[i] = model.Symbols[j];
@@ -328,172 +396,26 @@ namespace SabreTools.Compression.Quantum
}
}
// Then convert frequencies back to cumfreq
// Calculate cumulative frequencies from frequencies
for (int i = model.Entries - 1; i >= 0; i--)
{
model.Symbols[i].CumulativeFrequency += model.Symbols[i + 1].CumulativeFrequency;
if (i != model.Entries - 1)
model.Symbols![i]!.CumulativeFrequency += model.Symbols![i + 1]!.CumulativeFrequency;
}
// Then update the other part of the table
for (ushort i = 0; i < model.Entries; i++)
{
model.LookupTable[model.Symbols[i].Symbol] = i;
}
// Reset the time to reorder
model.TimeToReorder = 50;
}
}
// Bitstream reading macros (Quantum / normal byte order)
#region Macros
/*
* These bit access routines work by using the area beyond the MSB and the
* LSB as a free source of zeroes. This avoids having to mask any bits.
* So we have to know the bit width of the bitbuffer variable. This is
* defined as Uint_BITS.
*
* Uint_BITS should be at least 16 bits. Unlike LZX's Huffman decoding,
* Quantum's arithmetic decoding only needs 1 bit at a time, it doesn't
* need an assured number. Retrieving larger bitstrings can be done with
* multiple reads and fills of the bitbuffer. The code should work fine
* for machines where Uint >= 32 bits.
*
* Also note that Quantum reads bytes in normal order; LZX is in
* little-endian order.
*/
/// <summary>
/// Should be used first to set up the system
/// Get the frequency of a symbol based on its total frequency
/// </summary>
private static void Q_INIT_BITSTREAM(out int bitsleft, out uint bitbuf)
private ushort GetFrequency(ushort totalFrequency)
{
bitsleft = 0;
bitbuf = 0;
ulong range = (ulong)(((CS_H - CS_L) & 0xFFFF) + 1);
ulong frequency = (ulong)((CS_C - CS_L + 1) * totalFrequency - 1) / range;
return (ushort)(frequency & 0xFFFF);
}
/// <summary>
/// Adds more data to the bit buffer, if there is room for another 16 bits.
/// </summary>
private static void Q_FILL_BUFFER(byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
if (bitsleft > 8)
return;
byte b0 = inpos + 0 < inbuf.Length ? inbuf[inpos + 0] : (byte)0;
byte b1 = inpos + 1 < inbuf.Length ? inbuf[inpos + 1] : (byte)0;
bitbuf |= (uint)(((b0 << 8) | b1) << (16 - bitsleft));
bitsleft += 16;
inpos += 2;
}
/// <summary>
/// Extracts (without removing) N bits from the bit buffer
/// </summary>
private static uint Q_PEEK_BITS(int n, uint bitbuf)
{
return bitbuf >> (32 - n);
}
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
private static void Q_REMOVE_BITS(int n, ref int bitsleft, ref uint bitbuf)
{
bitbuf <<= n;
bitsleft -= n;
}
/// <summary>
/// Takes N bits from the buffer and puts them in v. Unlike LZX, this can loop
/// several times to get the requisite number of bits.
/// </summary>
private static uint Q_READ_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
uint v = 0; int bitrun;
for (int bitsneed = n; bitsneed != 0; bitsneed -= bitrun)
{
Q_FILL_BUFFER(inbuf, ref inpos, ref bitsleft, ref bitbuf);
bitrun = (bitsneed > bitsleft) ? bitsleft : bitsneed;
v = (v << bitrun) | Q_PEEK_BITS(bitrun, bitbuf);
Q_REMOVE_BITS(bitrun, ref bitsleft, ref bitbuf);
}
return v;
}
/// <summary>
/// Fetches the next symbol from the stated model and puts it in symbol.
/// It may need to read the bitstream to do this.
/// </summary>
private static ushort GET_SYMBOL(Model model, ref ushort H, ref ushort L, ref ushort C, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
{
ushort symf = GetFrequency(model.Symbols[0].CumulativeFrequency, H, L, C);
int i;
for (i = 1; i < model.Entries; i++)
{
if (model.Symbols[i].CumulativeFrequency <= symf)
break;
}
ushort symbol = model.Symbols[i - 1].Symbol;
GetCode(model.Symbols[i - 1].CumulativeFrequency,
model.Symbols[i].CumulativeFrequency,
model.Symbols[0].CumulativeFrequency,
ref H, ref L, ref C,
inbuf, ref inpos, ref bitsleft, ref bitbuf);
UpdateModel(model, i);
return symbol;
}
/// <summary>
/// Get the frequency for a given range and total frequency
/// </summary>
private static ushort GetFrequency(ushort totalFrequency, ushort H, ushort L, ushort C)
{
uint range = (uint)(((H - L) & 0xFFFF) + 1);
uint freq = (uint)(((C - L + 1) * totalFrequency - 1) / range);
return (ushort)(freq & 0xFFFF);
}
/// <summary>
/// The decoder renormalization loop
/// </summary>
private static void GetCode(int previousFrequency,
int cumulativeFrequency,
int totalFrequency,
ref ushort H,
ref ushort L,
ref ushort C,
byte[] inbuf,
ref int inpos,
ref int bitsleft,
ref uint bitbuf)
{
uint range = (uint)((H - L) + 1);
H = (ushort)(L + ((previousFrequency * range) / totalFrequency) - 1);
L = (ushort)(L + (cumulativeFrequency * range) / totalFrequency);
while (true)
{
if ((L & 0x8000) != (H & 0x8000))
{
if ((L & 0x4000) == 0 || (H & 0x4000) != 0)
break;
// Underflow case
C ^= 0x4000;
L &= 0x3FFF;
H |= 0x4000;
}
L <<= 1;
H = (ushort)((H << 1) | 1);
C = (ushort)((C << 1) | Q_READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf));
}
}
#endregion
}
}

View File

@@ -1,193 +0,0 @@
using SabreTools.Models.Compression.Quantum;
namespace SabreTools.Compression.Quantum
{
/// <see href="https://github.com/kyz/libmspack/blob/master/libmspack/mspack/qtmd.c"/>
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
internal class State
{
/// <summary>
/// The actual decoding window
/// </summary>
public byte[] Window;
/// <summary>
/// Window size (1Kb through 2Mb)
/// </summary>
public uint WindowSize;
/// <summary>
/// Window size when it was first allocated
/// </summary>
public uint ActualSize;
/// <summary>
/// Current offset within the window
/// </summary>
public uint WindowPosition;
#region Models
/// <summary>
/// Symbol table for selector model
/// </summary>
public ModelSymbol[] SelectorModelSymbols = new ModelSymbol[7 + 1];
/// <summary>
/// Model for selector values
/// </summary>
public Model SelectorModel;
/// <summary>
/// Model for Selector 0
/// </summary>
public Model Model0;
/// <summary>
/// Model for Selector 1
/// </summary>
public Model Model1;
/// <summary>
/// Model for Selector 2
/// </summary>
public Model Model2;
/// <summary>
/// Model for Selector 3
/// </summary>
public Model Model3;
/// <summary>
/// Model for Selector 4
/// </summary>
public Model Model4;
/// <summary>
/// Model for Selector 5
/// </summary>
public Model Model5;
/// <summary>
/// Model for Selector 6 Position
/// </summary>
public Model Model6Position;
/// <summary>
/// Model for Selector 6 Length
/// </summary>
public Model Model6Length;
#endregion
#region Symbol Tables
/// <summary>
/// Symbol table for Selector 0
/// </summary>
public ModelSymbol[] Model0Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 1
/// </summary>
public ModelSymbol[] Model1Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 2
/// </summary>
public ModelSymbol[] Model2Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 3
/// </summary>
public ModelSymbol[] Model3Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 4
/// </summary>
public ModelSymbol[] Model4Symbols = new ModelSymbol[0x18 + 1];
/// <summary>
/// Symbol table for Selector 5
/// </summary>
public ModelSymbol[] Model5Symbols = new ModelSymbol[0x24 + 1];
/// <summary>
/// Symbol table for Selector 6 Position
/// </summary>
public ModelSymbol[] Model6PositionSymbols = new ModelSymbol[0x2a + 1];
/// <summary>
/// Symbol table for Selector 6 Length
/// </summary>
public ModelSymbol[] Model6LengthSymbols = new ModelSymbol[0x1b + 1];
#endregion
#region Decompression Tables
/// <summary>
/// An index to the position slot bases
/// </summary>
public uint[] PositionSlotBases = new uint[42]
{
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
};
/// <summary>
/// How many bits of offset-from-base data is needed
/// </summary>
public byte[] ExtraBits = new byte[42]
{
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
};
/// <summary>
/// An index to the position slot bases [Selector 6]
/// </summary>
public byte[] LengthBases = new byte[27]
{
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
};
/// <summary>
/// How many bits of offset-from-base data is needed [Selector 6]
/// </summary>
public byte[] LengthExtraBits = new byte[27]
{
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
};
#endregion
#region Decompression State
/// <summary>
/// Bit buffer to persist between runs
/// </summary>
public uint BitBuffer = 0;
/// <summary>
/// Bits remaining to persist between runs
/// </summary>
public int BitsLeft = 0;
#endregion
}
}

View File

@@ -9,6 +9,12 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
| Compression Name | Decompress | Compress |
| --- | --- | --- |
| LZ | Yes | No |
| LZX | Incomplete | No |
| MSZIP | Incomplete | No |
| Quantum | Incomplete | No |
| MSZIP | Yes* | 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

@@ -1,36 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net48;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>0.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PropertyGroup>
<!-- Assembly Properties -->
<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;osx-arm64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.4.2</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Clean compression implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2023</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Printing</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>compression decompression lz</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Clean compression implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Compression</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>compression decompression lz mszip</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.1.1" />
<PackageReference Include="SabreTools.Models" Version="1.1.2" />
</ItemGroup>
</Project>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.3.3" />
<PackageReference Include="SabreTools.Models" Version="1.4.2" />
</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();
}
}
}

9
zlib/LICENSE Normal file
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.

23
zlib/README.md Normal file
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.

178
zlib/ZlibDeflateStream.cs Normal file
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();
}
}
}

170
zlib/ZlibInflateStream.cs Normal file
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();
}
}
}

7101
zlib/zlib.cs Normal file

File diff suppressed because one or more lines are too long