mirror of
https://github.com/SabreTools/SabreTools.Compression.git
synced 2026-02-05 13:49:46 +00:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ 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.
|
||||
|
||||
@@ -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.3.0</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.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user