Partially fix CAB decompression

This commit is contained in:
Matt Nadareski
2022-05-25 23:46:45 -07:00
parent 768c77a6bc
commit c913b10286
3 changed files with 235 additions and 250 deletions

View File

@@ -357,7 +357,6 @@ namespace LibMSPackSharp.CAB
}
// Seek to start of data blocks
// TODO: For LZX, they seem to start 8 bytes after the offset for some reason?
if (!System.Seek(State.InputFileHandle, file.Folder.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
return Error = Error.MSPACK_ERR_SEEK;
@@ -553,85 +552,74 @@ namespace LibMSPackSharp.CAB
/// use. will read data blocks (and merge split blocks) from the cabinet
/// and serve the read bytes to the decompressors
/// </summary>
internal static int SysRead(object file, byte[] buffer, int pointer, int bytes)
internal int SysRead(object file, byte[] buffer, int pointer, int bytes)
{
if (file is Decompressor self)
int avail, todo, outlen = 0;
bool ignore_cksum = Salvage ||
(FixMSZip &&
((State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_MSZIP));
bool ignore_blocksize = Salvage;
todo = bytes;
while (todo > 0)
{
SystemImpl sys = self.System;
avail = State.InputEnd - State.InputPointer;
int avail, todo, outlen = 0;
bool ignore_cksum = self.Salvage ||
(self.FixMSZip &&
((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_MSZIP));
bool ignore_blocksize = self.Salvage;
todo = bytes;
while (todo > 0)
// If out of input data, read a new block
if (avail != 0)
{
avail = self.State.InputEnd - self.State.InputPointer;
// Copy as many input bytes available as possible
if (avail > todo)
avail = todo;
// If out of input data, read a new block
if (avail != 0)
Array.Copy(State.Input, State.InputPointer, buffer, pointer, avail);
State.InputPointer += avail;
pointer += avail;
todo -= avail;
}
else
{
// Out of data, read a new block
// Check if we're out of input blocks, advance block counter
if (State.Block++ >= State.Folder.Header.NumBlocks)
{
// Copy as many input bytes available as possible
if (avail > todo)
avail = todo;
if (!Salvage)
ReadError = Error.MSPACK_ERR_DATAFORMAT;
else
Console.WriteLine("Ran out of CAB input blocks prematurely");
Array.Copy(self.State.Input, self.State.InputPointer, buffer, pointer, avail);
self.State.InputPointer += avail;
pointer += avail;
todo -= avail;
break;
}
else
// Read a block
ReadError = SysReadBlock(ref outlen, ignore_cksum, ignore_blocksize);
if (ReadError != Error.MSPACK_ERR_OK)
return -1;
State.Outlen += outlen;
// Special Quantum hack -- trailer byte to allow the decompressor
// to realign itself. CAB Quantum blocks, unlike LZX blocks, can have
// anything from 0 to 4 trailing null bytes.
if ((State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_QUANTUM)
State.Input[State.InputEnd++] = 0xFF;
// Is this the last block?
if (State.Block >= State.Folder.Header.NumBlocks)
{
// Out of data, read a new block
// Check if we're out of input blocks, advance block counter
if (self.State.Block++ >= self.State.Folder.Header.NumBlocks)
if ((State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_LZX)
{
if (!self.Salvage)
self.ReadError = Error.MSPACK_ERR_DATAFORMAT;
else
Console.WriteLine("Ran out of CAB input blocks prematurely");
break;
}
// Read a block
self.ReadError = SysReadBlock(sys, self.State, ref outlen, ignore_cksum, ignore_blocksize);
if (self.ReadError != Error.MSPACK_ERR_OK)
return -1;
self.State.Outlen += outlen;
// Special Quantum hack -- trailer byte to allow the decompressor
// to realign itself. CAB Quantum blocks, unlike LZX blocks, can have
// anything from 0 to 4 trailing null bytes.
if ((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_QUANTUM)
self.State.Input[self.State.InputEnd++] = 0xFF;
// Is this the last block?
if (self.State.Block >= self.State.Folder.Header.NumBlocks)
{
if ((self.State.CompressionType & CompressionType.COMPTYPE_MASK) == CompressionType.COMPTYPE_LZX)
{
// Special LZX hack -- on the last block, inform LZX of the
// size of the output data stream.
(self.State.DecompressorState as LZX).SetOutputLength(self.State.Outlen);
}
// Special LZX hack -- on the last block, inform LZX of the
// size of the output data stream.
(State.DecompressorState as LZX).SetOutputLength(State.Outlen);
}
}
}
return bytes - todo;
}
else if (file is FileStream impl)
{
return SystemImpl.DefaultSystem.Read(impl, buffer, pointer, bytes);
}
return -1;
return bytes - todo;
}
/// <summary>
@@ -640,50 +628,35 @@ namespace LibMSPackSharp.CAB
/// sys.write() function, or does nothing with the data when
/// self.State.OutputFileHandle == null. advances self.State.Offset
/// </summary>
internal static int SysWrite(object file, byte[] buffer, int pointer, int bytes)
internal int SysWrite(object file, byte[] buffer, int pointer, int bytes)
{
// Null output file means skip those bytes
if (file == null)
{
return bytes;
}
else if (file is Decompressor self)
{
self.State.Offset += (uint)bytes;
if (self.State.OutputFileHandle != null)
return self.System.Write(self.State.OutputFileHandle, buffer, pointer, bytes);
State.Offset += (uint)bytes;
if (State.OutputFileHandle != null)
return System.Write(State.OutputFileHandle, buffer, pointer, bytes);
return bytes;
}
else if (file is FileStream impl)
{
return SystemImpl.DefaultSystem.Write(impl, buffer, pointer, bytes);
}
// Unknown file to write to
return 0;
return bytes;
}
/// <summary>
/// Reads a whole data block from a cab file. The block may span more than
/// one cab file, if it does then the fragments will be reassembled
/// </summary>
private static Error SysReadBlock(SystemImpl sys, DecompressState d, ref int output, bool ignore_cksum, bool ignore_blocksize)
private Error SysReadBlock(ref int output, bool ignore_cksum, bool ignore_blocksize)
{
byte[] hdr = new byte[_DataBlockHeader.Size];
int full_len;
// Reset the input block pointer and end of block pointer
d.InputPointer = d.InputEnd = 0;
State.InputPointer = State.InputEnd = 0;
do
{
// Read the block header
if (SystemImpl.DefaultSystem.Read(d.InputFileHandle, hdr, 0, _DataBlockHeader.Size) != _DataBlockHeader.Size)
if (SystemImpl.DefaultSystem.Read(State.InputFileHandle, hdr, 0, _DataBlockHeader.Size) != _DataBlockHeader.Size)
return Error.MSPACK_ERR_READ;
// Skip any reserved block headers
if (d.Data.Cab.Header.HeaderReserved != 0 && !sys.Seek(d.InputFileHandle, d.Data.Cab.Header.HeaderReserved, SeekMode.MSPACK_SYS_SEEK_CUR))
if (State.Data.Cab.Header.DataReserved != 0 && !System.Seek(State.InputFileHandle, State.Data.Cab.Header.DataReserved, SeekMode.MSPACK_SYS_SEEK_CUR))
return Error.MSPACK_ERR_SEEK;
// Create a block header from the data
@@ -692,7 +665,7 @@ namespace LibMSPackSharp.CAB
return err;
// Blocks must not be over CAB_INPUTMAX in size
full_len = (d.InputEnd - d.InputPointer) + dataBlockHeader.CompressedSize; // Include cab-spanning blocks
full_len = (State.InputEnd - State.InputPointer) + dataBlockHeader.CompressedSize; // Include cab-spanning blocks
if (full_len > CAB_INPUTMAX)
{
Console.WriteLine($"Block size {full_len} > CAB_INPUTMAX");
@@ -711,24 +684,24 @@ namespace LibMSPackSharp.CAB
}
// Read the block data
if (SystemImpl.DefaultSystem.Read(d.InputFileHandle, d.Input, d.InputEnd, dataBlockHeader.CompressedSize) != dataBlockHeader.CompressedSize)
if (SystemImpl.DefaultSystem.Read(State.InputFileHandle, State.Input, State.InputEnd, dataBlockHeader.CompressedSize) != dataBlockHeader.CompressedSize)
return Error.MSPACK_ERR_READ;
// Perform checksum test on the block (if one is stored)
if (dataBlockHeader.CheckSum != 0)
{
uint sum2 = Checksum(d.Input, d.InputEnd, dataBlockHeader.CompressedSize, 0);
uint sum2 = Checksum(State.Input, State.InputEnd, dataBlockHeader.CompressedSize, 0);
if (Checksum(hdr, 4, 4, sum2) != dataBlockHeader.CheckSum)
{
if (!ignore_cksum)
return Error.MSPACK_ERR_CHECKSUM;
sys.Message(d.InputFileHandle, "WARNING; bad block checksum found");
System.Message(State.InputFileHandle, "WARNING; bad block checksum found");
}
}
// Advance end of block pointer to include newly read data
d.InputEnd += dataBlockHeader.CompressedSize;
State.InputEnd += dataBlockHeader.CompressedSize;
// Uncompressed size == 0 means this block was part of a split block
// and it continues as the first block of the next cabinet in the set.
@@ -742,23 +715,23 @@ namespace LibMSPackSharp.CAB
// Otherwise, advance to next cabinet
// Close current file handle
sys.Close(d.InputFileHandle);
d.InputFileHandle = null;
System.Close(State.InputFileHandle);
State.InputFileHandle = null;
// Advance to next member in the cabinet set
if ((d.Data = d.Data.Next) == null)
if ((State.Data = State.Data.Next) == null)
{
sys.Message(d.InputFileHandle, "WARNING; ran out of cabinets in set. Are any missing?");
System.Message(State.InputFileHandle, "WARNING; ran out of cabinets in set. Are any missing?");
return Error.MSPACK_ERR_DATAFORMAT;
}
// Open next cab file
d.InputCabinet = d.Data.Cab;
if ((d.InputFileHandle = sys.Open(d.InputCabinet.Filename, OpenMode.MSPACK_SYS_OPEN_READ)) == null)
State.InputCabinet = State.Data.Cab;
if ((State.InputFileHandle = System.Open(State.InputCabinet.Filename, OpenMode.MSPACK_SYS_OPEN_READ)) == null)
return Error.MSPACK_ERR_OPEN;
// Seek to start of data blocks
if (!sys.Seek(d.InputFileHandle, d.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
if (!System.Seek(State.InputFileHandle, State.Data.Offset, SeekMode.MSPACK_SYS_SEEK_START))
return Error.MSPACK_ERR_SEEK;
} while (true);
}

View File

@@ -321,7 +321,7 @@ namespace LibMSPackSharp.Compression
while (Frame < end_frame)
{
// Have we reached the reset interval? (if there is one?)
if (ResetInterval != 0 && ((Frame & ResetInterval) == 0))
if (ResetInterval != 0 && ((Frame % ResetInterval) == 0))
{
if (BlockRemaining != 0)
{
@@ -800,9 +800,29 @@ namespace LibMSPackSharp.Compression
return Error.MSPACK_ERR_OK;
}
/// <summary>
/// Use the cleaned up code for decompression based on wimlib
/// </summary>
private void ResetState()
{
R[0] = 1;
R[1] = 1;
R[2] = 1;
HeaderRead = 0;
BlockRemaining = 0;
BlockType = LZXBlockType.LZX_BLOCKTYPE_INVALID0;
// Initialise tables to 0 (because deltas will be applied to them)
for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++)
{
MAINTREE_len[i] = 0;
}
for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
{
LENGTH_len[i] = 0;
}
}
#region wimlib
public Error DecompressNew(long out_bytes)
{
int warned = 0;
@@ -1012,56 +1032,6 @@ namespace LibMSPackSharp.Compression
return Error.MSPACK_ERR_OK;
}
private Error BUILD_TABLE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols)
{
if (!MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table))
{
Console.WriteLine($"Failed to build table");
return Error = Error.MSPACK_ERR_DECRUNCH;
}
return Error = Error.MSPACK_ERR_OK;
}
private Error BUILD_TABLE_MAYBE_EMPTY()
{
LENGTH_empty = 0;
if (!MakeDecodeTableMSB(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, LENGTH_len, LENGTH_table))
{
for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
{
if (LENGTH_len[i] > 0)
{
Console.WriteLine("Failed to build table");
return Error = Error.MSPACK_ERR_DECRUNCH;
}
}
// Empty tree - allow it, but don't decode symbols with it
LENGTH_empty = 1;
}
return Error = Error.MSPACK_ERR_OK;
}
private Error READ_LENGTHS(byte[] lengths, uint first, uint last)
{
int i_ptr = InputPointer;
int i_end = InputEnd;
uint bit_buffer = BitBuffer;
int bits_left = BitsLeft;
if (ReadLens(lengths, first, last) != Error.MSPACK_ERR_OK)
return Error;
InputPointer = i_ptr;
InputEnd = i_end;
BitBuffer = bit_buffer;
BitsLeft = bits_left;
return Error = Error.MSPACK_ERR_OK;
}
private Error Copy(uint match_offset, int match_len, ref int this_run)
{
// Copy match
@@ -1399,101 +1369,6 @@ namespace LibMSPackSharp.Compression
return Error = Error.MSPACK_ERR_OK;
}
private Error ReadLens(byte[] lens, uint first, uint last)
{
uint x, y;
int z;
// Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits)
for (x = 0; x < LZX_PRETREE_MAXSYMBOLS; x++)
{
y = (uint)READ_BITS_MSB(4);
PRETREE_len[x] = (byte)y;
}
BUILD_TABLE(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
if (Error != Error.MSPACK_ERR_OK)
return Error;
for (x = first; x < last;)
{
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
switch (z)
{
// Code = 17, run of ([read 4 bits]+4) zeros
case 17:
y = (uint)READ_BITS_MSB(4);
y += 4;
while (y-- != 0)
{
lens[x++] = 0;
}
break;
// Code = 18, run of ([read 5 bits]+20) zeros
case 18:
y = (uint)READ_BITS_MSB(5);
y += 20;
while (y-- != 0)
{
lens[x++] = 0;
}
break;
// Code = 19, run of ([read 1 bit]+4) [read huffman symbol]
case 19:
y = (uint)READ_BITS_MSB(1);
y += 4;
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
z = lens[x] - z;
if (z < 0)
z += 17;
while (y-- != 0)
{
lens[x++] = (byte)z;
}
break;
// Code = 0 to 16, delta current length entry
default:
z = lens[x] - z;
if (z < 0)
z += 17;
lens[x++] = (byte)z;
break;
}
}
return Error.MSPACK_ERR_OK;
}
private void ResetState()
{
R[0] = 1;
R[1] = 1;
R[2] = 1;
HeaderRead = 0;
BlockRemaining = 0;
BlockType = LZXBlockType.LZX_BLOCKTYPE_INVALID0;
// Initialise tables to 0 (because deltas will be applied to them)
for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++)
{
MAINTREE_len[i] = 0;
}
for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
{
LENGTH_len[i] = 0;
}
}
private void UndoE8Preprocessing(int data, long out_bytes)
{
int p8 = data;
@@ -1540,5 +1415,7 @@ namespace LibMSPackSharp.Compression
} while (p8 < p8_end);
}
}
#endregion
}
}

View File

@@ -10,11 +10,146 @@
* For further details, see the file COPYING.LIB distributed with libmspack
*/
using System;
using static LibMSPackSharp.Compression.Constants;
namespace LibMSPackSharp.Compression
{
public partial class LZX : CompressionStream
{
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.MSPACK_ERR_DECRUNCH;
/// <summary>
/// BUILD_TABLE(tbl) builds a huffman lookup table from code lengths
/// </summary>
private Error BUILD_TABLE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols)
{
if (!MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table))
{
Console.WriteLine("Failed to build table");
return Error = Error.MSPACK_ERR_DECRUNCH;
}
return Error = Error.MSPACK_ERR_OK;
}
private Error BUILD_TABLE_MAYBE_EMPTY()
{
LENGTH_empty = 0;
if (!MakeDecodeTableMSB(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, LENGTH_len, LENGTH_table))
{
for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)
{
if (LENGTH_len[i] > 0)
{
Console.WriteLine("Failed to build table");
return Error = Error.MSPACK_ERR_DECRUNCH;
}
}
// Empty tree - allow it, but don't decode symbols with it
LENGTH_empty = 1;
}
return Error = Error.MSPACK_ERR_OK;
}
/// <summary>
/// READ_LENGTHS(tablename, first, last) 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 Error READ_LENGTHS(byte[] lengths, uint first, uint last)
{
int i_ptr = InputPointer;
int i_end = InputEnd;
uint bit_buffer = BitBuffer;
int bits_left = BitsLeft;
if (ReadLens(lengths, first, last) != Error.MSPACK_ERR_OK)
return Error;
InputPointer = i_ptr;
InputEnd = i_end;
BitBuffer = bit_buffer;
BitsLeft = bits_left;
return Error = Error.MSPACK_ERR_OK;
}
private Error ReadLens(byte[] lens, uint first, uint last)
{
uint x, y;
int z;
// Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits)
for (x = 0; x < LZX_PRETREE_MAXSYMBOLS; x++)
{
y = (uint)READ_BITS_MSB(4);
PRETREE_len[x] = (byte)y;
}
BUILD_TABLE(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
if (Error != Error.MSPACK_ERR_OK)
return Error;
for (x = first; x < last;)
{
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
switch (z)
{
// Code = 17, run of ([read 4 bits]+4) zeros
case 17:
y = (uint)READ_BITS_MSB(4);
y += 4;
while (y-- != 0)
{
lens[x++] = 0;
}
break;
// Code = 18, run of ([read 5 bits]+20) zeros
case 18:
y = (uint)READ_BITS_MSB(5);
y += 20;
while (y-- != 0)
{
lens[x++] = 0;
}
break;
// Code = 19, run of ([read 1 bit]+4) [read huffman symbol]
case 19:
y = (uint)READ_BITS_MSB(1);
y += 4;
z = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS);
z = lens[x] - z;
if (z < 0)
z += 17;
while (y-- != 0)
{
lens[x++] = (byte)z;
}
break;
// Code = 0 to 16, delta current length entry
default:
z = lens[x] - z;
if (z < 0)
z += 17;
lens[x++] = (byte)z;
break;
}
}
return Error.MSPACK_ERR_OK;
}
}
}