82 Commits

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

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

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

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

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

238
BitStream.cs Normal file
View File

@@ -0,0 +1,238 @@
using System;
using System.IO;
using SabreTools.IO;
namespace SabreTools.Compression
{
/// <summary>
/// Wrapper to allow reading bits from a source stream
/// </summary>
public class BitStream
{
/// <inheritdoc cref="Stream.Position"/>
public long Position => _source.Position;
/// <inheritdoc cref="Stream.Length"/>
public long Length => _source.Length;
/// <summary>
/// Original stream source
/// </summary>
private Stream _source;
/// <summary>
/// Last read byte value from the stream
/// </summary>
private byte? _bitBuffer;
/// <summary>
/// Index in the byte of the current bit
/// </summary>
private int _bitIndex;
/// <summary>
/// Create a new BitStream from a source Stream
/// </summary>
public BitStream(Stream? source)
{
if (source == null || !source.CanRead || !source.CanSeek)
throw new ArgumentException(nameof(source));
_source = source;
_bitBuffer = null;
_bitIndex = 0;
}
/// <summary>
/// Discard the current cached byte
/// </summary>
public void Discard()
{
_bitBuffer = null;
_bitIndex = 0;
}
/// <summary>
/// Read a single bit, if possible
/// </summary>
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
public byte? ReadBit()
{
// If we reached the end of the stream
if (_source.Position >= _source.Length)
return null;
// If we don't have a value cached
if (_bitBuffer == null)
{
// Read the next byte, if possible
_bitBuffer = ReadSourceByte();
if (_bitBuffer == null)
return null;
// Reset the bit index
_bitIndex = 0;
}
// Get the value by bit-shifting
int value = _bitBuffer.Value & 0x01;
_bitBuffer = (byte?)(_bitBuffer >> 1);
_bitIndex++;
// Reset the byte if we're at the end
if (_bitIndex >= 8)
Discard();
return (byte)value;
}
/// <summary>
/// Read a multiple bits in LSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsLSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (uint)(bitValue.Value << i);
}
return value;
}
/// <summary>
/// Read a multiple bits in MSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsMSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (value << 1) + bitValue.Value;
}
return value;
}
/// <summary>
/// Read a byte, if possible
/// </summary>
/// <returns>The next byte, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte? ReadByte()
{
try
{
Discard();
return _source.ReadByteValue();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt16, if possible
/// </summary>
/// <returns>The next UInt16, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ushort? ReadUInt16()
{
try
{
Discard();
return _source.ReadUInt16();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt32, if possible
/// </summary>
/// <returns>The next UInt32, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public uint? ReadUInt32()
{
try
{
Discard();
return _source.ReadUInt32();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt64, if possible
/// </summary>
/// <returns>The next UInt64, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ulong? ReadUInt64()
{
try
{
Discard();
return _source.ReadUInt64();
}
catch
{
return null;
}
}
/// <summary>
/// Read <paramref name="bytes"/> bytes, if possible
/// </summary>
/// <param name="bytes">Number of bytes to read</param>
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte[]? ReadBytes(int bytes)
{
try
{
Discard();
return _source.ReadBytes(bytes);
}
catch
{
return null;
}
}
/// <summary>
/// Read a single byte from the underlying stream, if possible
/// </summary>
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
private byte? ReadSourceByte()
{
try
{
return _source.ReadByteValue();
}
catch
{
return null;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

142
MSZIP/HuffmanDecoder.cs Normal file
View File

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

View File

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

View File

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

50
OldDotNet.cs Normal file
View File

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

51
Quantum/Constants.cs Normal file
View File

@@ -0,0 +1,51 @@
namespace SabreTools.Compression.Quantum
{
/// <see href="www.russotto.net/quantumcomp.html"/>
/// TODO: Remove this class when Models gets updated
public static class Constants
{
public static readonly int[] PositionSlot = new int[]
{
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
0x100000, 0x180000
};
public static readonly int[] PositionExtraBits = new int[]
{
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 17, 17, 18, 18,
19, 19
};
public static readonly int[] LengthSlot = new int[]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
0xbe, 0xde, 0xfe
};
public static readonly int[] LengthExtraBits = new int[]
{
0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 2, 2, 2, 2, 3, 3,
3, 3, 4, 4, 4, 4, 5, 5,
5, 5, 0
};
/// <summary>
/// Number of position slots for (tsize - 10)
/// </summary>
public static readonly int[] NumPositionSlots = new int[]
{
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
};
}
}

View File

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

View File

@@ -1,193 +0,0 @@
using SabreTools.Models.Compression.Quantum;
namespace SabreTools.Compression.Quantum
{
/// <see href="https://github.com/kyz/libmspack/blob/master/libmspack/mspack/qtmd.c"/>
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
internal class State
{
/// <summary>
/// The actual decoding window
/// </summary>
public byte[] Window;
/// <summary>
/// Window size (1Kb through 2Mb)
/// </summary>
public uint WindowSize;
/// <summary>
/// Window size when it was first allocated
/// </summary>
public uint ActualSize;
/// <summary>
/// Current offset within the window
/// </summary>
public uint WindowPosition;
#region Models
/// <summary>
/// Symbol table for selector model
/// </summary>
public ModelSymbol[] SelectorModelSymbols = new ModelSymbol[7 + 1];
/// <summary>
/// Model for selector values
/// </summary>
public Model SelectorModel;
/// <summary>
/// Model for Selector 0
/// </summary>
public Model Model0;
/// <summary>
/// Model for Selector 1
/// </summary>
public Model Model1;
/// <summary>
/// Model for Selector 2
/// </summary>
public Model Model2;
/// <summary>
/// Model for Selector 3
/// </summary>
public Model Model3;
/// <summary>
/// Model for Selector 4
/// </summary>
public Model Model4;
/// <summary>
/// Model for Selector 5
/// </summary>
public Model Model5;
/// <summary>
/// Model for Selector 6 Position
/// </summary>
public Model Model6Position;
/// <summary>
/// Model for Selector 6 Length
/// </summary>
public Model Model6Length;
#endregion
#region Symbol Tables
/// <summary>
/// Symbol table for Selector 0
/// </summary>
public ModelSymbol[] Model0Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 1
/// </summary>
public ModelSymbol[] Model1Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 2
/// </summary>
public ModelSymbol[] Model2Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 3
/// </summary>
public ModelSymbol[] Model3Symbols = new ModelSymbol[0x40 + 1];
/// <summary>
/// Symbol table for Selector 4
/// </summary>
public ModelSymbol[] Model4Symbols = new ModelSymbol[0x18 + 1];
/// <summary>
/// Symbol table for Selector 5
/// </summary>
public ModelSymbol[] Model5Symbols = new ModelSymbol[0x24 + 1];
/// <summary>
/// Symbol table for Selector 6 Position
/// </summary>
public ModelSymbol[] Model6PositionSymbols = new ModelSymbol[0x2a + 1];
/// <summary>
/// Symbol table for Selector 6 Length
/// </summary>
public ModelSymbol[] Model6LengthSymbols = new ModelSymbol[0x1b + 1];
#endregion
#region Decompression Tables
/// <summary>
/// An index to the position slot bases
/// </summary>
public uint[] PositionSlotBases = new uint[42]
{
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
0x100000, 0x180000
};
/// <summary>
/// How many bits of offset-from-base data is needed
/// </summary>
public byte[] ExtraBits = new byte[42]
{
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 17, 17, 18, 18,
19, 19
};
/// <summary>
/// An index to the position slot bases [Selector 6]
/// </summary>
public byte[] LengthBases = new byte[27]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
0xbe, 0xde, 0xfe
};
/// <summary>
/// How many bits of offset-from-base data is needed [Selector 6]
/// </summary>
public byte[] LengthExtraBits = new byte[27]
{
0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 2, 2, 2, 2, 3, 3,
3, 3, 4, 4, 4, 4, 5, 5,
5, 5, 0
};
#endregion
#region Decompression State
/// <summary>
/// Bit buffer to persist between runs
/// </summary>
public uint BitBuffer = 0;
/// <summary>
/// Bits remaining to persist between runs
/// </summary>
public int BitsLeft = 0;
#endregion
}
}

View File

@@ -9,6 +9,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.

View File

@@ -1,36 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net48;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>0.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.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>