mirror of
https://github.com/SabreTools/SabreTools.Compression.git
synced 2026-02-05 05:37:41 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a916cb9954 | ||
|
|
a6ef762a73 | ||
|
|
7aac1e0bed | ||
|
|
bc569964d8 | ||
|
|
a3ac98a9f4 | ||
|
|
57c0f9b747 | ||
|
|
38f3ea1c98 | ||
|
|
ab36802840 | ||
|
|
422bda1830 | ||
|
|
1c989985d9 | ||
|
|
62c6e79ad3 | ||
|
|
6bbf521828 | ||
|
|
7a4e2f0ee0 | ||
|
|
1d1a6f5976 | ||
|
|
065b68124b | ||
|
|
07b50e8c46 | ||
|
|
8eb82384d6 | ||
|
|
dd6cc0e2f3 | ||
|
|
58502e0362 | ||
|
|
f3bf1082d3 | ||
|
|
7958b24a36 | ||
|
|
3010a0523c | ||
|
|
d7670ae685 | ||
|
|
2d09d9696a | ||
|
|
d75883a6cf | ||
|
|
8e5cf3ee2e | ||
|
|
e739fd6fd5 | ||
|
|
6b238df5dc | ||
|
|
3e3a0e122b | ||
|
|
cb6e157cb4 | ||
|
|
12466d7083 | ||
|
|
47cb06cf34 | ||
|
|
c152cba81d | ||
|
|
4684a6612c | ||
|
|
a58da1d8db | ||
|
|
8f098a6669 | ||
|
|
8c5482a59a | ||
|
|
b1f1863e9a | ||
|
|
8ab555d6fc | ||
|
|
32b2f6c443 | ||
|
|
44f1544725 | ||
|
|
471cbc5707 | ||
|
|
5b785fb28f | ||
|
|
38dd2a5caf | ||
|
|
5e21a09fd1 | ||
|
|
8174af616f | ||
|
|
297fffe8d7 | ||
|
|
bd9258d9fa | ||
|
|
b7a081824c | ||
|
|
9617e5c583 | ||
|
|
ec40e759a9 | ||
|
|
15bf2001b5 | ||
|
|
81eab984fb | ||
|
|
47691d2034 | ||
|
|
dde90a852d | ||
|
|
2ce175af39 | ||
|
|
3353264090 | ||
|
|
5477afaf1e | ||
|
|
9229e1b5f7 | ||
|
|
82223f3ee4 | ||
|
|
a69d3a5bb2 | ||
|
|
67d49ac4c0 | ||
|
|
568d6f9e72 | ||
|
|
d014d57750 | ||
|
|
c71f73b109 | ||
|
|
d6d8b2d9de | ||
|
|
8227d9637c | ||
|
|
a7cfb47dbe | ||
|
|
f23078d792 | ||
|
|
3f4de3ee67 | ||
|
|
9d9f03c283 | ||
|
|
b4650010e0 | ||
|
|
a3dae1b5e4 | ||
|
|
82ea2ca03c | ||
|
|
0eb17b9e26 | ||
|
|
2413b5feeb | ||
|
|
48f8397c81 | ||
|
|
c77b7c0a5b | ||
|
|
798eb64daa | ||
|
|
dc467def95 | ||
|
|
6dbe6dbcbc | ||
|
|
0f140d6a1f | ||
|
|
fe2ea2b102 | ||
|
|
3fd69dc1b2 | ||
|
|
472777a1cf | ||
|
|
f47434bc3b | ||
|
|
04af8d3f79 | ||
|
|
327a97e523 | ||
|
|
80bd06f9e3 | ||
|
|
b561385b58 | ||
|
|
3e343211aa |
43
.github/workflows/build_nupkg.yml
vendored
Normal file
43
.github/workflows/build_nupkg.yml
vendored
Normal 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
17
.github/workflows/check_pr.yml
vendored
Normal 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
238
BitStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
12
LZX/Bits.cs
12
LZX/Bits.cs
@@ -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*
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
119
LZX/State.cs
119
LZX/State.cs
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
380
MSZIP/DeflateDecompressor.cs
Normal file
380
MSZIP/DeflateDecompressor.cs
Normal 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
142
MSZIP/HuffmanDecoder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
50
OldDotNet.cs
Normal 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
51
Quantum/Constants.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
193
Quantum/State.cs
193
Quantum/State.cs
@@ -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
|
||||
}
|
||||
}
|
||||
12
README.MD
12
README.MD
@@ -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 |
|
||||
|
||||
@@ -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>
|
||||
|
||||
218
zlib/Hebron.Runtime/CRuntime.cs
Normal file
218
zlib/Hebron.Runtime/CRuntime.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public static unsafe class CRuntime
|
||||
{
|
||||
private static readonly string numbers = "0123456789";
|
||||
|
||||
public static void* malloc(ulong size)
|
||||
{
|
||||
return malloc((long)size);
|
||||
}
|
||||
|
||||
public static void* malloc(long size)
|
||||
{
|
||||
var ptr = Marshal.AllocHGlobal((int)size);
|
||||
|
||||
MemoryStats.Allocated();
|
||||
|
||||
return ptr.ToPointer();
|
||||
}
|
||||
|
||||
public static void free(void* a)
|
||||
{
|
||||
if (a == null)
|
||||
return;
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
MemoryStats.Freed();
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, long size)
|
||||
{
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*ap++ = *bp++;
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, ulong size)
|
||||
{
|
||||
memcpy(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, long size)
|
||||
{
|
||||
void* temp = null;
|
||||
|
||||
try
|
||||
{
|
||||
temp = malloc(size);
|
||||
memcpy(temp, b, size);
|
||||
memcpy(a, temp, size);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (temp != null)
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, ulong size)
|
||||
{
|
||||
memmove(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, long size)
|
||||
{
|
||||
var result = 0;
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
{
|
||||
if (*ap != *bp)
|
||||
result += 1;
|
||||
|
||||
ap++;
|
||||
bp++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, ulong size)
|
||||
{
|
||||
return memcmp(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(byte* a, byte[] b, ulong size)
|
||||
{
|
||||
fixed (void* bptr = b)
|
||||
{
|
||||
return memcmp(a, bptr, (long)size);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, long size)
|
||||
{
|
||||
var bptr = (byte*)ptr;
|
||||
var bval = (byte)value;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*bptr++ = bval;
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, ulong size)
|
||||
{
|
||||
memset(ptr, value, (long)size);
|
||||
}
|
||||
|
||||
public static uint _lrotl(uint x, int y)
|
||||
{
|
||||
return (x << y) | (x >> (32 - y));
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, long newSize)
|
||||
{
|
||||
if (a == null)
|
||||
return malloc(newSize);
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
var result = Marshal.ReAllocHGlobal(ptr, new IntPtr(newSize));
|
||||
|
||||
return result.ToPointer();
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, ulong newSize)
|
||||
{
|
||||
return realloc(a, (long)newSize);
|
||||
}
|
||||
|
||||
public static int abs(int v)
|
||||
{
|
||||
return Math.Abs(v);
|
||||
}
|
||||
|
||||
public static double pow(double a, double b)
|
||||
{
|
||||
return Math.Pow(a, b);
|
||||
}
|
||||
|
||||
public static void SetArray<T>(T[] data, T value)
|
||||
{
|
||||
for (var i = 0; i < data.Length; ++i)
|
||||
data[i] = value;
|
||||
}
|
||||
|
||||
public static double ldexp(double number, int exponent)
|
||||
{
|
||||
return number * Math.Pow(2, exponent);
|
||||
}
|
||||
|
||||
public static int strcmp(sbyte* src, string token)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < token.Length; ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int strncmp(sbyte* src, string token, ulong size)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < Math.Min(token.Length, (int)size); ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long strtol(sbyte* start, sbyte** end, int radix)
|
||||
{
|
||||
// First step - determine length
|
||||
var length = 0;
|
||||
sbyte* ptr = start;
|
||||
while (numbers.IndexOf((char)*ptr) != -1)
|
||||
{
|
||||
++ptr;
|
||||
++length;
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
|
||||
// Now build up the number
|
||||
ptr = start;
|
||||
while (length > 0)
|
||||
{
|
||||
long num = numbers.IndexOf((char)*ptr);
|
||||
long pow = (long)Math.Pow(10, length - 1);
|
||||
result += num * pow;
|
||||
|
||||
++ptr;
|
||||
--length;
|
||||
}
|
||||
|
||||
if (end != null)
|
||||
{
|
||||
*end = ptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
zlib/Hebron.Runtime/MemoryStats.cs
Normal file
28
zlib/Hebron.Runtime/MemoryStats.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe static class MemoryStats
|
||||
{
|
||||
private static int _allocations;
|
||||
|
||||
public static int Allocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allocations;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Allocated()
|
||||
{
|
||||
Interlocked.Increment(ref _allocations);
|
||||
}
|
||||
|
||||
internal static void Freed()
|
||||
{
|
||||
Interlocked.Decrement(ref _allocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
88
zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray1D<T> where T : struct
|
||||
{
|
||||
private readonly T[] _data;
|
||||
private readonly GCHandle _pinHandle;
|
||||
public bool IsFreed { get; private set; }
|
||||
|
||||
internal GCHandle PinHandle => _pinHandle;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T this[uint index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Data => _data;
|
||||
|
||||
public UnsafeArray1D(int size)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(size));
|
||||
}
|
||||
|
||||
_data = new T[size];
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public UnsafeArray1D(T[] data, int sizeOf)
|
||||
{
|
||||
if (sizeOf <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOf));
|
||||
}
|
||||
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
if (!IsFreed)
|
||||
{
|
||||
_pinHandle.Free();
|
||||
IsFreed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UnsafeArray1D()
|
||||
{
|
||||
if (!IsFreed)
|
||||
_pinHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer()
|
||||
{
|
||||
return _pinHandle.AddrOfPinnedObject().ToPointer();
|
||||
}
|
||||
|
||||
public static implicit operator void*(UnsafeArray1D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
|
||||
public static void* operator +(UnsafeArray1D<T> array, int delta)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
45
zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray2D<T> where T : struct
|
||||
{
|
||||
private readonly UnsafeArray1D<T>[] _data;
|
||||
private long[] _pinAddresses;
|
||||
private readonly GCHandle _pinAddressesHandle;
|
||||
|
||||
public UnsafeArray1D<T> this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UnsafeArray2D(int size1, int size2)
|
||||
{
|
||||
_data = new UnsafeArray1D<T>[size1];
|
||||
_pinAddresses = new long[size1];
|
||||
for (var i = 0; i < size1; ++i)
|
||||
{
|
||||
_data[i] = new UnsafeArray1D<T>(size2);
|
||||
_pinAddresses[i] = _data[i].PinHandle.AddrOfPinnedObject().ToInt64();
|
||||
}
|
||||
|
||||
_pinAddressesHandle = GCHandle.Alloc(_pinAddresses, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
~UnsafeArray2D()
|
||||
{
|
||||
_pinAddressesHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer() => _pinAddressesHandle.AddrOfPinnedObject().ToPointer();
|
||||
|
||||
public static implicit operator void*(UnsafeArray2D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
zlib/LICENSE
Normal file
9
zlib/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Nanook
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
23
zlib/README.md
Normal file
23
zlib/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
Project to port ZLib from C to C# (CSharp).
|
||||
|
||||
Src zlib 1.2.12 2022-Mar-28 - https://github.com/madler/zlib
|
||||
|
||||
See the Stages folder
|
||||
|
||||
1_zlib.c - Created by running 1_zlib.c_Concat.ps1 Builds with Clang (used by hebron to convert)
|
||||
- Only deflate, inflate, crc32 and adler32 code at the moment. GZip might be added if required.
|
||||
- The only edits to these files are to remove any #includes that have been combined
|
||||
- The file list includes a 000_ to insert any #defines etc and 100_ for a main for debugging etc
|
||||
- Notice crc32.c and trees.c had to be split to allow the single file to build
|
||||
|
||||
2_zlib.cs_Converted - The converted output that Hebron produced - https://github.com/HebronFramework/Hebron
|
||||
- This is a little app that uses Clang to read the C code as DOM and write with Roslyn
|
||||
- It does a fairly decent job and removes a lot of complication
|
||||
|
||||
3_zlib.cs_Working - The fixed up and amended C# that actually runs and matches the C code output
|
||||
- It's had minimal change so is not the prettiest C# code
|
||||
- It's Unsafe in places
|
||||
|
||||
Deflate and Inflate streams have been added.
|
||||
178
zlib/ZlibDeflateStream.cs
Normal file
178
zlib/ZlibDeflateStream.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibDeflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream) : this(level, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream, bool leaveOpen) : this(level, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, bool headerless, Stream baseStream, bool leaveOpen) : this(level, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, int bufferSize, Stream baseStream, bool leaveOpen) : this(level, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.Level = level;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
|
||||
public int Level { get; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.deflateInit_(_s, this.Level, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
hdr = 2;
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
_s.avail_in = (uint)count;
|
||||
|
||||
fixed (byte* i = buffer, o = _b)
|
||||
{
|
||||
_s.next_in = i;
|
||||
_s.next_out = o + _s.total_out;
|
||||
|
||||
while (err >= 0 && _s.avail_in != 0) //process the buffer
|
||||
{
|
||||
if (_s.avail_out == 0) //get more data
|
||||
{
|
||||
if (_s.total_out != 0)
|
||||
{
|
||||
if (hdr != 0)
|
||||
{
|
||||
BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.total_out -= (uint)hdr;
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
BaseStream.Write(_b!, 0, (int)_s.total_out);
|
||||
}
|
||||
_p += _s.total_out;
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || _s.avail_out != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be written to the base stream. Call when write is finished with.
|
||||
/// Used for creating block seekable files. The caller must manage blocks, indexes and lengths
|
||||
/// </summary>
|
||||
unsafe public long BlockFlush()
|
||||
{
|
||||
//finish previous stream
|
||||
if (_s != null)
|
||||
{
|
||||
int err = 0;
|
||||
fixed (byte* o = _b)
|
||||
{
|
||||
_s.next_in = null;
|
||||
_s.avail_in = 0;
|
||||
_s.next_out = o + _s.total_out; //point to correct location
|
||||
|
||||
int hdr = _p == 0 && Headerless ? 2 : 0;
|
||||
while (err == 0 && (_s.total_out != 0 || _s.state!.pending != 0))
|
||||
{
|
||||
this.BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_p += _s.total_out - hdr;
|
||||
hdr = 0;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
if (_s.state!.pending != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
err = ZLib.deflate(_s, 4);
|
||||
|
||||
}
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsafe protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.BlockFlush();
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
zlib/ZlibInflateStream.cs
Normal file
170
zlib/ZlibInflateStream.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibInflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
private bool _complete;
|
||||
|
||||
public ZlibInflateStream(Stream baseStream) : this(0, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(Stream baseStream, bool leaveOpen) : this(0, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, Stream baseStream, bool leaveOpen) : this(0, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, int bufferSize, Stream baseStream, bool leaveOpen) : this(0, headerless, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(int bufferSize, Stream baseStream, bool leaveOpen) : this(0, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(long maxRead, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.MaxRead = maxRead == 0 ? int.MaxValue : maxRead;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
_complete = false;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
public long MaxRead { get; private set; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
if (_complete)
|
||||
return 0;
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.inflateInit_(_s, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
{
|
||||
_b![0] = 0x78;
|
||||
_b[1] = 0x9c; //da
|
||||
hdr = 2;
|
||||
}
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
int read;
|
||||
_s.avail_out = (uint)count;
|
||||
|
||||
fixed (byte* i = _b, o = buffer)
|
||||
{
|
||||
_s.next_in = i + _s.total_in;
|
||||
_s.next_out = o;
|
||||
|
||||
while (err == 0 && (_s.avail_out != 0 && !_complete)) //process the buffer
|
||||
{
|
||||
if (_s.avail_in == 0) //get more data
|
||||
{
|
||||
_s.total_in = 0;
|
||||
read = (int)Math.Min(this.MaxRead - _p, (long)_b!.Length);
|
||||
if (hdr != 0) //test once to save on the extra calculations
|
||||
{
|
||||
_s.avail_in = (uint)(hdr + (read = BaseStream.Read(_b, hdr, Math.Min(read, _b.Length - hdr))));
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
_s.avail_in = (uint)(read = BaseStream.Read(_b, 0, read));
|
||||
_complete = read == 0;
|
||||
_p += (long)read;
|
||||
_s.next_in = i;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || (!_complete && _s.total_out != 0))
|
||||
err = ZLib.inflate(_s, 2);
|
||||
}
|
||||
}
|
||||
|
||||
uint ret = _s.total_out;
|
||||
_s.total_out = 0u;
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be read from the base stream without overreading. Call when write is finished with.
|
||||
/// Used for reading block seekable files. The caller must manage blocks, indexes and lengths. Seek the BaseStream
|
||||
/// </summary>
|
||||
public long BlockFlush(int maxRead)
|
||||
{
|
||||
this.MaxRead = maxRead;
|
||||
if (_s != null)
|
||||
{
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
_complete = false;
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
BlockFlush(0);
|
||||
_complete = true;
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
7101
zlib/zlib.cs
Normal file
7101
zlib/zlib.cs
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user