mirror of
https://github.com/SabreTools/SabreTools.Compression.git
synced 2026-02-06 05:35:30 +00:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e217f4109c | ||
|
|
882f2c5335 | ||
|
|
3f6c7dc0d6 | ||
|
|
bf413cbb85 | ||
|
|
7a403cf368 | ||
|
|
aec4611d14 | ||
|
|
7052584cea | ||
|
|
efe6c545b9 | ||
|
|
612a8b3c83 | ||
|
|
bc06cb5bdb | ||
|
|
ae223a4589 | ||
|
|
018fd01922 | ||
|
|
910b01b072 | ||
|
|
d239d9f09b | ||
|
|
0cf3e3e816 | ||
|
|
fe319b71f1 | ||
|
|
cd5bf99f21 | ||
|
|
f79b5353f7 | ||
|
|
5e184b03c5 | ||
|
|
a1417d2f8a | ||
|
|
7580d49830 | ||
|
|
c04c7d438d | ||
|
|
a0f602ed6f | ||
|
|
4bc7f53c9f | ||
|
|
049a8cf499 | ||
|
|
9a8875f1e0 | ||
|
|
a916cb9954 | ||
|
|
a6ef762a73 | ||
|
|
7aac1e0bed | ||
|
|
bc569964d8 | ||
|
|
a3ac98a9f4 | ||
|
|
57c0f9b747 | ||
|
|
38f3ea1c98 | ||
|
|
ab36802840 | ||
|
|
422bda1830 | ||
|
|
1c989985d9 | ||
|
|
62c6e79ad3 | ||
|
|
6bbf521828 | ||
|
|
7a4e2f0ee0 | ||
|
|
1d1a6f5976 | ||
|
|
065b68124b | ||
|
|
07b50e8c46 | ||
|
|
8eb82384d6 | ||
|
|
dd6cc0e2f3 | ||
|
|
58502e0362 | ||
|
|
f3bf1082d3 | ||
|
|
7958b24a36 | ||
|
|
3010a0523c | ||
|
|
d7670ae685 | ||
|
|
2d09d9696a | ||
|
|
d75883a6cf | ||
|
|
8e5cf3ee2e | ||
|
|
e739fd6fd5 | ||
|
|
6b238df5dc | ||
|
|
3e3a0e122b | ||
|
|
cb6e157cb4 | ||
|
|
12466d7083 | ||
|
|
47cb06cf34 | ||
|
|
c152cba81d | ||
|
|
4684a6612c | ||
|
|
a58da1d8db | ||
|
|
8f098a6669 | ||
|
|
8c5482a59a | ||
|
|
b1f1863e9a | ||
|
|
8ab555d6fc | ||
|
|
32b2f6c443 | ||
|
|
44f1544725 | ||
|
|
471cbc5707 | ||
|
|
5b785fb28f | ||
|
|
38dd2a5caf | ||
|
|
5e21a09fd1 | ||
|
|
8174af616f | ||
|
|
297fffe8d7 | ||
|
|
bd9258d9fa | ||
|
|
b7a081824c | ||
|
|
9617e5c583 | ||
|
|
ec40e759a9 | ||
|
|
15bf2001b5 | ||
|
|
81eab984fb | ||
|
|
47691d2034 | ||
|
|
dde90a852d | ||
|
|
2ce175af39 | ||
|
|
3353264090 | ||
|
|
5477afaf1e | ||
|
|
9229e1b5f7 | ||
|
|
82223f3ee4 | ||
|
|
a69d3a5bb2 | ||
|
|
67d49ac4c0 | ||
|
|
568d6f9e72 | ||
|
|
d014d57750 | ||
|
|
c71f73b109 | ||
|
|
d6d8b2d9de | ||
|
|
8227d9637c | ||
|
|
a7cfb47dbe | ||
|
|
f23078d792 | ||
|
|
3f4de3ee67 | ||
|
|
9d9f03c283 | ||
|
|
b4650010e0 | ||
|
|
a3dae1b5e4 | ||
|
|
82ea2ca03c | ||
|
|
0eb17b9e26 | ||
|
|
2413b5feeb | ||
|
|
48f8397c81 | ||
|
|
c77b7c0a5b | ||
|
|
798eb64daa | ||
|
|
dc467def95 | ||
|
|
6dbe6dbcbc | ||
|
|
0f140d6a1f | ||
|
|
fe2ea2b102 | ||
|
|
3fd69dc1b2 | ||
|
|
472777a1cf | ||
|
|
f47434bc3b | ||
|
|
04af8d3f79 | ||
|
|
327a97e523 | ||
|
|
80bd06f9e3 | ||
|
|
b561385b58 | ||
|
|
3e343211aa |
43
.github/workflows/build_nupkg.yml
vendored
Normal file
43
.github/workflows/build_nupkg.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Nuget Pack
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
17
.github/workflows/check_pr.yml
vendored
Normal file
17
.github/workflows/check_pr.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Build PR
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
12
LZX/Bits.cs
12
LZX/Bits.cs
@@ -1,12 +0,0 @@
|
||||
namespace SabreTools.Compression.LZX
|
||||
{
|
||||
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
|
||||
internal class Bits
|
||||
{
|
||||
public uint BitBuffer;
|
||||
|
||||
public int BitsLeft;
|
||||
|
||||
public int InputPosition; //byte*
|
||||
}
|
||||
}
|
||||
@@ -1,759 +0,0 @@
|
||||
using System;
|
||||
using SabreTools.Compression.LZX;
|
||||
using static SabreTools.Models.Compression.LZX.Constants;
|
||||
using static SabreTools.Models.MicrosoftCabinet.Constants;
|
||||
|
||||
namespace SabreTools.Compression.LZX
|
||||
{
|
||||
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/fdi.c"/>
|
||||
internal class Decompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize an LZX decompressor state
|
||||
/// </summary>
|
||||
public static bool Init(int window, State state)
|
||||
{
|
||||
uint wndsize = (uint)(1 << window);
|
||||
int posn_slots;
|
||||
|
||||
/* LZX supports window sizes of 2^15 (32Kb) through 2^21 (2Mb) */
|
||||
/* if a previously allocated window is big enough, keep it */
|
||||
if (window < 15 || window > 21)
|
||||
return false;
|
||||
|
||||
if (state.actual_size < wndsize)
|
||||
state.window = null;
|
||||
|
||||
if (state.window == null)
|
||||
{
|
||||
state.window = new byte[wndsize];
|
||||
state.actual_size = wndsize;
|
||||
}
|
||||
|
||||
state.window_size = wndsize;
|
||||
|
||||
/* calculate required position slots */
|
||||
if (window == 20) posn_slots = 42;
|
||||
else if (window == 21) posn_slots = 50;
|
||||
else posn_slots = window << 1;
|
||||
|
||||
/*posn_slots=i=0; while (i < wndsize) i += 1 << CAB(extra_bits)[posn_slots++]; */
|
||||
|
||||
state.R0 = state.R1 = state.R2 = 1;
|
||||
state.main_elements = (ushort)(LZX_NUM_CHARS + (posn_slots << 3));
|
||||
state.header_read = 0;
|
||||
state.frames_read = 0;
|
||||
state.block_remaining = 0;
|
||||
state.block_type = LZX_BLOCKTYPE_INVALID;
|
||||
state.intel_curpos = 0;
|
||||
state.intel_started = 0;
|
||||
state.window_posn = 0;
|
||||
|
||||
/* initialize tables to 0 (because deltas will be applied to them) */
|
||||
// memset(state.MAINTREE_len, 0, sizeof(state.MAINTREE_len));
|
||||
// memset(state.LENGTH_len, 0, sizeof(state.LENGTH_len));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a byte array using a given State
|
||||
/// </summary>
|
||||
public static bool Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
|
||||
{
|
||||
int inpos = 0; // inbuf[0];
|
||||
int endinp = inpos + inlen;
|
||||
int window = 0; // state.window[0];
|
||||
int runsrc, rundest; // byte*
|
||||
|
||||
uint window_posn = state.window_posn;
|
||||
uint window_size = state.window_size;
|
||||
uint R0 = state.R0;
|
||||
uint R1 = state.R1;
|
||||
uint R2 = state.R2;
|
||||
|
||||
uint match_offset, i, j, k; /* ijk used in READ_HUFFSYM macro */
|
||||
Bits lb = new Bits(); /* used in READ_LENGTHS macro */
|
||||
|
||||
int togo = outlen, this_run, main_element, aligned_bits;
|
||||
int match_length, copy_length, length_footer, extra, verbatim_bits;
|
||||
|
||||
INIT_BITSTREAM(out int bitsleft, out uint bitbuf);
|
||||
|
||||
/* read header if necessary */
|
||||
if (state.header_read == 0)
|
||||
{
|
||||
i = j = 0;
|
||||
k = READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
if (k != 0)
|
||||
{
|
||||
i = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
j = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
}
|
||||
|
||||
state.intel_filesize = (int)((i << 16) | j); /* or 0 if not encoded */
|
||||
state.header_read = 1;
|
||||
}
|
||||
|
||||
/* main decoding loop */
|
||||
while (togo > 0)
|
||||
{
|
||||
/* last block finished, new block expected */
|
||||
if (state.block_remaining == 0)
|
||||
{
|
||||
if (state.block_type == LZX_BLOCKTYPE_UNCOMPRESSED)
|
||||
{
|
||||
if ((state.block_length & 1) != 0)
|
||||
inpos++; /* realign bitstream to word */
|
||||
|
||||
INIT_BITSTREAM(out bitsleft, out bitbuf);
|
||||
}
|
||||
|
||||
state.block_type = (ushort)READ_BITS(3, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
i = READ_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
j = READ_BITS(8, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
state.block_remaining = state.block_length = (i << 8) | j;
|
||||
|
||||
switch (state.block_type)
|
||||
{
|
||||
case LZX_BLOCKTYPE_ALIGNED:
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
j = READ_BITS(3, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
state.tblALIGNED_len[i] = (byte)j;
|
||||
}
|
||||
|
||||
make_decode_table(LZX_ALIGNED_MAXSYMBOLS, LZX_ALIGNED_TABLEBITS, state.tblALIGNED_len, state.tblALIGNED_table);
|
||||
|
||||
/* rest of aligned header is same as verbatim */
|
||||
goto case LZX_BLOCKTYPE_VERBATIM;
|
||||
|
||||
case LZX_BLOCKTYPE_VERBATIM:
|
||||
READ_LENGTHS(state.tblMAINTREE_len, 0, 256, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
READ_LENGTHS(state.tblMAINTREE_len, 256, state.main_elements, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
make_decode_table(LZX_MAINTREE_MAXSYMBOLS, LZX_MAINTREE_TABLEBITS, state.tblMAINTREE_len, state.tblMAINTREE_table);
|
||||
if (state.tblMAINTREE_len[0xE8] != 0)
|
||||
state.intel_started = 1;
|
||||
|
||||
READ_LENGTHS(state.tblLENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS, lb, state, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
make_decode_table(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, state.tblLENGTH_len, state.tblLENGTH_table);
|
||||
break;
|
||||
|
||||
case LZX_BLOCKTYPE_UNCOMPRESSED:
|
||||
state.intel_started = 1; /* because we can't assume otherwise */
|
||||
ENSURE_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf); /* get up to 16 pad bits into the buffer */
|
||||
|
||||
/* and align the bitstream! */
|
||||
if (bitsleft > 16)
|
||||
inpos -= 2;
|
||||
|
||||
R0 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
|
||||
R1 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
|
||||
R2 = (uint)(inbuf[inpos + 0] | (inbuf[inpos + 1] << 8) | (inbuf[inpos + 2] << 16) | (inbuf[inpos + 3] << 24)); inpos += 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* buffer exhaustion check */
|
||||
if (inpos > endinp)
|
||||
{
|
||||
/* it's possible to have a file where the next run is less than
|
||||
* 16 bits in size. In this case, the READ_HUFFSYM() macro used
|
||||
* in building the tables will exhaust the buffer, so we should
|
||||
* allow for this, but not allow those accidentally read bits to
|
||||
* be used (so we check that there are at least 16 bits
|
||||
* remaining - in this boundary case they aren't really part of
|
||||
* the compressed data)
|
||||
*/
|
||||
if (inpos > (endinp + 2) || bitsleft < 16)
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((this_run = (int)state.block_remaining) > 0 && togo > 0)
|
||||
{
|
||||
if (this_run > togo) this_run = togo;
|
||||
togo -= this_run;
|
||||
state.block_remaining -= (uint)this_run;
|
||||
|
||||
/* apply 2^x-1 mask */
|
||||
window_posn &= window_size - 1;
|
||||
|
||||
/* runs can't straddle the window wraparound */
|
||||
if ((window_posn + this_run) > window_size)
|
||||
return false;
|
||||
|
||||
switch (state.block_type)
|
||||
{
|
||||
|
||||
case LZX_BLOCKTYPE_VERBATIM:
|
||||
while (this_run > 0)
|
||||
{
|
||||
main_element = READ_HUFFSYM(state.tblMAINTREE_table, state.tblMAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
if (main_element < LZX_NUM_CHARS)
|
||||
{
|
||||
/* literal: 0 to LZX_NUM_CHARS-1 */
|
||||
state.window[window + window_posn++] = (byte)main_element;
|
||||
this_run--;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
|
||||
main_element -= LZX_NUM_CHARS;
|
||||
|
||||
match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;
|
||||
if (match_length == LZX_NUM_PRIMARY_LENGTHS)
|
||||
{
|
||||
length_footer = READ_HUFFSYM(state.tblLENGTH_table, state.tblLENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_length += length_footer;
|
||||
}
|
||||
|
||||
match_length += LZX_MIN_MATCH;
|
||||
match_offset = (uint)(main_element >> 3);
|
||||
|
||||
if (match_offset > 2)
|
||||
{
|
||||
/* not repeated offset */
|
||||
if (match_offset != 3)
|
||||
{
|
||||
extra = state.ExtraBits[match_offset];
|
||||
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_offset = (uint)(state.PositionSlotBases[match_offset] - 2 + verbatim_bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
match_offset = 1;
|
||||
}
|
||||
|
||||
/* update repeated offset LRU queue */
|
||||
R2 = R1; R1 = R0; R0 = match_offset;
|
||||
}
|
||||
else if (match_offset == 0)
|
||||
{
|
||||
match_offset = R0;
|
||||
}
|
||||
else if (match_offset == 1)
|
||||
{
|
||||
match_offset = R1;
|
||||
R1 = R0; R0 = match_offset;
|
||||
}
|
||||
else /* match_offset == 2 */
|
||||
{
|
||||
match_offset = R2;
|
||||
R2 = R0; R0 = match_offset;
|
||||
}
|
||||
|
||||
rundest = (int)(window + window_posn);
|
||||
this_run -= match_length;
|
||||
|
||||
/* copy any wrapped around source data */
|
||||
if (window_posn >= match_offset)
|
||||
{
|
||||
/* no wrap */
|
||||
runsrc = (int)(rundest - match_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
runsrc = (int)(rundest + (window_size - match_offset));
|
||||
copy_length = (int)(match_offset - window_posn);
|
||||
if (copy_length < match_length)
|
||||
{
|
||||
match_length -= copy_length;
|
||||
window_posn += (uint)copy_length;
|
||||
while (copy_length-- > 0)
|
||||
{
|
||||
state.window[rundest++] = state.window[runsrc++];
|
||||
}
|
||||
|
||||
runsrc = window;
|
||||
}
|
||||
}
|
||||
|
||||
window_posn += (uint)match_length;
|
||||
|
||||
/* copy match data - no worries about destination wraps */
|
||||
while (match_length-- > 0)
|
||||
{
|
||||
state.window[rundest++] = state.window[runsrc++];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LZX_BLOCKTYPE_ALIGNED:
|
||||
while (this_run > 0)
|
||||
{
|
||||
main_element = READ_HUFFSYM(state.tblMAINTREE_table, state.tblMAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
|
||||
if (main_element < LZX_NUM_CHARS)
|
||||
{
|
||||
/* literal: 0 to LZX_NUM_CHARS-1 */
|
||||
state.window[window + window_posn++] = (byte)main_element;
|
||||
this_run--;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* mverbatim_bitsatch: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
|
||||
main_element -= LZX_NUM_CHARS;
|
||||
|
||||
match_length = main_element & LZX_NUM_PRIMARY_LENGTHS;
|
||||
if (match_length == LZX_NUM_PRIMARY_LENGTHS)
|
||||
{
|
||||
length_footer = READ_HUFFSYM(state.tblLENGTH_table, state.tblLENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_length += length_footer;
|
||||
}
|
||||
match_length += LZX_MIN_MATCH;
|
||||
|
||||
match_offset = (uint)(main_element >> 3);
|
||||
|
||||
if (match_offset > 2)
|
||||
{
|
||||
/* not repeated offset */
|
||||
extra = state.ExtraBits[match_offset];
|
||||
match_offset = state.PositionSlotBases[match_offset] - 2;
|
||||
if (extra > 3)
|
||||
{
|
||||
/* verbatim and aligned bits */
|
||||
extra -= 3;
|
||||
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_offset += (uint)(verbatim_bits << 3);
|
||||
aligned_bits = READ_HUFFSYM(state.tblALIGNED_table, state.tblALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_offset += (uint)aligned_bits;
|
||||
}
|
||||
else if (extra == 3)
|
||||
{
|
||||
/* aligned bits only */
|
||||
aligned_bits = READ_HUFFSYM(state.tblALIGNED_table, state.tblALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_offset += (uint)aligned_bits;
|
||||
}
|
||||
else if (extra > 0)
|
||||
{
|
||||
/* extra==1, extra==2 */
|
||||
/* verbatim bits only */
|
||||
verbatim_bits = (int)READ_BITS(extra, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
match_offset += (uint)verbatim_bits;
|
||||
}
|
||||
else /* extra == 0 */
|
||||
{
|
||||
/* ??? */
|
||||
match_offset = 1;
|
||||
}
|
||||
|
||||
/* update repeated offset LRU queue */
|
||||
R2 = R1; R1 = R0; R0 = match_offset;
|
||||
}
|
||||
else if (match_offset == 0)
|
||||
{
|
||||
match_offset = R0;
|
||||
}
|
||||
else if (match_offset == 1)
|
||||
{
|
||||
match_offset = R1;
|
||||
R1 = R0; R0 = match_offset;
|
||||
}
|
||||
else /* match_offset == 2 */
|
||||
{
|
||||
match_offset = R2;
|
||||
R2 = R0; R0 = match_offset;
|
||||
}
|
||||
|
||||
rundest = (int)(window + window_posn);
|
||||
this_run -= match_length;
|
||||
|
||||
/* copy any wrapped around source data */
|
||||
if (window_posn >= match_offset)
|
||||
{
|
||||
/* no wrap */
|
||||
runsrc = (int)(rundest - match_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
runsrc = (int)(rundest + (window_size - match_offset));
|
||||
copy_length = (int)(match_offset - window_posn);
|
||||
if (copy_length < match_length)
|
||||
{
|
||||
match_length -= copy_length;
|
||||
window_posn += (uint)copy_length;
|
||||
while (copy_length-- > 0)
|
||||
{
|
||||
state.window[rundest++] = state.window[runsrc++];
|
||||
}
|
||||
|
||||
runsrc = window;
|
||||
}
|
||||
}
|
||||
|
||||
window_posn += (uint)match_length;
|
||||
|
||||
/* copy match data - no worries about destination wraps */
|
||||
while (match_length-- > 0)
|
||||
{
|
||||
state.window[rundest++] = state.window[runsrc++];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LZX_BLOCKTYPE_UNCOMPRESSED:
|
||||
if ((inpos + this_run) > endinp)
|
||||
return false;
|
||||
|
||||
Array.Copy(inbuf, inpos, state.window, window + window_posn, this_run);
|
||||
inpos += this_run;
|
||||
window_posn += (uint)this_run;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false; /* might as well */
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (togo != 0)
|
||||
return false;
|
||||
|
||||
Array.Copy(state.window, window + ((window_posn == 0) ? window_size : window_posn) - outlen, outbuf, 0, outlen);
|
||||
|
||||
state.window_posn = window_posn;
|
||||
state.R0 = R0;
|
||||
state.R1 = R1;
|
||||
state.R2 = R2;
|
||||
|
||||
/* intel E8 decoding */
|
||||
if ((state.frames_read++ < 32768) && state.intel_filesize != 0)
|
||||
{
|
||||
if (outlen <= 6 || state.intel_started == 0)
|
||||
{
|
||||
state.intel_curpos += outlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
int data = 0; // outbuf[0];
|
||||
int dataend = data + outlen - 10;
|
||||
int curpos = state.intel_curpos;
|
||||
int filesize = state.intel_filesize;
|
||||
int abs_off, rel_off;
|
||||
|
||||
state.intel_curpos = curpos + outlen;
|
||||
|
||||
while (data < dataend)
|
||||
{
|
||||
if (outbuf[data++] != 0xE8)
|
||||
{
|
||||
curpos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
abs_off = outbuf[data + 0] | (outbuf[data + 1] << 8) | (outbuf[data + 2] << 16) | (outbuf[data + 3] << 24);
|
||||
if ((abs_off >= -curpos) && (abs_off < filesize))
|
||||
{
|
||||
rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize;
|
||||
outbuf[data + 0] = (byte)rel_off;
|
||||
outbuf[data + 1] = (byte)(rel_off >> 8);
|
||||
outbuf[data + 2] = (byte)(rel_off >> 16);
|
||||
outbuf[data + 3] = (byte)(rel_off >> 24);
|
||||
}
|
||||
data += 4;
|
||||
curpos += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Read and build the Huffman tree from the lengths
|
||||
/// </summary>
|
||||
private static int ReadLengths(byte[] lengths, uint first, uint last, Bits lb, State state, byte[] inbuf)
|
||||
{
|
||||
uint x, y;
|
||||
uint bitbuf = lb.BitBuffer;
|
||||
int bitsleft = lb.BitsLeft;
|
||||
int inpos = lb.InputPosition;
|
||||
|
||||
for (x = 0; x < 20; x++)
|
||||
{
|
||||
y = READ_BITS(4, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
state.tblPRETREE_len[x] = (byte)y;
|
||||
}
|
||||
|
||||
make_decode_table(LZX_PRETREE_MAXSYMBOLS, LZX_PRETREE_TABLEBITS, state.tblPRETREE_len, state.tblPRETREE_table);
|
||||
|
||||
for (x = first; x < last;)
|
||||
{
|
||||
int z = READ_HUFFSYM(state.tblPRETREE_table, state.tblPRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
if (z == 17)
|
||||
{
|
||||
y = READ_BITS(4, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
y += 4;
|
||||
while (y-- > 0)
|
||||
{
|
||||
lengths[x++] = 0;
|
||||
}
|
||||
}
|
||||
else if (z == 18)
|
||||
{
|
||||
y = READ_BITS(5, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
y += 20;
|
||||
while (y-- > 0)
|
||||
{
|
||||
lengths[x++] = 0;
|
||||
}
|
||||
}
|
||||
else if (z == 19)
|
||||
{
|
||||
y = READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
y += 4;
|
||||
|
||||
z = READ_HUFFSYM(state.tblPRETREE_table, state.tblPRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
z = lengths[x] - z;
|
||||
if (z < 0)
|
||||
z += 17;
|
||||
|
||||
while (y-- > 0)
|
||||
{
|
||||
lengths[x++] = (byte)z;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
z = lengths[x] - z;
|
||||
if (z < 0)
|
||||
z += 17;
|
||||
|
||||
lengths[x++] = (byte)z;
|
||||
}
|
||||
}
|
||||
|
||||
lb.BitBuffer = bitbuf;
|
||||
lb.BitsLeft = bitsleft;
|
||||
lb.InputPosition = inpos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Bitstream reading macros (LZX / intel little-endian byte order)
|
||||
#region Bitstream Reading Macros
|
||||
|
||||
/*
|
||||
* These bit access routines work by using the area beyond the MSB and the
|
||||
* LSB as a free source of zeroes. This avoids having to mask any bits.
|
||||
* So we have to know the bit width of the bitbuffer variable.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Should be used first to set up the system
|
||||
/// </summary>
|
||||
private static void INIT_BITSTREAM(out int bitsleft, out uint bitbuf)
|
||||
{
|
||||
bitsleft = 0;
|
||||
bitbuf = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures there are at least N bits in the bit buffer. It can guarantee
|
||||
// up to 17 bits (i.e. it can read in 16 new bits when there is down to
|
||||
/// 1 bit in the buffer, and it can read 32 bits when there are 0 bits in
|
||||
/// the buffer).
|
||||
/// </summary>
|
||||
/// <remarks>Quantum reads bytes in normal order; LZX is little-endian order</remarks>
|
||||
private static void ENSURE_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
while (bitsleft < n)
|
||||
{
|
||||
byte b0 = inpos + 0 < inbuf.Length ? inbuf[inpos + 0] : (byte)0;
|
||||
byte b1 = inpos + 1 < inbuf.Length ? inbuf[inpos + 1] : (byte)0;
|
||||
|
||||
bitbuf |= (uint)(((b1 << 8) | b0) << (16 - bitsleft));
|
||||
bitsleft += 16;
|
||||
inpos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts (without removing) N bits from the bit buffer
|
||||
/// </summary>
|
||||
private static uint PEEK_BITS(int n, uint bitbuf)
|
||||
{
|
||||
return bitbuf >> (32 - n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes N bits from the bit buffer
|
||||
/// </summary>
|
||||
private static void REMOVE_BITS(int n, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
bitbuf <<= n;
|
||||
bitsleft -= n;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes N bits from the buffer and puts them in v.
|
||||
/// </summary>
|
||||
private static uint READ_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
uint v = 0;
|
||||
if (n > 0)
|
||||
{
|
||||
ENSURE_BITS(n, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
v = PEEK_BITS(n, bitbuf);
|
||||
REMOVE_BITS(n, ref bitsleft, ref bitbuf);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Huffman Methods
|
||||
|
||||
/// <summary>
|
||||
/// This function was coded by David Tritscher. It builds a fast huffman
|
||||
/// decoding table out of just a canonical huffman code lengths table.
|
||||
/// </summary>
|
||||
/// <param name="nsyms">Total number of symbols in this huffman tree.</param>
|
||||
/// <param name="nbits">
|
||||
/// Any symbols with a code length of nbits or less can be decoded
|
||||
/// in one lookup of the table.
|
||||
/// </param>
|
||||
/// <param name="length">A table to get code lengths from [0 to syms-1]</param>
|
||||
/// <param name="table">The table to fill up with decoded symbols and pointers.</param>
|
||||
/// <returns>
|
||||
/// OK: 0
|
||||
/// error: 1
|
||||
/// </returns>
|
||||
private static int make_decode_table(uint nsyms, uint nbits, byte[] length, ushort[] table)
|
||||
{
|
||||
ushort sym;
|
||||
uint leaf;
|
||||
byte bit_num = 1;
|
||||
uint fill;
|
||||
uint pos = 0; /* the current position in the decode table */
|
||||
uint table_mask = (uint)(1 << (int)nbits);
|
||||
uint bit_mask = table_mask >> 1; /* don't do 0 length codes */
|
||||
uint next_symbol = bit_mask; /* base of allocation for long codes */
|
||||
|
||||
/* fill entries for codes short enough for a direct mapping */
|
||||
while (bit_num <= nbits)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] == bit_num)
|
||||
{
|
||||
leaf = pos;
|
||||
|
||||
if ((pos += bit_mask) > table_mask) return 1; /* table overrun */
|
||||
|
||||
/* fill all possible lookups of this symbol with the symbol itself */
|
||||
fill = bit_mask;
|
||||
while (fill-- > 0) table[leaf++] = sym;
|
||||
}
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
bit_num++;
|
||||
}
|
||||
|
||||
/* if there are any codes longer than nbits */
|
||||
if (pos != table_mask)
|
||||
{
|
||||
/* clear the remainder of the table */
|
||||
for (sym = (ushort)pos; sym < table_mask; sym++) table[sym] = 0;
|
||||
|
||||
/* give ourselves room for codes to grow by up to 16 more bits */
|
||||
pos <<= 16;
|
||||
table_mask <<= 16;
|
||||
bit_mask = 1 << 15;
|
||||
|
||||
while (bit_num <= 16)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] == bit_num)
|
||||
{
|
||||
leaf = pos >> 16;
|
||||
for (fill = 0; fill < bit_num - nbits; fill++)
|
||||
{
|
||||
/* if this path hasn't been taken yet, 'allocate' two entries */
|
||||
if (table[leaf] == 0)
|
||||
{
|
||||
table[(next_symbol << 1)] = 0;
|
||||
table[(next_symbol << 1) + 1] = 0;
|
||||
table[leaf] = (ushort)next_symbol++;
|
||||
}
|
||||
/* follow the path and select either left or right for next bit */
|
||||
leaf = (uint)(table[leaf] << 1);
|
||||
if (((pos >> (int)(15 - fill)) & 1) != 0) leaf++;
|
||||
}
|
||||
table[leaf] = sym;
|
||||
|
||||
if ((pos += bit_mask) > table_mask) return 1; /* table overflow */
|
||||
}
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
bit_num++;
|
||||
}
|
||||
}
|
||||
|
||||
/* full table? */
|
||||
if (pos == table_mask) return 0;
|
||||
|
||||
/* either erroneous table, or all elements are 0 - let's find out. */
|
||||
for (sym = 0; sym < nsyms; sym++) if (length[sym] != 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Huffman macros
|
||||
#region Huffman Macros
|
||||
|
||||
/// <summary>
|
||||
/// Decodes one huffman symbol from the bitstream using the stated table and
|
||||
/// puts it in v.
|
||||
/// </summary>
|
||||
private static int READ_HUFFSYM(ushort[] hufftbl, byte[] lentable, int tablebits, int maxsymbols, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
int v = 0, i, j = 0;
|
||||
ENSURE_BITS(16, inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
if ((i = hufftbl[PEEK_BITS(tablebits, bitbuf)]) >= maxsymbols)
|
||||
{
|
||||
j = 1 << (32 - tablebits);
|
||||
do
|
||||
{
|
||||
j >>= 1;
|
||||
i <<= 1;
|
||||
i |= (bitbuf & j) != 0 ? 1 : 0;
|
||||
if (j == 0)
|
||||
throw new System.Exception();
|
||||
} while ((i = hufftbl[i]) >= maxsymbols);
|
||||
}
|
||||
|
||||
j = lentable[v = i];
|
||||
REMOVE_BITS(j, ref bitsleft, ref bitbuf);
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads in code lengths for symbols first to last in the given table. The
|
||||
/// code lengths are stored in their own special LZX way.
|
||||
/// </summary>
|
||||
private static bool READ_LENGTHS(byte[] lentable, uint first, uint last, Bits lb, State state, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
lb.BitBuffer = bitbuf;
|
||||
lb.BitsLeft = bitsleft;
|
||||
lb.InputPosition = inpos;
|
||||
|
||||
if (ReadLengths(lentable, first, last, lb, state, inbuf) != 0)
|
||||
return false;
|
||||
|
||||
bitbuf = lb.BitBuffer;
|
||||
bitsleft = lb.BitsLeft;
|
||||
inpos = lb.InputPosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
119
LZX/State.cs
119
LZX/State.cs
@@ -1,119 +0,0 @@
|
||||
using static SabreTools.Models.Compression.LZX.Constants;
|
||||
|
||||
namespace SabreTools.Compression.LZX
|
||||
{
|
||||
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
|
||||
internal class State
|
||||
{
|
||||
/// <summary>
|
||||
/// the actual decoding window
|
||||
/// </summary>
|
||||
public byte[] window;
|
||||
|
||||
/// <summary>
|
||||
/// window size (32Kb through 2Mb)
|
||||
/// </summary>
|
||||
public uint window_size;
|
||||
|
||||
/// <summary>
|
||||
/// window size when it was first allocated
|
||||
/// </summary>
|
||||
public uint actual_size;
|
||||
|
||||
/// <summary>
|
||||
/// current offset within the window
|
||||
/// </summary>
|
||||
public uint window_posn;
|
||||
|
||||
/// <summary>
|
||||
/// for the LRU offset system
|
||||
/// </summary>
|
||||
public uint R0, R1, R2;
|
||||
|
||||
/// <summary>
|
||||
/// number of main tree elements
|
||||
/// </summary>
|
||||
public ushort main_elements;
|
||||
|
||||
/// <summary>
|
||||
/// have we started decoding at all yet?
|
||||
/// </summary>
|
||||
public int header_read;
|
||||
|
||||
/// <summary>
|
||||
/// type of this block
|
||||
/// </summary>
|
||||
public ushort block_type;
|
||||
|
||||
/// <summary>
|
||||
/// uncompressed length of this block
|
||||
/// </summary>
|
||||
public uint block_length;
|
||||
|
||||
/// <summary>
|
||||
/// uncompressed bytes still left to decode
|
||||
/// </summary>
|
||||
public uint block_remaining;
|
||||
|
||||
/// <summary>
|
||||
/// the number of CFDATA blocks processed
|
||||
/// </summary>
|
||||
public uint frames_read;
|
||||
|
||||
/// <summary>
|
||||
/// magic header value used for transform
|
||||
/// </summary>
|
||||
public int intel_filesize;
|
||||
|
||||
/// <summary>
|
||||
/// current offset in transform space
|
||||
/// </summary>
|
||||
public int intel_curpos;
|
||||
|
||||
/// <summary>
|
||||
/// have we seen any translatable data yet?
|
||||
/// </summary>
|
||||
public int intel_started;
|
||||
|
||||
public ushort[] tblPRETREE_table = new ushort[(1 << LZX_PRETREE_TABLEBITS) + (LZX_PRETREE_MAXSYMBOLS << 1)];
|
||||
public byte[] tblPRETREE_len = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public ushort[] tblMAINTREE_table = new ushort[(1 << LZX_MAINTREE_TABLEBITS) + (LZX_MAINTREE_MAXSYMBOLS << 1)];
|
||||
public byte[] tblMAINTREE_len = new byte[LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public ushort[] tblLENGTH_table = new ushort[(1 << LZX_LENGTH_TABLEBITS) + (LZX_LENGTH_MAXSYMBOLS << 1)];
|
||||
public byte[] tblLENGTH_len = new byte[LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public ushort[] tblALIGNED_table = new ushort[(1 << LZX_ALIGNED_TABLEBITS) + (LZX_ALIGNED_MAXSYMBOLS << 1)];
|
||||
public byte[] tblALIGNED_len = new byte[LZX_ALIGNED_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
#region Decompression Tables
|
||||
|
||||
/// <summary>
|
||||
/// An index to the position slot bases
|
||||
/// </summary>
|
||||
public uint[] PositionSlotBases = new uint[]
|
||||
{
|
||||
0, 1, 2, 3, 4, 6, 8, 12,
|
||||
16, 24, 32, 48, 64, 96, 128, 192,
|
||||
256, 384, 512, 768, 1024, 1536, 2048, 3072,
|
||||
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152,
|
||||
65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360,
|
||||
786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
|
||||
1835008, 1966080, 2097152
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// How many bits of offset-from-base data is needed
|
||||
/// </summary>
|
||||
public byte[] ExtraBits = new byte[]
|
||||
{
|
||||
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
|
||||
15, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
||||
17, 17, 17
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,637 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using SabreTools.Models.Compression.MSZIP;
|
||||
using static SabreTools.Models.Compression.MSZIP.Constants;
|
||||
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/fdi.c"/>
|
||||
internal unsafe class Decompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompress a byte array using a given State
|
||||
/// </summary>
|
||||
public static bool Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
|
||||
{
|
||||
fixed (byte* inpos = inbuf)
|
||||
{
|
||||
state.inpos = inpos;
|
||||
state.bb = state.bk = state.window_posn = 0;
|
||||
if (outlen > ZIPWSIZE)
|
||||
return false;
|
||||
|
||||
// CK = Chris Kirmse, official Microsoft purloiner
|
||||
if (state.inpos[0] != 0x43 || state.inpos[1] != 0x4B)
|
||||
return false;
|
||||
|
||||
state.inpos += 2;
|
||||
|
||||
int lastBlockFlag = 0;
|
||||
do
|
||||
{
|
||||
if (InflateBlock(&lastBlockFlag, state, inbuf, outbuf) != 0)
|
||||
return false;
|
||||
} while (lastBlockFlag == 0);
|
||||
|
||||
// Return success
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a deflated block
|
||||
/// </summary>
|
||||
private static uint InflateBlock(int* e, State state, byte[] inbuf, byte[] outbuf)
|
||||
{
|
||||
// Make local bit buffer
|
||||
uint b = state.bb;
|
||||
uint k = state.bk;
|
||||
|
||||
// Read the deflate block header
|
||||
var header = new DeflateBlockHeader();
|
||||
|
||||
// Read in last block bit
|
||||
ZIPNEEDBITS(1, state, ref b, ref k);
|
||||
header.BFINAL = (*e = (int)b & 1) != 0;
|
||||
ZIPDUMPBITS(1, ref b, ref k);
|
||||
|
||||
// Read in block type
|
||||
ZIPNEEDBITS(2, state, ref b, ref k);
|
||||
header.BTYPE = (CompressionType)(b & 3);
|
||||
ZIPDUMPBITS(2, ref b, ref k);
|
||||
|
||||
// Restore the global bit buffer
|
||||
state.bb = b;
|
||||
state.bk = k;
|
||||
|
||||
// Inflate that block type
|
||||
switch (header.BTYPE)
|
||||
{
|
||||
case CompressionType.NoCompression:
|
||||
return (uint)DecompressStored(state, inbuf, outbuf);
|
||||
case CompressionType.FixedHuffman:
|
||||
return (uint)DecompressFixed(state, inbuf, outbuf);
|
||||
case CompressionType.DynamicHuffman:
|
||||
return (uint)DecompressDynamic(state, inbuf, outbuf);
|
||||
|
||||
// Bad block type
|
||||
case CompressionType.Reserved:
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Decompress" a stored block
|
||||
/// </summary>
|
||||
private static int DecompressStored(State state, byte[] inbuf, byte[] outbuf)
|
||||
{
|
||||
// Make local copies of globals
|
||||
uint b = state.bb;
|
||||
uint k = state.bk;
|
||||
uint w = state.window_posn;
|
||||
|
||||
// Go to byte boundary
|
||||
int n = (int)(k & 7);
|
||||
ZIPDUMPBITS(n, ref b, ref k);
|
||||
|
||||
// Read the stored block header
|
||||
var header = new NonCompressedBlockHeader();
|
||||
|
||||
// Get the length and its compliment
|
||||
ZIPNEEDBITS(16, state, ref b, ref k);
|
||||
header.LEN = (ushort)(b & 0xffff);
|
||||
ZIPDUMPBITS(16, ref b, ref k);
|
||||
|
||||
ZIPNEEDBITS(16, state, ref b, ref k);
|
||||
header.NLEN = (ushort)(b & 0xffff);
|
||||
|
||||
if (header.LEN != (~header.NLEN & 0xffff))
|
||||
return 1; // Error in compressed data
|
||||
|
||||
ZIPDUMPBITS(16, ref b, ref k);
|
||||
|
||||
// Read and output the compressed data
|
||||
while (n-- > 0)
|
||||
{
|
||||
ZIPNEEDBITS(8, state, ref b, ref k);
|
||||
outbuf[w++] = (byte)b;
|
||||
ZIPDUMPBITS(8, ref b, ref k);
|
||||
}
|
||||
|
||||
// Restore the globals from the locals
|
||||
state.window_posn = w;
|
||||
state.bb = b;
|
||||
state.bk = k;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a block originally compressed with fixed Huffman codes
|
||||
/// </summary>
|
||||
private static int DecompressFixed(State state, byte[] inbuf, byte[] outbuf)
|
||||
{
|
||||
// Create the block header
|
||||
FixedHuffmanCompressedBlockHeader header = new FixedHuffmanCompressedBlockHeader();
|
||||
|
||||
fixed (uint* l = state.ll)
|
||||
fixed (ushort* Zipcplens = CopyLengths)
|
||||
fixed (ushort* Zipcplext = LiteralExtraBits)
|
||||
fixed (ushort* Zipcpdist = CopyOffsets)
|
||||
fixed (ushort* Zipcpdext = DistanceExtraBits)
|
||||
{
|
||||
// Assign the literal lengths
|
||||
state.ll = header.LiteralLengths;
|
||||
HuffmanNode* fixed_tl;
|
||||
int fixed_bl = 7;
|
||||
|
||||
// Build the literal length tree
|
||||
int i = BuildHuffmanTree(l, 288, 257, Zipcplens, Zipcplext, &fixed_tl, &fixed_bl, state);
|
||||
if (i != 0)
|
||||
return i;
|
||||
|
||||
// Assign the distance codes
|
||||
state.ll = header.DistanceCodes;
|
||||
HuffmanNode* fixed_td;
|
||||
int fixed_bd = 5;
|
||||
|
||||
// Build the distance code tree
|
||||
i = BuildHuffmanTree(l, 30, 0, Zipcpdist, Zipcpdext, &fixed_td, &fixed_bd, state);
|
||||
if (i != 0)
|
||||
return i;
|
||||
|
||||
// Decompress until an end-of-block code
|
||||
return InflateCodes(fixed_tl, fixed_td, fixed_bl, fixed_bd, state, inbuf, outbuf);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a block originally compressed with dynamic Huffman codes
|
||||
/// </summary>
|
||||
private static int DecompressDynamic(State state, byte[] inbuf, byte[] outbuf)
|
||||
{
|
||||
int i; /* temporary variables */
|
||||
uint j;
|
||||
uint l; /* last length */
|
||||
uint m; /* mask for bit lengths table */
|
||||
uint n; /* number of lengths to get */
|
||||
HuffmanNode* tl; /* literal/length code table */
|
||||
HuffmanNode* td; /* distance code table */
|
||||
int bl; /* lookup bits for tl */
|
||||
int bd; /* lookup bits for td */
|
||||
uint nb; /* number of bit length codes */
|
||||
uint nl; /* number of literal/length codes */
|
||||
uint nd; /* number of distance codes */
|
||||
uint b; /* bit buffer */
|
||||
uint k; /* number of bits in bit buffer */
|
||||
|
||||
/* make local bit buffer */
|
||||
b = state.bb;
|
||||
k = state.bk;
|
||||
|
||||
state.ll = new uint[288 + 32];
|
||||
fixed (uint* ll = state.ll)
|
||||
{
|
||||
/* read in table lengths */
|
||||
ZIPNEEDBITS(5, state, ref b, ref k);
|
||||
nl = 257 + (b & 0x1f); /* number of literal/length codes */
|
||||
ZIPDUMPBITS(5, ref b, ref k);
|
||||
|
||||
ZIPNEEDBITS(5, state, ref b, ref k);
|
||||
nd = 1 + (b & 0x1f); /* number of distance codes */
|
||||
ZIPDUMPBITS(5, ref b, ref k);
|
||||
|
||||
ZIPNEEDBITS(4, state, ref b, ref k);
|
||||
nb = 4 + (b & 0xf); /* number of bit length codes */
|
||||
ZIPDUMPBITS(4, ref b, ref k);
|
||||
if (nl > 288 || nd > 32)
|
||||
return 1; /* bad lengths */
|
||||
|
||||
/* read in bit-length-code lengths */
|
||||
for (j = 0; j < nb; j++)
|
||||
{
|
||||
ZIPNEEDBITS(3, state, ref b, ref k);
|
||||
state.ll[BitLengthOrder[j]] = b & 7;
|
||||
ZIPDUMPBITS(3, ref b, ref k);
|
||||
}
|
||||
for (; j < 19; j++)
|
||||
state.ll[BitLengthOrder[j]] = 0;
|
||||
|
||||
/* build decoding table for trees--single level, 7 bit lookup */
|
||||
bl = 7;
|
||||
if ((i = BuildHuffmanTree(ll, 19, 19, null, null, &tl, &bl, state)) != 0)
|
||||
return i; /* incomplete code set */
|
||||
|
||||
/* read in literal and distance code lengths */
|
||||
n = nl + nd;
|
||||
m = BitMasks[bl];
|
||||
i = (int)(l = 0);
|
||||
while ((uint)i < n)
|
||||
{
|
||||
ZIPNEEDBITS(bl, state, ref b, ref k);
|
||||
j = (td = tl + (b & m))->b;
|
||||
ZIPDUMPBITS((int)j, ref b, ref k);
|
||||
j = td->n;
|
||||
if (j < 16) /* length of code in bits (0..15) */
|
||||
{
|
||||
state.ll[i++] = l = j; /* save last length in l */
|
||||
}
|
||||
else if (j == 16) /* repeat last length 3 to 6 times */
|
||||
{
|
||||
ZIPNEEDBITS(2, state, ref b, ref k);
|
||||
j = 3 + (b & 3);
|
||||
ZIPDUMPBITS(2, ref b, ref k);
|
||||
if ((uint)i + j > n)
|
||||
return 1;
|
||||
while (j-- > 0)
|
||||
{
|
||||
state.ll[i++] = l;
|
||||
}
|
||||
}
|
||||
else if (j == 17) /* 3 to 10 zero length codes */
|
||||
{
|
||||
ZIPNEEDBITS(3, state, ref b, ref k);
|
||||
j = 3 + (b & 7);
|
||||
ZIPDUMPBITS(3, ref b, ref k);
|
||||
if ((uint)i + j > n)
|
||||
return 1;
|
||||
while (j-- > 0)
|
||||
state.ll[i++] = 0;
|
||||
l = 0;
|
||||
}
|
||||
else /* j == 18: 11 to 138 zero length codes */
|
||||
{
|
||||
ZIPNEEDBITS(7, state, ref b, ref k);
|
||||
j = 11 + (b & 0x7f);
|
||||
ZIPDUMPBITS(7, ref b, ref k);
|
||||
if ((uint)i + j > n)
|
||||
return 1;
|
||||
while (j-- > 0)
|
||||
state.ll[i++] = 0;
|
||||
l = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* restore the global bit buffer */
|
||||
state.bb = b;
|
||||
state.bk = k;
|
||||
|
||||
fixed (ushort* Zipcplens = CopyLengths)
|
||||
fixed (ushort* Zipcplext = LiteralExtraBits)
|
||||
fixed (ushort* Zipcpdist = CopyOffsets)
|
||||
fixed (ushort* Zipcpdext = DistanceExtraBits)
|
||||
{
|
||||
/* build the decoding tables for literal/length and distance codes */
|
||||
bl = ZIPLBITS;
|
||||
if ((i = BuildHuffmanTree(ll, nl, 257, Zipcplens, Zipcplext, &tl, &bl, state)) != 0)
|
||||
{
|
||||
return i; /* incomplete code set */
|
||||
}
|
||||
bd = ZIPDBITS;
|
||||
BuildHuffmanTree(ll + nl, nd, 0, Zipcpdist, Zipcpdext, &td, &bd, state);
|
||||
|
||||
/* decompress until an end-of-block code */
|
||||
if (InflateCodes(tl, td, bl, bd, state, inbuf, outbuf) != 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a Huffman tree from a set of lengths
|
||||
/// </summary>
|
||||
private static int BuildHuffmanTree(uint* b, uint n, uint s, ushort* d, ushort* e, HuffmanNode** t, int* m, State state)
|
||||
{
|
||||
uint a; /* counter for codes of length k */
|
||||
uint el; /* length of EOB code (value 256) */
|
||||
uint f; /* i repeats in table every f entries */
|
||||
int g; /* maximum code length */
|
||||
int h; /* table level */
|
||||
uint i; /* counter, current code */
|
||||
uint j; /* counter */
|
||||
int k; /* number of bits in current code */
|
||||
int* l; /* stack of bits per table */
|
||||
uint* p; /* pointer into state.c[],state.b[],state.v[] */
|
||||
HuffmanNode* q; /* points to current table */
|
||||
HuffmanNode r = new HuffmanNode(); /* table entry for structure assignment */
|
||||
int w; /* bits before this table == (l * h) */
|
||||
uint* xp; /* pointer into x */
|
||||
int y; /* number of dummy codes added */
|
||||
uint z; /* number of entries in current table */
|
||||
|
||||
fixed (int* state_lx_ptr = state.lx)
|
||||
{
|
||||
l = state_lx_ptr + 1;
|
||||
|
||||
/* Generate counts for each bit length */
|
||||
el = n > 256 ? b[256] : ZIPBMAX; /* set length of EOB code, if any */
|
||||
|
||||
for (i = 0; i < ZIPBMAX + 1; ++i)
|
||||
state.c[i] = 0;
|
||||
p = b; i = n;
|
||||
do
|
||||
{
|
||||
state.c[*p]++; p++; /* assume all entries <= ZIPBMAX */
|
||||
} while (--i > 0);
|
||||
|
||||
if (state.c[0] == n) /* null input--all zero length codes */
|
||||
{
|
||||
*t = null;
|
||||
*m = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find minimum and maximum length, bound *m by those */
|
||||
for (j = 1; j <= ZIPBMAX; j++)
|
||||
{
|
||||
if (state.c[j] > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
k = (int)j; /* minimum code length */
|
||||
if ((uint)*m < j)
|
||||
*m = (int)j;
|
||||
|
||||
for (i = ZIPBMAX; i > 0; i--)
|
||||
{
|
||||
if (state.c[i] > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
g = (int)i; /* maximum code length */
|
||||
if ((uint)*m > i)
|
||||
*m = (int)i;
|
||||
|
||||
/* Adjust last length count to fill out codes, if needed */
|
||||
for (y = 1 << (int)j; j < i; j++, y <<= 1)
|
||||
{
|
||||
if ((y -= (int)state.c[j]) < 0)
|
||||
return 2; /* bad input: more codes than bits */
|
||||
}
|
||||
|
||||
if ((y -= (int)state.c[i]) < 0)
|
||||
return 2;
|
||||
|
||||
state.c[i] += (uint)y;
|
||||
|
||||
/* Generate starting offsets LONGo the value table for each length */
|
||||
state.x[1] = j = 0;
|
||||
|
||||
fixed (uint* state_c_ptr = state.c)
|
||||
fixed (uint* state_x_ptr = state.x)
|
||||
{
|
||||
p = state_c_ptr + 1;
|
||||
xp = state_x_ptr + 2;
|
||||
while (--i > 0)
|
||||
{
|
||||
/* note that i == g from above */
|
||||
*xp++ = (j += *p++);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make a table of values in order of bit lengths */
|
||||
p = b; i = 0;
|
||||
do
|
||||
{
|
||||
if ((j = *p++) != 0)
|
||||
state.v[state.x[j]++] = i;
|
||||
} while (++i < n);
|
||||
|
||||
/* Generate the Huffman codes and for each, make the table entries */
|
||||
state.x[0] = i = 0; /* first Huffman code is zero */
|
||||
|
||||
fixed (uint* state_v_ptr = state.v)
|
||||
{
|
||||
p = state_v_ptr; /* grab values in bit order */
|
||||
h = -1; /* no tables yet--level -1 */
|
||||
w = l[-1] = 0; /* no bits decoded yet */
|
||||
state.u[0] = default; /* just to keep compilers happy */
|
||||
q = null; /* ditto */
|
||||
z = 0; /* ditto */
|
||||
|
||||
/* go through the bit lengths (k already is bits in shortest code) */
|
||||
for (; k <= g; k++)
|
||||
{
|
||||
a = state.c[k];
|
||||
while (a-- > 0)
|
||||
{
|
||||
/* here i is the Huffman code of length k bits for value *p */
|
||||
/* make tables up to required level */
|
||||
while (k > w + l[h])
|
||||
{
|
||||
w += l[h++]; /* add bits already decoded */
|
||||
|
||||
/* compute minimum size table less than or equal to *m bits */
|
||||
if ((z = (uint)(g - w)) > (uint)*m) /* upper limit */
|
||||
z = (uint)*m;
|
||||
|
||||
if ((f = (uint)(1 << (int)(j = (uint)(k - w)))) > a + 1) /* try a k-w bit table */
|
||||
{ /* too few codes for k-w bit table */
|
||||
f -= a + 1; /* deduct codes from patterns left */
|
||||
fixed (uint* state_c_ptr = state.c)
|
||||
{
|
||||
xp = state_c_ptr + k;
|
||||
while (++j < z) /* try smaller tables up to z bits */
|
||||
{
|
||||
if ((f <<= 1) <= *++xp)
|
||||
break; /* enough codes to use up j bits */
|
||||
f -= *xp; /* else deduct codes from patterns */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((uint)w + j > el && (uint)w < el)
|
||||
j = (uint)(el - w); /* make EOB code end at table */
|
||||
|
||||
z = (uint)(1 << (int)j); /* table entries for j-bit table */
|
||||
l[h] = (int)j; /* set table size in stack */
|
||||
|
||||
/* allocate and link in new table */
|
||||
q = (HuffmanNode*)Marshal.AllocHGlobal((int)((z + 1) * sizeof(HuffmanNode)));
|
||||
*t = q + 1; /* link to list for HuffmanNode_free() */
|
||||
*(t = &(*q).t) = null;
|
||||
state.u[h] = ++q; /* table starts after link */
|
||||
|
||||
/* connect to last table, if there is one */
|
||||
if (h > 0)
|
||||
{
|
||||
state.x[h] = i; /* save pattern for backing up */
|
||||
r.b = (byte)l[h - 1]; /* bits to dump before this table */
|
||||
r.e = (byte)(16 + j); /* bits in this table */
|
||||
r.t = q; /* pointer to this table */
|
||||
j = (uint)((i & ((1 << w) - 1)) >> (w - l[h - 1]));
|
||||
state.u[h - 1][j] = r; /* connect to last table */
|
||||
}
|
||||
}
|
||||
|
||||
/* set up table entry in r */
|
||||
r.b = (byte)(k - w);
|
||||
|
||||
fixed (uint* state_v_ptr_comp = state.v)
|
||||
{
|
||||
if (p >= state_v_ptr_comp + n)
|
||||
{
|
||||
r.e = 99; /* out of values--invalid code */
|
||||
}
|
||||
else if (*p < s)
|
||||
{
|
||||
r.e = (byte)(*p < 256 ? 16 : 15); /* 256 is end-of-block code */
|
||||
r.n = (ushort)*p++; /* simple code is just the value */
|
||||
}
|
||||
else
|
||||
{
|
||||
r.e = (byte)e[*p - s]; /* non-simple--look up in lists */
|
||||
r.n = d[*p++ - s];
|
||||
}
|
||||
}
|
||||
|
||||
/* fill code-like entries with r */
|
||||
f = (uint)(1 << (k - w));
|
||||
for (j = i >> w; j < z; j += f)
|
||||
{
|
||||
q[j] = r;
|
||||
}
|
||||
|
||||
/* backwards increment the k-bit code i */
|
||||
for (j = (uint)(1 << (k - 1)); (i & j) != 0; j >>= 1)
|
||||
{
|
||||
i ^= j;
|
||||
}
|
||||
|
||||
i ^= j;
|
||||
|
||||
/* backup over finished tables */
|
||||
while ((i & ((1 << w) - 1)) != state.x[h])
|
||||
w -= l[--h]; /* don't need to update q */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* return actual size of base table */
|
||||
*m = l[0];
|
||||
}
|
||||
|
||||
/* Return true (1) if we were given an incomplete table */
|
||||
return y != 0 && g != 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflate codes into Huffman trees
|
||||
/// </summary>
|
||||
private static int InflateCodes(HuffmanNode* tl, HuffmanNode* td, int bl, int bd, State state, byte[] inbuf, byte[] outbuf)
|
||||
{
|
||||
uint e; /* table entry flag/number of extra bits */
|
||||
uint n, d; /* length and index for copy */
|
||||
uint w; /* current window position */
|
||||
HuffmanNode* t; /* pointer to table entry */
|
||||
uint ml, md; /* masks for bl and bd bits */
|
||||
uint b; /* bit buffer */
|
||||
uint k; /* number of bits in bit buffer */
|
||||
|
||||
/* make local copies of globals */
|
||||
b = state.bb; /* initialize bit buffer */
|
||||
k = state.bk;
|
||||
w = state.window_posn; /* initialize window position */
|
||||
|
||||
/* inflate the coded data */
|
||||
ml = BitMasks[bl]; /* precompute masks for speed */
|
||||
md = BitMasks[bd];
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
ZIPNEEDBITS(bl, state, ref b, ref k);
|
||||
if ((e = (t = tl + (b & ml))->e) > 16)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (e == 99)
|
||||
return 1;
|
||||
ZIPDUMPBITS(t->b, ref b, ref k);
|
||||
e -= 16;
|
||||
ZIPNEEDBITS((int)e, state, ref b, ref k);
|
||||
} while ((e = (*(t = t->t + (b & BitMasks[e]))).e) > 16);
|
||||
}
|
||||
|
||||
ZIPDUMPBITS(t->b, ref b, ref k);
|
||||
if (e == 16) /* then it's a literal */
|
||||
{
|
||||
outbuf[w++] = (byte)t->n;
|
||||
}
|
||||
else /* it's an EOB or a length */
|
||||
{
|
||||
/* exit if end of block */
|
||||
if (e == 15)
|
||||
break;
|
||||
|
||||
/* get length of block to copy */
|
||||
ZIPNEEDBITS((int)e, state, ref b, ref k);
|
||||
n = t->n + (b & BitMasks[e]);
|
||||
ZIPDUMPBITS((int)e, ref b, ref k);
|
||||
|
||||
/* decode distance of block to copy */
|
||||
ZIPNEEDBITS(bd, state, ref b, ref k);
|
||||
|
||||
if ((e = (*(t = td + (b & md))).e) > 16)
|
||||
do
|
||||
{
|
||||
if (e == 99)
|
||||
return 1;
|
||||
ZIPDUMPBITS(t->b, ref b, ref k);
|
||||
e -= 16;
|
||||
ZIPNEEDBITS((int)e, state, ref b, ref k);
|
||||
} while ((e = (*(t = t->t + (b & BitMasks[e]))).e) > 16);
|
||||
|
||||
ZIPDUMPBITS(t->b, ref b, ref k);
|
||||
|
||||
ZIPNEEDBITS((int)e, state, ref b, ref k);
|
||||
d = w - t->n - (b & BitMasks[e]);
|
||||
ZIPDUMPBITS((int)e, ref b, ref k);
|
||||
|
||||
do
|
||||
{
|
||||
d &= ZIPWSIZE - 1;
|
||||
e = ZIPWSIZE - Math.Max(d, w);
|
||||
e = Math.Min(e, n);
|
||||
n -= e;
|
||||
do
|
||||
{
|
||||
outbuf[w++] = outbuf[d++];
|
||||
} while (--e > 0);
|
||||
} while (n > 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* restore the globals from the locals */
|
||||
state.window_posn = w; /* restore global window pointer */
|
||||
state.bb = b; /* restore global bit buffer */
|
||||
state.bk = k;
|
||||
|
||||
/* done */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#region Macros
|
||||
|
||||
private static void ZIPNEEDBITS(int n, State state, ref uint bitBuffer, ref uint bitCount)
|
||||
{
|
||||
while (bitCount < n)
|
||||
{
|
||||
int c = *state.inpos++;
|
||||
bitBuffer |= (uint)(c << (int)bitCount);
|
||||
bitCount += 8;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ZIPDUMPBITS(int n, ref uint bitBuffer, ref uint bitCount)
|
||||
{
|
||||
bitBuffer >>= n;
|
||||
bitCount -= (uint)n;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
internal unsafe struct HuffmanNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of extra bits or operation
|
||||
/// </summary>
|
||||
public byte e;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bits in this code or subcode
|
||||
/// </summary>
|
||||
public byte b;
|
||||
|
||||
#region v
|
||||
|
||||
/// <summary>
|
||||
/// Literal, length base, or distance base
|
||||
/// </summary>
|
||||
public ushort n;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to next level of table
|
||||
/// </summary>
|
||||
public HuffmanNode* t;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SabreTools.Models.Compression.Quantum;
|
||||
using SabreTools.Models.MicrosoftCabinet;
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompress a byte array using a given State
|
||||
/// </summary>
|
||||
public static int Decompress(State state, int inlen, byte[] inbuf, int outlen, byte[] outbuf)
|
||||
{
|
||||
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;
|
||||
|
||||
int extra, togo = outlen, matchLength = 0, copyLength;
|
||||
byte selector, sym;
|
||||
uint matchOffset = 0;
|
||||
|
||||
// Make local copies of state variables
|
||||
uint bitBuffer = state.BitBuffer;
|
||||
int bitsLeft = state.BitsLeft;
|
||||
|
||||
ushort H = 0xFFFF, L = 0;
|
||||
|
||||
// Read initial value of C
|
||||
ushort C = (ushort)Q_READ_BITS(16, inbuf, ref inpos, ref bitsLeft, ref bitBuffer);
|
||||
|
||||
// Apply 2^x-1 mask
|
||||
windowPosition &= windowSize - 1;
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a Quantum decompressor state
|
||||
/// </summary>
|
||||
public static bool InitState(State state, CFFOLDER folder)
|
||||
{
|
||||
int window = ((ushort)folder.CompressionType >> 8) & 0x1f;
|
||||
int level = ((ushort)folder.CompressionType >> 4) & 0xF;
|
||||
return InitState(state, window, level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a Quantum decompressor state
|
||||
/// </summary>
|
||||
public static bool InitState(State state, int window, int level)
|
||||
{
|
||||
uint windowSize = (uint)(1 << window);
|
||||
int maxSize = window * 2;
|
||||
|
||||
// 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)
|
||||
{
|
||||
state.Window = new byte[windowSize];
|
||||
state.ActualSize = windowSize;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a Quantum model that decodes symbols from s to (s + n - 1)
|
||||
/// </summary>
|
||||
private static Model CreateModel(ModelSymbol[] symbols, int entryCount, int initialSymbol)
|
||||
{
|
||||
// Set the basic values
|
||||
Model model = new Model
|
||||
{
|
||||
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++)
|
||||
{
|
||||
// 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),
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
/// </summary>
|
||||
private static void UpdateModel(Model model, int symbol)
|
||||
{
|
||||
// Update the cumulative frequency for all symbols less than the provided
|
||||
for (int i = 0; i < symbol; i++)
|
||||
{
|
||||
model.Symbols[i].CumulativeFrequency += 8;
|
||||
}
|
||||
|
||||
// If the first symbol still has a cumulative frequency under 3800
|
||||
if (model.Symbols[0].CumulativeFrequency <= 3800)
|
||||
return;
|
||||
|
||||
// If we have more than 1 shift left in the model
|
||||
if (--model.TimeToReorder != 0)
|
||||
{
|
||||
// Loop through the entries from highest to lowest,
|
||||
// performing the shift on 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);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no shifts left in the model
|
||||
else
|
||||
{
|
||||
// Reset the shifts left value to 50
|
||||
model.TimeToReorder = 50;
|
||||
|
||||
// Loop through the entries setting the 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;
|
||||
}
|
||||
|
||||
// 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++)
|
||||
{
|
||||
for (int j = i + 1; j < model.Entries; j++)
|
||||
{
|
||||
if (model.Symbols[i].CumulativeFrequency < model.Symbols[j].CumulativeFrequency)
|
||||
{
|
||||
var temp = model.Symbols[i];
|
||||
model.Symbols[i] = model.Symbols[j];
|
||||
model.Symbols[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then convert frequencies back to cumfreq
|
||||
for (int i = model.Entries - 1; i >= 0; i--)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
/// </summary>
|
||||
private static void Q_INIT_BITSTREAM(out int bitsleft, out uint bitbuf)
|
||||
{
|
||||
bitsleft = 0;
|
||||
bitbuf = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds more data to the bit buffer, if there is room for another 16 bits.
|
||||
/// </summary>
|
||||
private static void Q_FILL_BUFFER(byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
if (bitsleft > 8)
|
||||
return;
|
||||
|
||||
byte b0 = inpos + 0 < inbuf.Length ? inbuf[inpos + 0] : (byte)0;
|
||||
byte b1 = inpos + 1 < inbuf.Length ? inbuf[inpos + 1] : (byte)0;
|
||||
|
||||
bitbuf |= (uint)(((b0 << 8) | b1) << (16 - bitsleft));
|
||||
bitsleft += 16;
|
||||
inpos += 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts (without removing) N bits from the bit buffer
|
||||
/// </summary>
|
||||
private static uint Q_PEEK_BITS(int n, uint bitbuf)
|
||||
{
|
||||
return bitbuf >> (32 - n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes N bits from the bit buffer
|
||||
/// </summary>
|
||||
private static void Q_REMOVE_BITS(int n, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
bitbuf <<= n;
|
||||
bitsleft -= n;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes N bits from the buffer and puts them in v. Unlike LZX, this can loop
|
||||
/// several times to get the requisite number of bits.
|
||||
/// </summary>
|
||||
private static uint Q_READ_BITS(int n, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
uint v = 0; int bitrun;
|
||||
for (int bitsneed = n; bitsneed != 0; bitsneed -= bitrun)
|
||||
{
|
||||
Q_FILL_BUFFER(inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
bitrun = (bitsneed > bitsleft) ? bitsleft : bitsneed;
|
||||
v = (v << bitrun) | Q_PEEK_BITS(bitrun, bitbuf);
|
||||
Q_REMOVE_BITS(bitrun, ref bitsleft, ref bitbuf);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the next symbol from the stated model and puts it in symbol.
|
||||
/// It may need to read the bitstream to do this.
|
||||
/// </summary>
|
||||
private static ushort GET_SYMBOL(Model model, ref ushort H, ref ushort L, ref ushort C, byte[] inbuf, ref int inpos, ref int bitsleft, ref uint bitbuf)
|
||||
{
|
||||
ushort symf = GetFrequency(model.Symbols[0].CumulativeFrequency, H, L, C);
|
||||
|
||||
int i;
|
||||
for (i = 1; i < model.Entries; i++)
|
||||
{
|
||||
if (model.Symbols[i].CumulativeFrequency <= symf)
|
||||
break;
|
||||
}
|
||||
|
||||
ushort symbol = model.Symbols[i - 1].Symbol;
|
||||
GetCode(model.Symbols[i - 1].CumulativeFrequency,
|
||||
model.Symbols[i].CumulativeFrequency,
|
||||
model.Symbols[0].CumulativeFrequency,
|
||||
ref H, ref L, ref C,
|
||||
inbuf, ref inpos, ref bitsleft, ref bitbuf);
|
||||
|
||||
UpdateModel(model, i);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the frequency for a given range and total frequency
|
||||
/// </summary>
|
||||
private static ushort GetFrequency(ushort totalFrequency, ushort H, ushort L, ushort C)
|
||||
{
|
||||
uint range = (uint)(((H - L) & 0xFFFF) + 1);
|
||||
uint freq = (uint)(((C - L + 1) * totalFrequency - 1) / range);
|
||||
return (ushort)(freq & 0xFFFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The decoder renormalization loop
|
||||
/// </summary>
|
||||
private static void GetCode(int previousFrequency,
|
||||
int cumulativeFrequency,
|
||||
int totalFrequency,
|
||||
ref ushort H,
|
||||
ref ushort L,
|
||||
ref ushort C,
|
||||
byte[] inbuf,
|
||||
ref int inpos,
|
||||
ref int bitsleft,
|
||||
ref uint bitbuf)
|
||||
{
|
||||
uint range = (uint)((H - L) + 1);
|
||||
H = (ushort)(L + ((previousFrequency * range) / totalFrequency) - 1);
|
||||
L = (ushort)(L + (cumulativeFrequency * range) / totalFrequency);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if ((L & 0x8000) != (H & 0x8000))
|
||||
{
|
||||
if ((L & 0x4000) == 0 || (H & 0x4000) != 0)
|
||||
break;
|
||||
|
||||
// Underflow case
|
||||
C ^= 0x4000;
|
||||
L &= 0x3FFF;
|
||||
H |= 0x4000;
|
||||
}
|
||||
|
||||
L <<= 1;
|
||||
H = (ushort)((H << 1) | 1);
|
||||
C = (ushort)((C << 1) | Q_READ_BITS(1, inbuf, ref inpos, ref bitsleft, ref bitbuf));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
193
Quantum/State.cs
193
Quantum/State.cs
@@ -1,193 +0,0 @@
|
||||
using SabreTools.Models.Compression.Quantum;
|
||||
|
||||
namespace SabreTools.Compression.Quantum
|
||||
{
|
||||
/// <see href="https://github.com/kyz/libmspack/blob/master/libmspack/mspack/qtmd.c"/>
|
||||
/// <see href="https://github.com/wine-mirror/wine/blob/master/dlls/cabinet/cabinet.h"/>
|
||||
internal class State
|
||||
{
|
||||
/// <summary>
|
||||
/// The actual decoding window
|
||||
/// </summary>
|
||||
public byte[] Window;
|
||||
|
||||
/// <summary>
|
||||
/// Window size (1Kb through 2Mb)
|
||||
/// </summary>
|
||||
public uint WindowSize;
|
||||
|
||||
/// <summary>
|
||||
/// Window size when it was first allocated
|
||||
/// </summary>
|
||||
public uint ActualSize;
|
||||
|
||||
/// <summary>
|
||||
/// Current offset within the window
|
||||
/// </summary>
|
||||
public uint WindowPosition;
|
||||
|
||||
#region Models
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for selector model
|
||||
/// </summary>
|
||||
public ModelSymbol[] SelectorModelSymbols = new ModelSymbol[7 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Model for selector values
|
||||
/// </summary>
|
||||
public Model SelectorModel;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 0
|
||||
/// </summary>
|
||||
public Model Model0;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 1
|
||||
/// </summary>
|
||||
public Model Model1;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 2
|
||||
/// </summary>
|
||||
public Model Model2;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 3
|
||||
/// </summary>
|
||||
public Model Model3;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 4
|
||||
/// </summary>
|
||||
public Model Model4;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 5
|
||||
/// </summary>
|
||||
public Model Model5;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 6 Position
|
||||
/// </summary>
|
||||
public Model Model6Position;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Selector 6 Length
|
||||
/// </summary>
|
||||
public Model Model6Length;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Symbol Tables
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 0
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model0Symbols = new ModelSymbol[0x40 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 1
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model1Symbols = new ModelSymbol[0x40 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 2
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model2Symbols = new ModelSymbol[0x40 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 3
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model3Symbols = new ModelSymbol[0x40 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 4
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model4Symbols = new ModelSymbol[0x18 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 5
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model5Symbols = new ModelSymbol[0x24 + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 6 Position
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model6PositionSymbols = new ModelSymbol[0x2a + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Symbol table for Selector 6 Length
|
||||
/// </summary>
|
||||
public ModelSymbol[] Model6LengthSymbols = new ModelSymbol[0x1b + 1];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decompression Tables
|
||||
|
||||
/// <summary>
|
||||
/// An index to the position slot bases
|
||||
/// </summary>
|
||||
public uint[] PositionSlotBases = new uint[42]
|
||||
{
|
||||
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
|
||||
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
|
||||
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
|
||||
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
|
||||
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
|
||||
0x100000, 0x180000
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// How many bits of offset-from-base data is needed
|
||||
/// </summary>
|
||||
public byte[] ExtraBits = new byte[42]
|
||||
{
|
||||
0, 0, 0, 0, 1, 1, 2, 2,
|
||||
3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10,
|
||||
11, 11, 12, 12, 13, 13, 14, 14,
|
||||
15, 15, 16, 16, 17, 17, 18, 18,
|
||||
19, 19
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// An index to the position slot bases [Selector 6]
|
||||
/// </summary>
|
||||
public byte[] LengthBases = new byte[27]
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
|
||||
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
|
||||
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
|
||||
0xbe, 0xde, 0xfe
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// How many bits of offset-from-base data is needed [Selector 6]
|
||||
/// </summary>
|
||||
public byte[] LengthExtraBits = new byte[27]
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 2, 2, 2, 2, 3, 3,
|
||||
3, 3, 4, 4, 4, 4, 5, 5,
|
||||
5, 5, 0
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decompression State
|
||||
|
||||
/// <summary>
|
||||
/// Bit buffer to persist between runs
|
||||
/// </summary>
|
||||
public uint BitBuffer = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Bits remaining to persist between runs
|
||||
/// </summary>
|
||||
public int BitsLeft = 0;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
15
README.MD
15
README.MD
@@ -8,7 +8,16 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
|
||||
|
||||
| Compression Name | Decompress | Compress |
|
||||
| --- | --- | --- |
|
||||
| Blast | Yes | No |
|
||||
| LZ | Yes | No |
|
||||
| LZX | Incomplete | No |
|
||||
| MSZIP | Incomplete | No |
|
||||
| Quantum | Incomplete | No |
|
||||
| LZX | No | No |
|
||||
| MSZIP | Yes* | No |
|
||||
| Quantum | No | No |
|
||||
|
||||
**Note:** If something is marked with a `*` it means that it need testing.
|
||||
|
||||
## External Libraries
|
||||
|
||||
| Library Name | Use |
|
||||
| --- | ---|
|
||||
| [ZLibPort](https://github.com/Nanook/zlib-C-To-CSharp-Port) | Adds zlib code for internal and external use; minor edits have been made |
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression\SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,5 +20,9 @@ Global
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
270
SabreTools.Compression/Blast/Blast.cs
Normal file
270
SabreTools.Compression/Blast/Blast.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
/* blast.c
|
||||
* Copyright (C) 2003, 2012, 2013 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in blast.h
|
||||
* version 1.3, 24 Aug 2013
|
||||
*
|
||||
* blast.c decompresses data compressed by the PKWare Compression Library.
|
||||
* This function provides functionality similar to the explode() function of
|
||||
* the PKWare library, hence the name "blast".
|
||||
*
|
||||
* This decompressor is based on the excellent format description provided by
|
||||
* Ben Rudiak-Gould in comp.compression on August 13, 2001. Interestingly, the
|
||||
* example Ben provided in the post is incorrect. The distance 110001 should
|
||||
* instead be 111000. When corrected, the example byte stream becomes:
|
||||
*
|
||||
* 00 04 82 24 25 8f 80 7f
|
||||
*
|
||||
* which decompresses to "AIAIAIAIAIAIA" (without the quotes).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Change history:
|
||||
*
|
||||
* 1.0 12 Feb 2003 - First version
|
||||
* 1.1 16 Feb 2003 - Fixed distance check for > 4 GB uncompressed data
|
||||
* 1.2 24 Oct 2012 - Add note about using binary mode in stdio
|
||||
* - Fix comparisons of differently signed integers
|
||||
* 1.3 24 Aug 2013 - Return unused input from blast()
|
||||
* - Fix test code to correctly report unused input
|
||||
* - Enable the provision of initial input to blast()
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
public unsafe static class BlastDecoder
|
||||
{
|
||||
#region Huffman Encoding
|
||||
|
||||
/// <summary>
|
||||
/// Literal code
|
||||
/// </summary>
|
||||
private static readonly Huffman litcode = new(MAXBITS + 1, 256);
|
||||
|
||||
/// <summary>
|
||||
/// Length code
|
||||
/// </summary>
|
||||
private static readonly Huffman lencode = new(MAXBITS + 1, 16);
|
||||
|
||||
/// <summary>
|
||||
/// Distance code
|
||||
/// </summary>
|
||||
private static readonly Huffman distcode = new(MAXBITS + 1, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Base for length codes
|
||||
/// </summary>
|
||||
private static readonly short[] baseLength =
|
||||
[
|
||||
3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Extra bits for length codes
|
||||
/// </summary>
|
||||
private static readonly byte[] extra =
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor
|
||||
/// </summary>
|
||||
static BlastDecoder()
|
||||
{
|
||||
// Repeated code lengths of literal codes
|
||||
byte[] litlen =
|
||||
[
|
||||
11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8,
|
||||
9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5,
|
||||
7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12,
|
||||
8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27,
|
||||
44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45,
|
||||
44, 173
|
||||
];
|
||||
litcode.Initialize(litlen);
|
||||
|
||||
// Repeated code lengths of length codes 0..15
|
||||
byte[] lenlen =
|
||||
[
|
||||
2, 35, 36, 53, 38, 23
|
||||
];
|
||||
lencode.Initialize(lenlen);
|
||||
|
||||
// Repeated code lengths of distance codes 0..63
|
||||
byte[] distlen =
|
||||
[
|
||||
2, 20, 53, 230, 247, 151, 248
|
||||
];
|
||||
distcode.Initialize(distlen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// blast() decompresses the PKWare Data Compression Library (DCL) compressed
|
||||
/// format. It provides the same functionality as the explode() function in
|
||||
/// that library. (Note: PKWare overused the "implode" verb, and the format
|
||||
/// used by their library implode() function is completely different and
|
||||
/// incompatible with the implode compression method supported by PKZIP.)
|
||||
///
|
||||
/// The binary mode for stdio functions should be used to assure that the
|
||||
/// compressed data is not corrupted when read or written. For example:
|
||||
/// fopen(..., "rb") and fopen(..., "wb").
|
||||
/// </summary>
|
||||
public static int Blast(byte[] inhow, List<byte> outhow)
|
||||
{
|
||||
// Input/output state
|
||||
var state = new State(inhow, outhow);
|
||||
|
||||
// Attempt to decompress using the above state
|
||||
int err;
|
||||
try
|
||||
{
|
||||
err = Decomp(state);
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
// This was originally a jump, which is bad form for C#
|
||||
err = 2;
|
||||
}
|
||||
|
||||
// Write any leftover output and update the error code if needed
|
||||
if (err != 1 && state.Next != 0 && !state.ProcessOutput() && err == 0)
|
||||
err = 1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode PKWare Compression Library stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// First byte is 0 if literals are uncoded or 1 if they are coded. Second
|
||||
/// byte is 4, 5, or 6 for the number of extra bits in the distance code.
|
||||
/// This is the base-2 logarithm of the dictionary size minus six.
|
||||
///
|
||||
/// Compressed data is a combination of literals and length/distance pairs
|
||||
/// terminated by an end code. Literals are either Huffman coded or
|
||||
/// uncoded bytes. A length/distance pair is a coded length followed by a
|
||||
/// coded distance to represent a string that occurs earlier in the
|
||||
/// uncompressed data that occurs again at the current location.
|
||||
///
|
||||
/// A bit preceding a literal or length/distance pair indicates which comes
|
||||
/// next, 0 for literals, 1 for length/distance.
|
||||
///
|
||||
/// If literals are uncoded, then the next eight bits are the literal, in the
|
||||
/// normal bit order in the stream, i.e. no bit-reversal is needed. Similarly,
|
||||
/// no bit reversal is needed for either the length extra bits or the distance
|
||||
/// extra bits.
|
||||
///
|
||||
/// Literal bytes are simply written to the output. A length/distance pair is
|
||||
/// an instruction to copy previously uncompressed bytes to the output. The
|
||||
/// copy is from distance bytes back in the output stream, copying for length
|
||||
/// bytes.
|
||||
///
|
||||
/// Distances pointing before the beginning of the output data are not
|
||||
/// permitted.
|
||||
///
|
||||
/// Overlapped copies, where the length is greater than the distance, are
|
||||
/// allowed and common. For example, a distance of one and a length of 518
|
||||
/// simply copies the last byte 518 times. A distance of four and a length of
|
||||
/// twelve copies the last four bytes three times. A simple forward copy
|
||||
/// ignoring whether the length is greater than the distance or not implements
|
||||
/// this correctly.
|
||||
/// </remarks>
|
||||
private static int Decomp(State state)
|
||||
{
|
||||
int symbol; // decoded symbol, extra bits for distance
|
||||
int len; // length for copy
|
||||
uint dist; // distance for copy
|
||||
int copy; // copy counter
|
||||
int from, to; // copy pointers
|
||||
|
||||
// Read header
|
||||
int lit = state.Bits(8); // true if literals are coded
|
||||
if (lit > 1)
|
||||
return -1;
|
||||
|
||||
int dict = state.Bits(8); // log2(dictionary size) - 6
|
||||
if (dict < 4 || dict > 6)
|
||||
return -2;
|
||||
|
||||
// Decode literals and length/distance pairs
|
||||
do
|
||||
{
|
||||
if (state.Bits(1) != 0)
|
||||
{
|
||||
// Get length
|
||||
symbol = lencode.Decode(state);
|
||||
len = baseLength[symbol] + state.Bits(extra[symbol]);
|
||||
if (len == 519)
|
||||
break; // end code
|
||||
|
||||
// Get distance
|
||||
symbol = len == 2 ? 2 : dict;
|
||||
dist = (uint)(distcode.Decode(state) << symbol);
|
||||
dist += (uint)state.Bits(symbol);
|
||||
dist++;
|
||||
if (state.First && dist > state.Next)
|
||||
return -3; //distance too far back
|
||||
|
||||
// Copy length bytes from distance bytes back
|
||||
do
|
||||
{
|
||||
to = (int)(state.OutputPtr + state.Next);
|
||||
from = (int)(to - dist);
|
||||
copy = MAXWIN;
|
||||
if (state.Next < dist)
|
||||
{
|
||||
from += copy;
|
||||
copy = (int)dist;
|
||||
}
|
||||
|
||||
copy -= (int)state.Next;
|
||||
if (copy > len)
|
||||
copy = len;
|
||||
|
||||
len -= copy;
|
||||
state.Next += (uint)copy;
|
||||
do
|
||||
{
|
||||
state.Output[to++] = state.Output[from++];
|
||||
}
|
||||
while (--copy != 0);
|
||||
|
||||
if (state.Next == MAXWIN)
|
||||
{
|
||||
if (!state.ProcessOutput())
|
||||
return 1;
|
||||
|
||||
state.Next = 0;
|
||||
state.First = false;
|
||||
}
|
||||
}
|
||||
while (len != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get literal and write it
|
||||
symbol = lit != 0 ? litcode.Decode(state) : state.Bits(8);
|
||||
state.Output[state.Next++] = (byte)symbol;
|
||||
if (state.Next == MAXWIN)
|
||||
{
|
||||
if (!state.ProcessOutput())
|
||||
return 1;
|
||||
|
||||
state.Next = 0;
|
||||
state.First = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
SabreTools.Compression/Blast/Constants.cs
Normal file
15
SabreTools.Compression/Blast/Constants.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum code length
|
||||
/// </summary>
|
||||
public const int MAXBITS = 13;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum window size
|
||||
/// </summary>
|
||||
public const int MAXWIN = 4096;
|
||||
}
|
||||
}
|
||||
207
SabreTools.Compression/Blast/Huffman.cs
Normal file
207
SabreTools.Compression/Blast/Huffman.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
/// <summary>
|
||||
/// Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
|
||||
/// each length, which for a canonical code are stepped through in order.
|
||||
/// symbol[] are the symbol values in canonical order, where the number of
|
||||
/// entries is the sum of the counts in count[]. The decoding process can be
|
||||
/// seen in the function decode() below.
|
||||
/// </summary>
|
||||
public class Huffman
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of symbols of each length
|
||||
/// </summary>
|
||||
public short[] Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to number of symbols of each length
|
||||
/// </summary>
|
||||
public int CountPtr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Canonically ordered symbols
|
||||
/// </summary>
|
||||
public short[] Symbol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="countLength">Length of the Count array</param>
|
||||
/// <param name="symbolLength">Length of the Symbol array</param>
|
||||
public Huffman(int countLength, int symbolLength)
|
||||
{
|
||||
Count = new short[countLength];
|
||||
Symbol = new short[symbolLength];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a list of repeated code lengths rep[0..n-1], where each byte is a
|
||||
/// count (high four bits + 1) and a code length (low four bits), generate the
|
||||
/// list of code lengths. This compaction reduces the size of the object code.
|
||||
/// Then given the list of code lengths length[0..n-1] representing a canonical
|
||||
/// Huffman code for n symbols, construct the tables required to decode those
|
||||
/// codes. Those tables are the number of codes of each length, and the symbols
|
||||
/// sorted by length, retaining their original order within each length. The
|
||||
/// return value is zero for a complete code set, negative for an over-
|
||||
/// subscribed code set, and positive for an incomplete code set. The tables
|
||||
/// can be used if the return value is zero or positive, but they cannot be used
|
||||
/// if the return value is negative. If the return value is zero, it is not
|
||||
/// possible for decode() using that table to return an error--any stream of
|
||||
/// enough bits will resolve to a symbol. If the return value is positive, then
|
||||
/// it is possible for decode() using that table to return an error for received
|
||||
/// codes past the end of the incomplete lengths.
|
||||
/// </summary>
|
||||
/// <param name="rep">Repeated code length array</param>
|
||||
public int Initialize(byte[] rep)
|
||||
{
|
||||
int n = rep.Length; // Length of the bit length array
|
||||
short symbol = 0; // Current symbol when stepping through length[]
|
||||
short len; // Current length when stepping through h.Count[]
|
||||
int left; // Number of possible codes left of current length
|
||||
short[] offs = new short[MAXBITS + 1]; // offsets in symbol table for each length
|
||||
short[] length = new short[256]; // Code lengths
|
||||
|
||||
// Convert compact repeat counts into symbol bit length list
|
||||
int repPtr = 0;
|
||||
do
|
||||
{
|
||||
len = rep[repPtr++];
|
||||
left = (len >> 4) + 1;
|
||||
len &= 15;
|
||||
do
|
||||
{
|
||||
length[symbol++] = len;
|
||||
}
|
||||
while (--left != 0);
|
||||
}
|
||||
while (--n != 0);
|
||||
|
||||
n = symbol;
|
||||
|
||||
// Count number of codes of each length
|
||||
for (len = 0; len <= MAXBITS; len++)
|
||||
{
|
||||
Count[len] = 0;
|
||||
}
|
||||
|
||||
// Assumes lengths are within bounds
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
{
|
||||
(Count[length[symbol]])++;
|
||||
}
|
||||
|
||||
// No codes! Complete, but decode() will fail
|
||||
if (Count[0] == n)
|
||||
return 0;
|
||||
|
||||
// Check for an over-subscribed or incomplete set of lengths
|
||||
left = 1; // One possible code of zero length
|
||||
for (len = 1; len <= MAXBITS; len++)
|
||||
{
|
||||
left <<= 1; // One more bit, double codes left
|
||||
left -= Count[len]; // Deduct count from possible codes
|
||||
if (left < 0)
|
||||
return left; // over-subscribed--return negative
|
||||
}
|
||||
|
||||
// Generate offsets into symbol table for each length for sorting
|
||||
offs[1] = 0;
|
||||
for (len = 1; len < MAXBITS; len++)
|
||||
{
|
||||
offs[len + 1] = (short)(offs[len] + Count[len]);
|
||||
}
|
||||
|
||||
// Put symbols in table sorted by length, by symbol order within each length
|
||||
for (symbol = 0; symbol < n; symbol++)
|
||||
{
|
||||
if (length[symbol] != 0)
|
||||
Symbol[offs[length[symbol]]++] = symbol;
|
||||
}
|
||||
|
||||
// Return zero for complete set, positive for incomplete set
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a code from the stream s using huffman table h. Return the symbol or
|
||||
/// a negative value if there is an error. If all of the lengths are zero, i.e.
|
||||
/// an empty code, or if the code is incomplete and an invalid code is received,
|
||||
/// then -9 is returned after reading MAXBITS bits.
|
||||
/// </summary>
|
||||
/// <param name="state">Current input/output state to process</param>
|
||||
/// <remarks>
|
||||
/// The codes as stored in the compressed data are bit-reversed relative to
|
||||
/// a simple integer ordering of codes of the same lengths. Hence below the
|
||||
/// bits are pulled from the compressed data one at a time and used to
|
||||
/// build the code value reversed from what is in the stream in order to
|
||||
/// permit simple integer comparisons for decoding.
|
||||
///
|
||||
/// The first code for the shortest length is all ones. Subsequent codes of
|
||||
/// the same length are simply integer decrements of the previous code. When
|
||||
/// moving up a length, a one bit is appended to the code. For a complete
|
||||
/// code, the last code of the longest length will be all zeros. To support
|
||||
/// this ordering, the bits pulled during decoding are inverted to apply the
|
||||
/// more "natural" ordering starting with all zeros and incrementing.
|
||||
/// </remarks>
|
||||
public int Decode(State state)
|
||||
{
|
||||
int len = 1; // Current number of bits in code
|
||||
int code = 0; // len bits being decoded
|
||||
int first = 0; // First code of length len
|
||||
int count; // Number of codes of length len
|
||||
int index = 0; // Index of first code of length len in symbol table
|
||||
int bitbuf = state.BitBuf; // Bits from stream
|
||||
int left = state.BitCnt; // Bits left in next or left to process
|
||||
int nextPtr = CountPtr + 1; // Next number of codes
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (left-- != 0)
|
||||
{
|
||||
// Invert code
|
||||
code |= (bitbuf & 1) ^ 1;
|
||||
bitbuf >>= 1;
|
||||
count = Count[nextPtr++];
|
||||
|
||||
// If length len, return symbol
|
||||
if (code < first + count)
|
||||
{
|
||||
state.BitBuf = bitbuf;
|
||||
state.BitCnt = (state.BitCnt - len) & 7;
|
||||
return Symbol[index + (code - first)];
|
||||
}
|
||||
|
||||
// Else update for next length
|
||||
index += count;
|
||||
first += count;
|
||||
first <<= 1;
|
||||
code <<= 1;
|
||||
len++;
|
||||
}
|
||||
|
||||
left = (MAXBITS + 1) - len;
|
||||
if (left == 0)
|
||||
break;
|
||||
|
||||
if (state.Left == 0)
|
||||
{
|
||||
state.Left = state.ProcessInput();
|
||||
if (state.Left == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
bitbuf = state.Input[state.InputPtr++];
|
||||
state.Left--;
|
||||
if (left > 8)
|
||||
left = 8;
|
||||
}
|
||||
|
||||
// Ran out of codes
|
||||
return -9;
|
||||
}
|
||||
};
|
||||
}
|
||||
160
SabreTools.Compression/Blast/State.cs
Normal file
160
SabreTools.Compression/Blast/State.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static SabreTools.Compression.Blast.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Blast
|
||||
{
|
||||
/// <summary>
|
||||
/// Input and output state
|
||||
/// </summary>
|
||||
public class State
|
||||
{
|
||||
#region Input State
|
||||
|
||||
/// <summary>
|
||||
/// Opaque information passed to InputFunction()
|
||||
/// </summary>
|
||||
public byte[] InHow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Next input location
|
||||
/// </summary>
|
||||
public List<byte> Input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next input location
|
||||
/// </summary>
|
||||
public int InputPtr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Available input at in
|
||||
/// </summary>
|
||||
public uint Left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit buffer
|
||||
/// </summary>
|
||||
public int BitBuf { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of bits in bit buffer
|
||||
/// </summary>
|
||||
public int BitCnt { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Output State
|
||||
|
||||
/// <summary>
|
||||
/// Opaque information passed to OutputFunction()
|
||||
/// </summary>
|
||||
public List<byte> OutHow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of next write location in out[]
|
||||
/// </summary>
|
||||
public uint Next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to check distances (for first 4K)
|
||||
/// </summary>
|
||||
public bool First { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output buffer and sliding window
|
||||
/// </summary>
|
||||
public byte[] Output { get; set; } = new byte[MAXWIN];
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the next output location
|
||||
/// </summary>
|
||||
public int OutputPtr { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="inhow">Input byte array</param>
|
||||
/// <param name="outhow">Output byte list</param>
|
||||
public State(byte[] inhow, List<byte> outhow)
|
||||
{
|
||||
InHow = inhow;
|
||||
Input = new List<byte>();
|
||||
InputPtr = 0;
|
||||
Left = 0;
|
||||
BitBuf = 0;
|
||||
BitCnt = 0;
|
||||
|
||||
OutHow = outhow;
|
||||
Next = 0;
|
||||
First = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return need bits from the input stream. This always leaves less than
|
||||
/// eight bits in the buffer. bits() works properly for need == 0.
|
||||
/// </summary>
|
||||
/// <param name="need">Number of bits to read</param>
|
||||
/// <remarks>
|
||||
/// Bits are stored in bytes from the least significant bit to the most
|
||||
/// significant bit. Therefore bits are dropped from the bottom of the bit
|
||||
/// buffer, using shift right, and new bytes are appended to the top of the
|
||||
/// bit buffer, using shift left.
|
||||
/// </remarks>
|
||||
public int Bits(int need)
|
||||
{
|
||||
// Load at least need bits into val
|
||||
int val = BitBuf;
|
||||
while (BitCnt < need)
|
||||
{
|
||||
if (Left == 0)
|
||||
{
|
||||
Left = ProcessInput();
|
||||
if (Left == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
// Load eight bits
|
||||
val |= (int)(Input[InputPtr++]) << BitCnt;
|
||||
Left--;
|
||||
BitCnt += 8;
|
||||
}
|
||||
|
||||
// Drop need bits and update buffer, always zero to seven bits left
|
||||
BitBuf = val >> need;
|
||||
BitCnt -= need;
|
||||
|
||||
// Return need bits, zeroing the bits above that
|
||||
return val & ((1 << need) - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process input for the current state
|
||||
/// </summary>
|
||||
/// <returns>Amount of data in Input</returns>
|
||||
public uint ProcessInput()
|
||||
{
|
||||
Input = new List<byte>(InHow);
|
||||
return (uint)Input.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process output for the current state
|
||||
/// </summary>
|
||||
/// <returns>True if the output could be added, false otherwise</returns>
|
||||
public bool ProcessOutput()
|
||||
{
|
||||
try
|
||||
{
|
||||
OutHow.AddRange(Output.Take((int)Next));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Compression.LZ;
|
||||
using static SabreTools.Models.Compression.LZ.Constants;
|
||||
|
||||
@@ -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)
|
||||
@@ -65,7 +57,7 @@ namespace SabreTools.Compression.LZ
|
||||
long read = lz.CopyTo(sourceState, destState, out LZERROR error);
|
||||
|
||||
// Copy the data to the buffer
|
||||
var decompressed = new byte[0];
|
||||
byte[]? decompressed;
|
||||
if (read == 0 || (error != LZERROR.LZERROR_OK && error != LZERROR.LZERROR_NOT_LZ))
|
||||
{
|
||||
decompressed = null;
|
||||
@@ -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();
|
||||
381
SabreTools.Compression/MSZIP/DeflateDecompressor.cs
Normal file
381
SabreTools.Compression/MSZIP/DeflateDecompressor.cs
Normal file
@@ -0,0 +1,381 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Models.Compression.MSZIP;
|
||||
using static SabreTools.Models.Compression.MSZIP.Constants;
|
||||
|
||||
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 readonly ReadOnlyBitStream _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 ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(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 ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(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
|
||||
var 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
|
||||
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
var 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
|
||||
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
var 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
|
||||
}
|
||||
}
|
||||
143
SabreTools.Compression/MSZIP/HuffmanDecoder.cs
Normal file
143
SabreTools.Compression/MSZIP/HuffmanDecoder.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.IO.Streams;
|
||||
|
||||
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(ReadOnlyBitStream 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
SabreTools.Compression/MSZIP/HuffmanNode.cs
Normal file
23
SabreTools.Compression/MSZIP/HuffmanNode.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single node in a Huffman tree
|
||||
/// </summary>
|
||||
public class HuffmanNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Left child of the current node
|
||||
/// </summary>
|
||||
public HuffmanNode? Left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Right child of the current node
|
||||
/// </summary>
|
||||
public HuffmanNode? Right { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of the current node
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
}
|
||||
50
SabreTools.Compression/OldDotNet.cs
Normal file
50
SabreTools.Compression/OldDotNet.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
#if NET20 || NET35
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Compression
|
||||
{
|
||||
/// <summary>
|
||||
/// Derived from the mscorlib code from .NET Framework 4.0
|
||||
/// </summary>
|
||||
internal static class OldDotNet
|
||||
{
|
||||
public static void CopyTo(this Stream source, Stream destination)
|
||||
{
|
||||
if (destination == null)
|
||||
{
|
||||
throw new ArgumentNullException("destination");
|
||||
}
|
||||
|
||||
if (!source.CanRead && !source.CanWrite)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
|
||||
if (!destination.CanRead && !destination.CanWrite)
|
||||
{
|
||||
throw new ObjectDisposedException("destination");
|
||||
}
|
||||
|
||||
if (!source.CanRead)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
if (!destination.CanWrite)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
byte[] array = new byte[81920];
|
||||
int count;
|
||||
while ((count = source.Read(array, 0, array.Length)) != 0)
|
||||
{
|
||||
destination.Write(array, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
51
SabreTools.Compression/Quantum/Constants.cs
Normal file
51
SabreTools.Compression/Quantum/Constants.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace SabreTools.Compression.Quantum
|
||||
{
|
||||
/// <see href="www.russotto.net/quantumcomp.html"/>
|
||||
/// TODO: Remove this class when Models gets updated
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly int[] PositionSlot =
|
||||
[
|
||||
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 =
|
||||
[
|
||||
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 =
|
||||
[
|
||||
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 =
|
||||
[
|
||||
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 =
|
||||
[
|
||||
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
|
||||
];
|
||||
}
|
||||
}
|
||||
422
SabreTools.Compression/Quantum/Decompressor.cs
Normal file
422
SabreTools.Compression/Quantum/Decompressor.cs
Normal file
@@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Models.Compression.Quantum;
|
||||
using static SabreTools.Compression.Quantum.Constants;
|
||||
|
||||
namespace SabreTools.Compression.Quantum
|
||||
{
|
||||
/// <see href="www.russotto.net/quantumcomp.html"/>
|
||||
public class Decompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal bitstream to use for decompression
|
||||
/// </summary>
|
||||
private readonly ReadOnlyBitStream _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)
|
||||
{
|
||||
// If we have an invalid stream
|
||||
if (input == null || input.Length == 0)
|
||||
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));
|
||||
|
||||
// Create a memory stream to wrap
|
||||
var ms = new MemoryStream(input);
|
||||
|
||||
// Wrap the stream in a ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(ms);
|
||||
|
||||
// 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>
|
||||
/// Create a new Decompressor from a Stream
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to decompress</param>
|
||||
/// <param name="windowBits">Number of bits in the sliding window</param>
|
||||
public Decompressor(Stream? input, uint windowBits)
|
||||
{
|
||||
// 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 ReadOnlyBitStream
|
||||
_bitStream = new ReadOnlyBitStream(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>
|
||||
/// Process the stream and return the decompressed output
|
||||
/// </summary>
|
||||
/// <returns>Byte array representing the decompressed data, null on error</returns>
|
||||
public byte[] Process()
|
||||
{
|
||||
// Initialize the coding state
|
||||
CS_H = 0xffff;
|
||||
CS_L = 0x0000;
|
||||
CS_C = (ushort)(_bitStream.ReadBitsMSB(16) ?? 0);
|
||||
|
||||
// Loop until the end of the stream
|
||||
var bytes = new List<byte>();
|
||||
while (_bitStream.Position < _bitStream.Length)
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and initialize a model base on the start symbol and length
|
||||
/// </summary>
|
||||
private Model CreateModel(ushort start, int length)
|
||||
{
|
||||
// Create the model
|
||||
var model = new Model
|
||||
{
|
||||
Entries = length,
|
||||
Symbols = new ModelSymbol[length],
|
||||
TimeToReorder = 4,
|
||||
};
|
||||
|
||||
// Populate the symbol array
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
model.Symbols[i] = new ModelSymbol
|
||||
{
|
||||
Symbol = (ushort)(start + i),
|
||||
CumulativeFrequency = (ushort)(length - 1),
|
||||
};
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the next symbol from a model
|
||||
/// </summary>
|
||||
private int GetSymbol(Model model)
|
||||
{
|
||||
int freq = GetFrequency(model.Symbols![0]!.CumulativeFrequency);
|
||||
|
||||
int i;
|
||||
for (i = 1; i < model.Entries; i++)
|
||||
{
|
||||
if (model.Symbols[i]!.CumulativeFrequency <= freq)
|
||||
break;
|
||||
}
|
||||
|
||||
int sym = model.Symbols![i - 1]!.Symbol;
|
||||
|
||||
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)
|
||||
{
|
||||
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--)
|
||||
{
|
||||
// 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 hit the reordering time
|
||||
else
|
||||
{
|
||||
// Calculate frequencies from cumulative frequencies
|
||||
for (int i = 0; i < model.Entries; i++)
|
||||
{
|
||||
if (i != model.Entries - 1)
|
||||
model.Symbols![i]!.CumulativeFrequency -= model.Symbols![i + 1]!.CumulativeFrequency;
|
||||
|
||||
model.Symbols![i]!.CumulativeFrequency++;
|
||||
model.Symbols![i]!.CumulativeFrequency >>= 1;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
var temp = model.Symbols[i];
|
||||
model.Symbols[i] = model.Symbols[j];
|
||||
model.Symbols[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate cumulative frequencies from frequencies
|
||||
for (int i = model.Entries - 1; i >= 0; i--)
|
||||
{
|
||||
if (i != model.Entries - 1)
|
||||
model.Symbols![i]!.CumulativeFrequency += model.Symbols![i + 1]!.CumulativeFrequency;
|
||||
}
|
||||
|
||||
// Reset the time to reorder
|
||||
model.TimeToReorder = 50;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the frequency of a symbol based on its total frequency
|
||||
/// </summary>
|
||||
private ushort GetFrequency(ushort totalFrequency)
|
||||
{
|
||||
ulong range = (ulong)(((CS_H - CS_L) & 0xFFFF) + 1);
|
||||
ulong frequency = (ulong)((CS_C - CS_L + 1) * totalFrequency - 1) / range;
|
||||
return (ushort)(frequency & 0xFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
SabreTools.Compression/SabreTools.Compression.csproj
Normal file
34
SabreTools.Compression/SabreTools.Compression.csproj
Normal file
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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.5.1</Version>
|
||||
|
||||
<!-- 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 zlib blast</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.4.5" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
218
SabreTools.Compression/zlib/Hebron.Runtime/CRuntime.cs
Normal file
218
SabreTools.Compression/zlib/Hebron.Runtime/CRuntime.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public static unsafe class CRuntime
|
||||
{
|
||||
private static readonly string numbers = "0123456789";
|
||||
|
||||
public static void* malloc(ulong size)
|
||||
{
|
||||
return malloc((long)size);
|
||||
}
|
||||
|
||||
public static void* malloc(long size)
|
||||
{
|
||||
var ptr = Marshal.AllocHGlobal((int)size);
|
||||
|
||||
MemoryStats.Allocated();
|
||||
|
||||
return ptr.ToPointer();
|
||||
}
|
||||
|
||||
public static void free(void* a)
|
||||
{
|
||||
if (a == null)
|
||||
return;
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
MemoryStats.Freed();
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, long size)
|
||||
{
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*ap++ = *bp++;
|
||||
}
|
||||
|
||||
public static void memcpy(void* a, void* b, ulong size)
|
||||
{
|
||||
memcpy(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, long size)
|
||||
{
|
||||
void* temp = null;
|
||||
|
||||
try
|
||||
{
|
||||
temp = malloc(size);
|
||||
memcpy(temp, b, size);
|
||||
memcpy(a, temp, size);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (temp != null)
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memmove(void* a, void* b, ulong size)
|
||||
{
|
||||
memmove(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, long size)
|
||||
{
|
||||
var result = 0;
|
||||
var ap = (byte*)a;
|
||||
var bp = (byte*)b;
|
||||
for (long i = 0; i < size; ++i)
|
||||
{
|
||||
if (*ap != *bp)
|
||||
result += 1;
|
||||
|
||||
ap++;
|
||||
bp++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int memcmp(void* a, void* b, ulong size)
|
||||
{
|
||||
return memcmp(a, b, (long)size);
|
||||
}
|
||||
|
||||
public static int memcmp(byte* a, byte[] b, ulong size)
|
||||
{
|
||||
fixed (void* bptr = b)
|
||||
{
|
||||
return memcmp(a, bptr, (long)size);
|
||||
}
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, long size)
|
||||
{
|
||||
var bptr = (byte*)ptr;
|
||||
var bval = (byte)value;
|
||||
for (long i = 0; i < size; ++i)
|
||||
*bptr++ = bval;
|
||||
}
|
||||
|
||||
public static void memset(void* ptr, int value, ulong size)
|
||||
{
|
||||
memset(ptr, value, (long)size);
|
||||
}
|
||||
|
||||
public static uint _lrotl(uint x, int y)
|
||||
{
|
||||
return (x << y) | (x >> (32 - y));
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, long newSize)
|
||||
{
|
||||
if (a == null)
|
||||
return malloc(newSize);
|
||||
|
||||
var ptr = new IntPtr(a);
|
||||
var result = Marshal.ReAllocHGlobal(ptr, new IntPtr(newSize));
|
||||
|
||||
return result.ToPointer();
|
||||
}
|
||||
|
||||
public static void* realloc(void* a, ulong newSize)
|
||||
{
|
||||
return realloc(a, (long)newSize);
|
||||
}
|
||||
|
||||
public static int abs(int v)
|
||||
{
|
||||
return Math.Abs(v);
|
||||
}
|
||||
|
||||
public static double pow(double a, double b)
|
||||
{
|
||||
return Math.Pow(a, b);
|
||||
}
|
||||
|
||||
public static void SetArray<T>(T[] data, T value)
|
||||
{
|
||||
for (var i = 0; i < data.Length; ++i)
|
||||
data[i] = value;
|
||||
}
|
||||
|
||||
public static double ldexp(double number, int exponent)
|
||||
{
|
||||
return number * Math.Pow(2, exponent);
|
||||
}
|
||||
|
||||
public static int strcmp(sbyte* src, string token)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < token.Length; ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int strncmp(sbyte* src, string token, ulong size)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
for (var i = 0; i < Math.Min(token.Length, (int)size); ++i)
|
||||
{
|
||||
if (src[i] != token[i])
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long strtol(sbyte* start, sbyte** end, int radix)
|
||||
{
|
||||
// First step - determine length
|
||||
var length = 0;
|
||||
sbyte* ptr = start;
|
||||
while (numbers.IndexOf((char)*ptr) != -1)
|
||||
{
|
||||
++ptr;
|
||||
++length;
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
|
||||
// Now build up the number
|
||||
ptr = start;
|
||||
while (length > 0)
|
||||
{
|
||||
long num = numbers.IndexOf((char)*ptr);
|
||||
long pow = (long)Math.Pow(10, length - 1);
|
||||
result += num * pow;
|
||||
|
||||
++ptr;
|
||||
--length;
|
||||
}
|
||||
|
||||
if (end != null)
|
||||
{
|
||||
*end = ptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
SabreTools.Compression/zlib/Hebron.Runtime/MemoryStats.cs
Normal file
28
SabreTools.Compression/zlib/Hebron.Runtime/MemoryStats.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe static class MemoryStats
|
||||
{
|
||||
private static int _allocations;
|
||||
|
||||
public static int Allocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allocations;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Allocated()
|
||||
{
|
||||
Interlocked.Increment(ref _allocations);
|
||||
}
|
||||
|
||||
internal static void Freed()
|
||||
{
|
||||
Interlocked.Decrement(ref _allocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
88
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray1D.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray1D<T> where T : struct
|
||||
{
|
||||
private readonly T[] _data;
|
||||
private readonly GCHandle _pinHandle;
|
||||
public bool IsFreed { get; private set; }
|
||||
|
||||
internal GCHandle PinHandle => _pinHandle;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T this[uint index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Data => _data;
|
||||
|
||||
public UnsafeArray1D(int size)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(size));
|
||||
}
|
||||
|
||||
_data = new T[size];
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public UnsafeArray1D(T[] data, int sizeOf)
|
||||
{
|
||||
if (sizeOf <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOf));
|
||||
}
|
||||
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
_pinHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
|
||||
IsFreed = false;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
if (!IsFreed)
|
||||
{
|
||||
_pinHandle.Free();
|
||||
IsFreed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UnsafeArray1D()
|
||||
{
|
||||
if (!IsFreed)
|
||||
_pinHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer()
|
||||
{
|
||||
return _pinHandle.AddrOfPinnedObject().ToPointer();
|
||||
}
|
||||
|
||||
public static implicit operator void*(UnsafeArray1D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
|
||||
public static void* operator +(UnsafeArray1D<T> array, int delta)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
45
SabreTools.Compression/zlib/Hebron.Runtime/UnsafeArray2D.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public unsafe class UnsafeArray2D<T> where T : struct
|
||||
{
|
||||
private readonly UnsafeArray1D<T>[] _data;
|
||||
private long[] _pinAddresses;
|
||||
private readonly GCHandle _pinAddressesHandle;
|
||||
|
||||
public UnsafeArray1D<T> this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set
|
||||
{
|
||||
_data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UnsafeArray2D(int size1, int size2)
|
||||
{
|
||||
_data = new UnsafeArray1D<T>[size1];
|
||||
_pinAddresses = new long[size1];
|
||||
for (var i = 0; i < size1; ++i)
|
||||
{
|
||||
_data[i] = new UnsafeArray1D<T>(size2);
|
||||
_pinAddresses[i] = _data[i].PinHandle.AddrOfPinnedObject().ToInt64();
|
||||
}
|
||||
|
||||
_pinAddressesHandle = GCHandle.Alloc(_pinAddresses, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
~UnsafeArray2D()
|
||||
{
|
||||
_pinAddressesHandle.Free();
|
||||
}
|
||||
|
||||
public void* ToPointer() => _pinAddressesHandle.AddrOfPinnedObject().ToPointer();
|
||||
|
||||
public static implicit operator void*(UnsafeArray2D<T> array)
|
||||
{
|
||||
return array.ToPointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SabreTools.Compression/zlib/LICENSE
Normal file
9
SabreTools.Compression/zlib/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Nanook
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
23
SabreTools.Compression/zlib/README.md
Normal file
23
SabreTools.Compression/zlib/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
Project to port ZLib from C to C# (CSharp).
|
||||
|
||||
Src zlib 1.2.12 2022-Mar-28 - https://github.com/madler/zlib
|
||||
|
||||
See the Stages folder
|
||||
|
||||
1_zlib.c - Created by running 1_zlib.c_Concat.ps1 Builds with Clang (used by hebron to convert)
|
||||
- Only deflate, inflate, crc32 and adler32 code at the moment. GZip might be added if required.
|
||||
- The only edits to these files are to remove any #includes that have been combined
|
||||
- The file list includes a 000_ to insert any #defines etc and 100_ for a main for debugging etc
|
||||
- Notice crc32.c and trees.c had to be split to allow the single file to build
|
||||
|
||||
2_zlib.cs_Converted - The converted output that Hebron produced - https://github.com/HebronFramework/Hebron
|
||||
- This is a little app that uses Clang to read the C code as DOM and write with Roslyn
|
||||
- It does a fairly decent job and removes a lot of complication
|
||||
|
||||
3_zlib.cs_Working - The fixed up and amended C# that actually runs and matches the C code output
|
||||
- It's had minimal change so is not the prettiest C# code
|
||||
- It's Unsafe in places
|
||||
|
||||
Deflate and Inflate streams have been added.
|
||||
178
SabreTools.Compression/zlib/ZlibDeflateStream.cs
Normal file
178
SabreTools.Compression/zlib/ZlibDeflateStream.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibDeflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream) : this(level, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, Stream baseStream, bool leaveOpen) : this(level, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, bool headerless, Stream baseStream, bool leaveOpen) : this(level, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibDeflateStream(int level, int bufferSize, Stream baseStream, bool leaveOpen) : this(level, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibDeflateStream(int level, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.Level = level;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
|
||||
public int Level { get; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.deflateInit_(_s, this.Level, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
hdr = 2;
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
_s.avail_in = (uint)count;
|
||||
|
||||
fixed (byte* i = buffer, o = _b)
|
||||
{
|
||||
_s.next_in = i;
|
||||
_s.next_out = o + _s.total_out;
|
||||
|
||||
while (err >= 0 && _s.avail_in != 0) //process the buffer
|
||||
{
|
||||
if (_s.avail_out == 0) //get more data
|
||||
{
|
||||
if (_s.total_out != 0)
|
||||
{
|
||||
if (hdr != 0)
|
||||
{
|
||||
BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.total_out -= (uint)hdr;
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
BaseStream.Write(_b!, 0, (int)_s.total_out);
|
||||
}
|
||||
_p += _s.total_out;
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || _s.avail_out != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be written to the base stream. Call when write is finished with.
|
||||
/// Used for creating block seekable files. The caller must manage blocks, indexes and lengths
|
||||
/// </summary>
|
||||
unsafe public long BlockFlush()
|
||||
{
|
||||
//finish previous stream
|
||||
if (_s != null)
|
||||
{
|
||||
int err = 0;
|
||||
fixed (byte* o = _b)
|
||||
{
|
||||
_s.next_in = null;
|
||||
_s.avail_in = 0;
|
||||
_s.next_out = o + _s.total_out; //point to correct location
|
||||
|
||||
int hdr = _p == 0 && Headerless ? 2 : 0;
|
||||
while (err == 0 && (_s.total_out != 0 || _s.state!.pending != 0))
|
||||
{
|
||||
this.BaseStream.Write(_b!, hdr, (int)_s.total_out - hdr);
|
||||
_s.avail_out = (uint)_b!.Length;
|
||||
_p += _s.total_out - hdr;
|
||||
hdr = 0;
|
||||
_s.next_out = o;
|
||||
_s.total_out = 0;
|
||||
if (_s.state!.pending != 0)
|
||||
err = ZLib.deflate(_s, 2);
|
||||
}
|
||||
|
||||
err = ZLib.deflate(_s, 4);
|
||||
|
||||
}
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsafe protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.BlockFlush();
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
SabreTools.Compression/zlib/ZlibInflateStream.cs
Normal file
170
SabreTools.Compression/zlib/ZlibInflateStream.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public class ZlibInflateStream : Stream
|
||||
{
|
||||
private readonly bool _leaveOpen;
|
||||
private ZLib.z_stream_s? _s;
|
||||
private long _p;
|
||||
private byte[]? _b;
|
||||
private bool _complete;
|
||||
|
||||
public ZlibInflateStream(Stream baseStream) : this(0, false, 0, baseStream, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(Stream baseStream, bool leaveOpen) : this(0, false, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, Stream baseStream, bool leaveOpen) : this(0, headerless, 0, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
public ZlibInflateStream(bool headerless, int bufferSize, Stream baseStream, bool leaveOpen) : this(0, headerless, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(int bufferSize, Stream baseStream, bool leaveOpen) : this(0, false, bufferSize, baseStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public ZlibInflateStream(long maxRead, bool headerless, int bufferSize, Stream baseStream, bool leaveOpen)
|
||||
{
|
||||
this.MaxRead = maxRead == 0 ? int.MaxValue : maxRead;
|
||||
this.Headerless = headerless;
|
||||
this.BaseStream = baseStream;
|
||||
_leaveOpen = leaveOpen;
|
||||
_s = null;
|
||||
_b = new byte[bufferSize == 0 ? 0x10000 : bufferSize];
|
||||
_complete = false;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _p;
|
||||
|
||||
public override long Position { get => _p; set => throw new NotImplementedException(); }
|
||||
public long MaxRead { get; private set; }
|
||||
public bool Headerless { get; }
|
||||
public Stream BaseStream { get; }
|
||||
public string Version { get => ZLib.zlibVersion(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
unsafe public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
|
||||
if (buffer == null) throw new ArgumentNullException();
|
||||
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
|
||||
if ((offset + count) > buffer.Length) throw new ArgumentException();
|
||||
if (_complete)
|
||||
return 0;
|
||||
|
||||
int err = 0;
|
||||
int hdr = 0;
|
||||
if (_s == null)
|
||||
{
|
||||
_s = new ZLib.z_stream_s();
|
||||
ZLib.inflateInit_(_s, this.Version, 0); //0 = sizeof(z_stream_s) not used
|
||||
if (this.Headerless)
|
||||
{
|
||||
_b![0] = 0x78;
|
||||
_b[1] = 0x9c; //da
|
||||
hdr = 2;
|
||||
}
|
||||
_s.total_in = 0u;
|
||||
_s.total_out = 0u;
|
||||
_s.avail_in = 0u;
|
||||
_s.avail_out = 0u;
|
||||
}
|
||||
|
||||
int read;
|
||||
_s.avail_out = (uint)count;
|
||||
|
||||
fixed (byte* i = _b, o = buffer)
|
||||
{
|
||||
_s.next_in = i + _s.total_in;
|
||||
_s.next_out = o;
|
||||
|
||||
while (err == 0 && (_s.avail_out != 0 && !_complete)) //process the buffer
|
||||
{
|
||||
if (_s.avail_in == 0) //get more data
|
||||
{
|
||||
_s.total_in = 0;
|
||||
read = (int)Math.Min(this.MaxRead - _p, (long)_b!.Length);
|
||||
if (hdr != 0) //test once to save on the extra calculations
|
||||
{
|
||||
_s.avail_in = (uint)(hdr + (read = BaseStream.Read(_b, hdr, Math.Min(read, _b.Length - hdr))));
|
||||
hdr = 0;
|
||||
}
|
||||
else
|
||||
_s.avail_in = (uint)(read = BaseStream.Read(_b, 0, read));
|
||||
_complete = read == 0;
|
||||
_p += (long)read;
|
||||
_s.next_in = i;
|
||||
}
|
||||
|
||||
if (_s.avail_in != 0 || (!_complete && _s.total_out != 0))
|
||||
err = ZLib.inflate(_s, 2);
|
||||
}
|
||||
}
|
||||
|
||||
uint ret = _s.total_out;
|
||||
_s.total_out = 0u;
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow blocks to be read from the base stream without overreading. Call when write is finished with.
|
||||
/// Used for reading block seekable files. The caller must manage blocks, indexes and lengths. Seek the BaseStream
|
||||
/// </summary>
|
||||
public long BlockFlush(int maxRead)
|
||||
{
|
||||
this.MaxRead = maxRead;
|
||||
if (_s != null)
|
||||
{
|
||||
ZLib.deflateEnd(_s);
|
||||
_s = null;
|
||||
}
|
||||
_complete = false;
|
||||
long ret = _p;
|
||||
_p = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
BlockFlush(0);
|
||||
_complete = true;
|
||||
_b = null;
|
||||
if (!_leaveOpen)
|
||||
this.BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
7101
SabreTools.Compression/zlib/zlib.cs
Normal file
7101
SabreTools.Compression/zlib/zlib.cs
Normal file
File diff suppressed because one or more lines are too long
47
SabreTools.Compression/zlib/zlibConst.cs
Normal file
47
SabreTools.Compression/zlib/zlibConst.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace SabreTools.Compression.zlib
|
||||
{
|
||||
public static class zlibConst
|
||||
{
|
||||
public const int Z_NO_FLUSH = 0;
|
||||
public const int Z_PARTIAL_FLUSH = 1;
|
||||
public const int Z_SYNC_FLUSH = 2;
|
||||
public const int Z_FULL_FLUSH = 3;
|
||||
public const int Z_FINISH = 4;
|
||||
public const int Z_BLOCK = 5;
|
||||
public const int Z_TREES = 6;
|
||||
|
||||
public const int Z_OK = 0;
|
||||
public const int Z_STREAM_END = 1;
|
||||
public const int Z_NEED_DICT = 2;
|
||||
public const int Z_ERRNO = (-1);
|
||||
public const int Z_STREAM_ERROR = (-2);
|
||||
public const int Z_DATA_ERROR = (-3);
|
||||
public const int Z_MEM_ERROR = (-4);
|
||||
public const int Z_BUF_ERROR = (-5);
|
||||
public const int Z_VERSION_ERROR = (-6);
|
||||
|
||||
/// <summary>
|
||||
/// Get the zlib result name from an integer
|
||||
/// </summary>
|
||||
/// <param name="result">Integer to translate to the result name</param>
|
||||
/// <returns>Name of the result, the integer as a string otherwise</returns>
|
||||
public static string ToZlibConstName(this int result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
Z_OK => "Z_OK",
|
||||
Z_STREAM_END => "Z_STREAM_END",
|
||||
Z_NEED_DICT => "Z_NEED_DICT",
|
||||
|
||||
Z_ERRNO => "Z_ERRNO",
|
||||
Z_STREAM_ERROR => "Z_STREAM_ERROR",
|
||||
Z_DATA_ERROR => "Z_DATA_ERROR",
|
||||
Z_MEM_ERROR => "Z_MEM_ERROR",
|
||||
Z_BUF_ERROR => "Z_BUF_ERROR",
|
||||
Z_VERSION_ERROR => "Z_VERSION_ERROR",
|
||||
|
||||
_ => result.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Test/Program.cs
Normal file
12
Test/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using SabreTools.Compression;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// No implementation, used for experimentation
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Test/Test.csproj
Normal file
19
Test/Test.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Compression\SabreTools.Compression.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
publish-nix.sh
Executable file
36
publish-nix.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
NO_BUILD=false
|
||||
while getopts "b" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
NO_BUILD=true
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option provided"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set the current directory as a variable
|
||||
BUILD_FOLDER=$PWD
|
||||
|
||||
# Only build if requested
|
||||
if [ $NO_BUILD = false ]
|
||||
then
|
||||
# Restore Nuget packages for all builds
|
||||
echo "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Compression/SabreTools.Compression.csproj --output $BUILD_FOLDER
|
||||
fi
|
||||
26
publish-win.ps1
Normal file
26
publish-win.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoBuild")]
|
||||
[switch]$NO_BUILD
|
||||
)
|
||||
|
||||
# Set the current directory as a variable
|
||||
$BUILD_FOLDER = $PSScriptRoot
|
||||
|
||||
# Only build if requested
|
||||
if (!$NO_BUILD.IsPresent)
|
||||
{
|
||||
# Restore Nuget packages for all builds
|
||||
Write-Host "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Compression\SabreTools.Compression.csproj --output $BUILD_FOLDER
|
||||
}
|
||||
Reference in New Issue
Block a user