mirror of
https://github.com/SabreTools/SabreTools.Compression.git
synced 2026-02-05 13:49:46 +00:00
Compare commits
39 Commits
libmspack_
...
0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
246
BitStream.cs
Normal file
246
BitStream.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO;
|
||||
|
||||
namespace SabreTools.Compression
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper to allow reading bits from a source stream
|
||||
/// </summary>
|
||||
public class BitStream
|
||||
{
|
||||
/// <inheritdoc cref="Stream.Position"/>
|
||||
public long Position => _source.Position;
|
||||
|
||||
/// <inheritdoc cref="Stream.Length"/>
|
||||
public long Length => _source.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Original stream source
|
||||
/// </summary>
|
||||
private Stream _source;
|
||||
|
||||
/// <summary>
|
||||
/// Last read byte value from the stream
|
||||
/// </summary>
|
||||
private byte? _bitBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Index in the byte of the current bit
|
||||
/// </summary>
|
||||
private int _bitIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BitStream from a source Stream
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public BitStream(Stream source)
|
||||
#else
|
||||
public BitStream(Stream? source)
|
||||
#endif
|
||||
{
|
||||
if (source == null || !source.CanRead || !source.CanSeek)
|
||||
throw new ArgumentException(nameof(source));
|
||||
|
||||
_source = source;
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard the current cached byte
|
||||
/// </summary>
|
||||
public void Discard()
|
||||
{
|
||||
_bitBuffer = null;
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single bit, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
|
||||
public byte? ReadBit()
|
||||
{
|
||||
// If we reached the end of the stream
|
||||
if (_source.Position >= _source.Length)
|
||||
return null;
|
||||
|
||||
// If we don't have a value cached
|
||||
if (_bitBuffer == null)
|
||||
{
|
||||
// Read the next byte, if possible
|
||||
_bitBuffer = ReadSourceByte();
|
||||
if (_bitBuffer == null)
|
||||
return null;
|
||||
|
||||
// Reset the bit index
|
||||
_bitIndex = 0;
|
||||
}
|
||||
|
||||
// Get the value by bit-shifting
|
||||
int value = _bitBuffer.Value & 0x01;
|
||||
_bitBuffer = (byte?)(_bitBuffer >> 1);
|
||||
_bitIndex++;
|
||||
|
||||
// Reset the byte if we're at the end
|
||||
if (_bitIndex >= 8)
|
||||
Discard();
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in LSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsLSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value += (uint)(bitValue.Value << i);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a multiple bits in MSB, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
|
||||
public uint? ReadBitsMSB(int bits)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
// Read the next bit
|
||||
byte? bitValue = ReadBit();
|
||||
if (bitValue == null)
|
||||
return null;
|
||||
|
||||
// Add the bit shifted by the current index
|
||||
value += (value << 1) + bitValue.Value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a byte, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next byte, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public byte? ReadByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt16, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt16, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ushort? ReadUInt16()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt16();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt32, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt32, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public uint? ReadUInt32()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt32();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UInt64, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next UInt64, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
public ulong? ReadUInt64()
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadUInt64();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read <paramref name="bytes"/> bytes, if possible
|
||||
/// </summary>
|
||||
/// <param name="bytes">Number of bytes to read</param>
|
||||
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
|
||||
/// <remarks>Assumes the stream is byte-aligned</remarks>
|
||||
#if NET48
|
||||
public byte[] ReadBytes(int bytes)
|
||||
#else
|
||||
public byte[]? ReadBytes(int bytes)
|
||||
#endif
|
||||
{
|
||||
try
|
||||
{
|
||||
Discard();
|
||||
return _source.ReadBytes(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a single byte from the underlying stream, if possible
|
||||
/// </summary>
|
||||
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
|
||||
private byte? ReadSourceByte()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _source.ReadByteValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
416
MSZIP/DeflateDecompressor.cs
Normal file
416
MSZIP/DeflateDecompressor.cs
Normal file
@@ -0,0 +1,416 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Models.Compression.MSZIP;
|
||||
using static SabreTools.Models.Compression.MSZIP.Constants;
|
||||
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
/// <see href="https://www.rfc-editor.org/rfc/rfc1951"/>
|
||||
public class DeflateDecompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal bitstream to use for decompression
|
||||
/// </summary>
|
||||
private BitStream _bitStream;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Decompressor from a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to decompress</param>
|
||||
#if NET48
|
||||
public DeflateDecompressor(byte[] input)
|
||||
#else
|
||||
public DeflateDecompressor(byte[]? input)
|
||||
#endif
|
||||
{
|
||||
// If we have an invalid stream
|
||||
if (input == null || input.Length == 0)
|
||||
throw new ArgumentException(nameof(input));
|
||||
|
||||
// Create a memory stream to wrap
|
||||
var ms = new MemoryStream(input);
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(ms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Decompressor from a Stream
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to decompress</param>
|
||||
#if NET48
|
||||
public DeflateDecompressor(Stream input)
|
||||
#else
|
||||
public DeflateDecompressor(Stream? input)
|
||||
#endif
|
||||
{
|
||||
// If we have an invalid stream
|
||||
if (input == null || !input.CanRead || !input.CanSeek)
|
||||
throw new ArgumentException(nameof(input));
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a stream into a <see cref="Block"/>
|
||||
/// </summary>
|
||||
/// <returns>Block containing the decompressed data on success, null on error</returns>
|
||||
#if NET48
|
||||
public Block Process()
|
||||
#else
|
||||
public Block? Process()
|
||||
#endif
|
||||
{
|
||||
// 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 NET48
|
||||
if (deflateBlock.Header.BFINAL)
|
||||
#else
|
||||
if (deflateBlock.Header!.BFINAL)
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the deflate blocks to the block and return
|
||||
block.CompressedBlocks = deflateBlocks.ToArray();
|
||||
return block;
|
||||
}
|
||||
|
||||
#region Headers
|
||||
|
||||
/// <summary>
|
||||
/// Read a BlockHeader from the input stream
|
||||
/// </summary>
|
||||
private BlockHeader ReadBlockHeader()
|
||||
{
|
||||
var header = new BlockHeader();
|
||||
header.Signature = _bitStream.ReadUInt16() ?? 0;
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DeflateBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private DeflateBlockHeader ReadDeflateBlockHeader()
|
||||
{
|
||||
var header = new DeflateBlockHeader();
|
||||
header.BFINAL = _bitStream.ReadBit() != 0x01;
|
||||
uint? btype = _bitStream.ReadBitsLSB(2) ?? 0b11;
|
||||
header.BTYPE = (CompressionType)btype;
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a NonCompressedBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private NonCompressedBlockHeader ReadNonCompressedBlockHeader()
|
||||
{
|
||||
var header = new NonCompressedBlockHeader();
|
||||
header.LEN = _bitStream.ReadUInt16() ?? 0;
|
||||
header.NLEN = _bitStream.ReadUInt16() ?? 0;
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a FixedHuffmanCompressedBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private (FixedCompressedDataHeader, uint, uint) RaadFixedCompressedDataHeader()
|
||||
{
|
||||
// Nothing needs to be read, all values are fixed
|
||||
return (new FixedCompressedDataHeader(), 288, 30);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a DynamicHuffmanCompressedBlockHeader from the input stream
|
||||
/// </summary>
|
||||
private (DynamicCompressedDataHeader, uint, uint) ReadDynamicCompressedDataHeader()
|
||||
{
|
||||
var header = new DynamicCompressedDataHeader();
|
||||
|
||||
// Setup the counts first
|
||||
uint numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
uint numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
|
||||
uint numLength = 4 + _bitStream.ReadBitsLSB(4) ?? 0;
|
||||
|
||||
// Convert the alphabet based on lengths
|
||||
uint[] lengthLengths = new uint[19];
|
||||
for (int i = 0; i < numLength; i++)
|
||||
{
|
||||
lengthLengths[BitLengthOrder[i]] = (byte)(_bitStream.ReadBitsLSB(3) ?? 0);
|
||||
}
|
||||
for (int i = (int)numLength; i < 19; i++)
|
||||
{
|
||||
lengthLengths[BitLengthOrder[i]] = 0;
|
||||
}
|
||||
|
||||
// Make the lengths tree
|
||||
HuffmanDecoder lengthTree = new HuffmanDecoder(lengthLengths, 19);
|
||||
|
||||
// Setup the literal and distance lengths
|
||||
header.LiteralLengths = new uint[288];
|
||||
header.DistanceCodes = new uint[32];
|
||||
|
||||
// Read the literal and distance codes
|
||||
int repeatCode = 1;
|
||||
uint leftover = ReadHuffmanLengths(lengthTree, header.LiteralLengths, numLiteral, 0, ref repeatCode);
|
||||
_ = ReadHuffmanLengths(lengthTree, header.DistanceCodes, numDistance, leftover, ref repeatCode);
|
||||
|
||||
return (header, numLiteral, numDistance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block
|
||||
/// </summary>
|
||||
#if NET48
|
||||
private DeflateBlock ReadDeflateBlock()
|
||||
#else
|
||||
private DeflateBlock? ReadDeflateBlock()
|
||||
#endif
|
||||
{
|
||||
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>
|
||||
#if NET48
|
||||
private (NonCompressedBlockHeader, byte[]) ReadNoCompression()
|
||||
#else
|
||||
private (NonCompressedBlockHeader?, byte[]?) ReadNoCompression()
|
||||
#endif
|
||||
{
|
||||
// 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>
|
||||
#if NET48
|
||||
private (FixedCompressedDataHeader, byte[]) ReadFixedHuffman()
|
||||
#else
|
||||
private (FixedCompressedDataHeader, byte[]?) ReadFixedHuffman()
|
||||
#endif
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
|
||||
// Get the fixed huffman header
|
||||
(var header, uint numLiteral, uint numDistance) = RaadFixedCompressedDataHeader();
|
||||
|
||||
// Make the literal and distance trees
|
||||
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
|
||||
// Now loop and decode
|
||||
return (header, ReadHuffmanBlock(literalTree, distanceTree));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block with dynamic Huffman compression
|
||||
/// </summary>
|
||||
#if NET48
|
||||
private (DynamicCompressedDataHeader, byte[]) ReadDynamicHuffman()
|
||||
#else
|
||||
private (DynamicCompressedDataHeader?, byte[]?) ReadDynamicHuffman()
|
||||
#endif
|
||||
{
|
||||
// Get the dynamic huffman header
|
||||
(var header, uint numLiteral, uint numDistance) = ReadDynamicCompressedDataHeader();
|
||||
|
||||
// Make the literal and distance trees
|
||||
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
|
||||
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
|
||||
|
||||
// Now loop and decode
|
||||
return (header, ReadHuffmanBlock(literalTree, distanceTree));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an RFC1951 block with Huffman compression
|
||||
/// </summary>
|
||||
#if NET48
|
||||
private byte[] ReadHuffmanBlock(HuffmanDecoder literalTree, HuffmanDecoder distanceTree)
|
||||
#else
|
||||
private byte[]? ReadHuffmanBlock(HuffmanDecoder literalTree, HuffmanDecoder distanceTree)
|
||||
#endif
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
158
MSZIP/HuffmanDecoder.cs
Normal file
158
MSZIP/HuffmanDecoder.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace SabreTools.Compression.MSZIP
|
||||
{
|
||||
public class HuffmanDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Root Huffman node for the tree
|
||||
/// </summary>
|
||||
private HuffmanNode _root;
|
||||
|
||||
/// <summary>
|
||||
/// Create a Huffman tree to decode with
|
||||
/// </summary>
|
||||
/// <param name="lengths">Array representing the number of bits for each value</param>
|
||||
/// <param name="numCodes">Number of Huffman codes encoded</param>
|
||||
#if NET48
|
||||
public HuffmanDecoder(uint[] lengths, uint numCodes)
|
||||
#else
|
||||
public HuffmanDecoder(uint[]? lengths, uint numCodes)
|
||||
#endif
|
||||
{
|
||||
// Ensure we have lengths
|
||||
if (lengths == null)
|
||||
throw new ArgumentNullException(nameof(lengths));
|
||||
|
||||
// Set the root to null for now
|
||||
#if NET48
|
||||
HuffmanNode root = null;
|
||||
#else
|
||||
HuffmanNode? root = null;
|
||||
#endif
|
||||
|
||||
// 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
|
||||
#if NET48
|
||||
_root = root;
|
||||
#else
|
||||
_root = root!;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode the next value from the stream as a Huffman-encoded value
|
||||
/// </summary>
|
||||
/// <param name="input">BitStream representing the input</param>
|
||||
/// <returns>Value of the node described by the input</returns>
|
||||
public int Decode(BitStream input)
|
||||
{
|
||||
// Start at the root of the tree
|
||||
var node = _root;
|
||||
while (node?.Left != null)
|
||||
{
|
||||
// Read the next bit to determine direction
|
||||
byte? nextBit = input.ReadBit();
|
||||
if (nextBit == null)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
// Left == 0, Right == 1
|
||||
if (nextBit == 0)
|
||||
node = node.Left;
|
||||
else
|
||||
node = node.Right;
|
||||
}
|
||||
|
||||
// We traversed to the bottom of the branch
|
||||
return node?.Value ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a value based on an existing Huffman node
|
||||
/// </summary>
|
||||
/// <param name="node">Existing node to append to, or null if root</param>
|
||||
/// <param name="value">Value to append to the tree</param>
|
||||
/// <param name="length">Length of the current encoding</param>
|
||||
/// <param name="code">Encoding of the value to traverse</param>
|
||||
/// <returns>New instance of the node with value appended</returns>
|
||||
#if NET48
|
||||
private static HuffmanNode Insert(HuffmanNode node, int value, uint length, int code)
|
||||
#else
|
||||
private static HuffmanNode Insert(HuffmanNode? node, int value, uint length, int code)
|
||||
#endif
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
MSZIP/HuffmanNode.cs
Normal file
31
MSZIP/HuffmanNode.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
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>
|
||||
#if NET48
|
||||
public HuffmanNode Left { get; set; }
|
||||
#else
|
||||
public HuffmanNode? Left { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Right child of the current node
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public HuffmanNode Right { get; set; }
|
||||
#else
|
||||
public HuffmanNode? Right { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Value of the current node
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
}
|
||||
51
Quantum/Constants.cs
Normal file
51
Quantum/Constants.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace SabreTools.Compression.Quantum
|
||||
{
|
||||
/// <see href="www.russotto.net/quantumcomp.html"/>
|
||||
/// TODO: Remove this class when Models gets updated
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly int[] PositionSlot = new int[]
|
||||
{
|
||||
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
|
||||
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
|
||||
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
|
||||
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
|
||||
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
|
||||
0x100000, 0x180000
|
||||
};
|
||||
|
||||
public static readonly int[] PositionExtraBits = new int[]
|
||||
{
|
||||
0, 0, 0, 0, 1, 1, 2, 2,
|
||||
3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10,
|
||||
11, 11, 12, 12, 13, 13, 14, 14,
|
||||
15, 15, 16, 16, 17, 17, 18, 18,
|
||||
19, 19
|
||||
};
|
||||
|
||||
public static readonly int[] LengthSlot = new int[]
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
|
||||
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
|
||||
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
|
||||
0xbe, 0xde, 0xfe
|
||||
};
|
||||
|
||||
public static readonly int[] LengthExtraBits = new int[]
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 2, 2, 2, 2, 3, 3,
|
||||
3, 3, 4, 4, 4, 4, 5, 5,
|
||||
5, 5, 0
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Number of position slots for (tsize - 10)
|
||||
/// </summary>
|
||||
public static readonly int[] NumPositionSlots = new int[]
|
||||
{
|
||||
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
|
||||
};
|
||||
}
|
||||
}
|
||||
478
Quantum/Decompressor.cs
Normal file
478
Quantum/Decompressor.cs
Normal file
@@ -0,0 +1,478 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
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 BitStream _bitStream;
|
||||
|
||||
#region Models
|
||||
|
||||
/// <summary>
|
||||
/// Selector 0: literal, 64 entries, starting symbol 0
|
||||
/// </summary>
|
||||
private Model _model0;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 1: literal, 64 entries, starting symbol 64
|
||||
/// </summary>
|
||||
private Model _model1;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 2: literal, 64 entries, starting symbol 128
|
||||
/// </summary>
|
||||
private Model _model2;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 3: literal, 64 entries, starting symbol 192
|
||||
/// </summary>
|
||||
private Model _model3;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 4: LZ, 3 character matches
|
||||
/// </summary>
|
||||
private Model _model4;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 5: LZ, 4 character matches
|
||||
/// </summary>
|
||||
private Model _model5;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 6: LZ, 5+ character matches
|
||||
/// </summary>
|
||||
private Model _model6;
|
||||
|
||||
/// <summary>
|
||||
/// Selector 6 length model
|
||||
/// </summary>
|
||||
private Model _model6len;
|
||||
|
||||
/// <summary>
|
||||
/// Selector selector model
|
||||
/// </summary>
|
||||
private Model _selector;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coding State
|
||||
|
||||
/// <summary>
|
||||
/// Artihmetic coding state: high
|
||||
/// </summary>
|
||||
private ushort CS_H;
|
||||
|
||||
/// <summary>
|
||||
/// Artihmetic coding state: low
|
||||
/// </summary>
|
||||
private ushort CS_L;
|
||||
|
||||
/// <summary>
|
||||
/// Artihmetic coding state: current
|
||||
/// </summary>
|
||||
private ushort CS_C;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Decompressor from a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to decompress</param>
|
||||
/// <param name="windowBits">Number of bits in the sliding window</param>
|
||||
#if NET48
|
||||
public Decompressor(byte[] input, uint windowBits)
|
||||
#else
|
||||
public Decompressor(byte[]? input, uint windowBits)
|
||||
#endif
|
||||
{
|
||||
// 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 BitStream
|
||||
_bitStream = new BitStream(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>
|
||||
#if NET48
|
||||
public Decompressor(Stream input, uint windowBits)
|
||||
#else
|
||||
public Decompressor(Stream? input, uint windowBits)
|
||||
#endif
|
||||
{
|
||||
// If we have an invalid stream
|
||||
if (input == null || !input.CanRead || !input.CanSeek)
|
||||
throw new ArgumentException(nameof(input));
|
||||
|
||||
// If we have an invalid value for the window bits
|
||||
if (windowBits < 10 || windowBits > 21)
|
||||
throw new ArgumentOutOfRangeException(nameof(windowBits));
|
||||
|
||||
// Wrap the stream in a BitStream
|
||||
_bitStream = new BitStream(input);
|
||||
|
||||
// Initialize literal models
|
||||
this._model0 = CreateModel(0, 64);
|
||||
this._model1 = CreateModel(64, 64);
|
||||
this._model2 = CreateModel(128, 64);
|
||||
this._model3 = CreateModel(192, 64);
|
||||
|
||||
// Initialize LZ models
|
||||
int maxBitLength = (int)(windowBits * 2);
|
||||
this._model4 = CreateModel(0, maxBitLength > 24 ? 24 : maxBitLength);
|
||||
this._model5 = CreateModel(0, maxBitLength > 36 ? 36 : maxBitLength);
|
||||
this._model6 = CreateModel(0, maxBitLength);
|
||||
this._model6len = CreateModel(0, 27);
|
||||
|
||||
// Initialze the selector model
|
||||
this._selector = CreateModel(0, 7);
|
||||
|
||||
// Initialize coding state
|
||||
this.CS_H = 0;
|
||||
this.CS_L = 0;
|
||||
this.CS_C = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
#if NET48
|
||||
int freq = GetFrequency(model.Symbols[0].CumulativeFrequency);
|
||||
#else
|
||||
int freq = GetFrequency(model.Symbols![0]!.CumulativeFrequency);
|
||||
#endif
|
||||
|
||||
int i;
|
||||
for (i = 1; i < model.Entries; i++)
|
||||
{
|
||||
#if NET48
|
||||
if (model.Symbols[i].CumulativeFrequency <= freq)
|
||||
#else
|
||||
if (model.Symbols[i]!.CumulativeFrequency <= freq)
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#if NET48
|
||||
int sym = model.Symbols[i - 1].Symbol;
|
||||
|
||||
GetCode(model.Symbols[i - 1].CumulativeFrequency,
|
||||
model.Symbols[i].CumulativeFrequency,
|
||||
model.Symbols[0].CumulativeFrequency);
|
||||
#else
|
||||
int sym = model.Symbols![i - 1]!.Symbol;
|
||||
|
||||
GetCode(model.Symbols![i - 1]!.CumulativeFrequency,
|
||||
model.Symbols![i]!.CumulativeFrequency,
|
||||
model.Symbols![0]!.CumulativeFrequency);
|
||||
#endif
|
||||
|
||||
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++)
|
||||
{
|
||||
#if NET48
|
||||
var sym = model.Symbols[i];
|
||||
#else
|
||||
var sym = model.Symbols![i]!;
|
||||
#endif
|
||||
sym.CumulativeFrequency += 8;
|
||||
}
|
||||
|
||||
// Decrement reordering time, if needed
|
||||
#if NET48
|
||||
if (model.Symbols[0].CumulativeFrequency > 3800)
|
||||
#else
|
||||
if (model.Symbols![0]!.CumulativeFrequency > 3800)
|
||||
#endif
|
||||
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
|
||||
#if NET48
|
||||
var sym = model.Symbols[i];
|
||||
#else
|
||||
var sym = model.Symbols![i]!;
|
||||
#endif
|
||||
sym.CumulativeFrequency >>= 1;
|
||||
|
||||
// If we are lower the next frequency
|
||||
#if NET48
|
||||
if (i != 0 && sym.CumulativeFrequency <= model.Symbols[i + 1].CumulativeFrequency)
|
||||
sym.CumulativeFrequency = (ushort)(model.Symbols[i + 1].CumulativeFrequency + 1);
|
||||
#else
|
||||
if (i != 0 && sym.CumulativeFrequency <= model.Symbols![i + 1]!.CumulativeFrequency)
|
||||
sym.CumulativeFrequency = (ushort)(model.Symbols![i + 1]!.CumulativeFrequency + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// If we hit the reordering time
|
||||
else
|
||||
{
|
||||
// Calculate frequencies from cumulative frequencies
|
||||
for (int i = 0; i < model.Entries; i++)
|
||||
{
|
||||
#if NET48
|
||||
if (i != model.Entries - 1)
|
||||
model.Symbols[i].CumulativeFrequency -= model.Symbols[i + 1].CumulativeFrequency;
|
||||
|
||||
model.Symbols[i].CumulativeFrequency++;
|
||||
model.Symbols[i].CumulativeFrequency >>= 1;
|
||||
#else
|
||||
if (i != model.Entries - 1)
|
||||
model.Symbols![i]!.CumulativeFrequency -= model.Symbols![i + 1]!.CumulativeFrequency;
|
||||
|
||||
model.Symbols![i]!.CumulativeFrequency++;
|
||||
model.Symbols![i]!.CumulativeFrequency >>= 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Sort frequencies in decreasing order
|
||||
for (int i = 0; i < model.Entries; i++)
|
||||
{
|
||||
for (int j = i + 1; j < model.Entries; j++)
|
||||
{
|
||||
#if NET48
|
||||
if (model.Symbols[i].CumulativeFrequency < model.Symbols[j].CumulativeFrequency)
|
||||
#else
|
||||
if (model.Symbols![i]!.CumulativeFrequency < model.Symbols![j]!.CumulativeFrequency)
|
||||
#endif
|
||||
{
|
||||
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)
|
||||
#if NET48
|
||||
model.Symbols[i].CumulativeFrequency += model.Symbols[i + 1].CumulativeFrequency;
|
||||
#else
|
||||
model.Symbols![i]!.CumulativeFrequency += model.Symbols![i + 1]!.CumulativeFrequency;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
|
||||
| Compression Name | Decompress | Compress |
|
||||
| --- | --- | --- |
|
||||
| LZ | Yes | No |
|
||||
| LZX | Incomplete | No |
|
||||
| MSZIP | Incomplete | No |
|
||||
| Quantum | Incomplete | No |
|
||||
| MSZIP | Yes* | No |
|
||||
|
||||
**Note:** If something is marked with a `*` it means that it need testing.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- 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>
|
||||
<Version>0.1.1</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<!-- Package Properties -->
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Printing</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>compression decompression lz</PackageTags>
|
||||
<PackageTags>compression decompression lz mszip</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.1.1" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.1.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all compressor implementations
|
||||
/// </summary>
|
||||
public abstract class BaseCompressor : mspack_file
|
||||
{
|
||||
#if NET48
|
||||
public mspack_system system { get; set; }
|
||||
#else
|
||||
public mspack_system? system { get; set; }
|
||||
#endif
|
||||
|
||||
public MSPACK_ERR error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all decompressor implementations
|
||||
/// </summary>
|
||||
public abstract class BaseDecompressor : mspack_file
|
||||
{
|
||||
#if NET48
|
||||
public mspack_system system { get; set; }
|
||||
#else
|
||||
public mspack_system? system { get; set; }
|
||||
#endif
|
||||
|
||||
public MSPACK_ERR error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe abstract class BitStream
|
||||
{
|
||||
/// <summary>
|
||||
/// I/O routines
|
||||
/// </summary>
|
||||
public mspack_system sys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input file handle
|
||||
/// </summary>
|
||||
public mspack_file input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output file handle
|
||||
/// </summary>
|
||||
public mspack_file output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompression offset within window
|
||||
/// </summary>
|
||||
public uint window_posn { get; set; }
|
||||
|
||||
#region I/O buffering
|
||||
|
||||
public byte* inbuf { get; set; }
|
||||
|
||||
public byte* i_ptr { get; set; }
|
||||
|
||||
public byte* i_end { get; set; }
|
||||
|
||||
public byte* o_ptr { get; set; }
|
||||
|
||||
public byte* o_end { get; set; }
|
||||
|
||||
public int input_end { get; set; }
|
||||
|
||||
public uint bit_buffer { get; set; }
|
||||
|
||||
public uint bits_left { get; set; }
|
||||
|
||||
public uint inbuf_size { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public MSPACK_ERR error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.macros;
|
||||
using static SabreTools.Compression.libmspack.CAB.Constants;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.CAB
|
||||
{
|
||||
public unsafe class CABSystem : mspack_default_system
|
||||
{
|
||||
/// <summary>
|
||||
/// cabd_sys_read is the internal reader function which the decompressors
|
||||
/// use. will read data blocks (and merge split blocks) from the cabinet
|
||||
/// and serve the read bytes to the decompressors
|
||||
/// </summary>
|
||||
public override int read(mspack_file file, void* buffer, int bytes)
|
||||
{
|
||||
Decompressor self = (Decompressor)file;
|
||||
byte* buf = (byte*)buffer;
|
||||
mspack_system sys = self.system;
|
||||
int avail, todo, outlen = 0, ignore_cksum, ignore_blocksize;
|
||||
|
||||
ignore_cksum = self.salvage != 0 || (self.fix_mszip != 0 && ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_MSZIP)) == true ? 1 : 0;
|
||||
ignore_blocksize = self.salvage;
|
||||
|
||||
todo = bytes;
|
||||
while (todo > 0)
|
||||
{
|
||||
avail = (int)(self.d.i_end - self.d.i_ptr);
|
||||
|
||||
// If out of input data, read a new block
|
||||
if (avail != 0)
|
||||
{
|
||||
// Copy as many input bytes available as possible
|
||||
if (avail > todo) avail = todo;
|
||||
sys.copy(self.d.i_ptr, buf, avail);
|
||||
self.d.i_ptr += avail;
|
||||
buf += avail;
|
||||
todo -= avail;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Out of data, read a new block
|
||||
|
||||
// Check if we're out of input blocks, advance block counter
|
||||
if (self.d.block++ >= self.d.folder.num_blocks)
|
||||
{
|
||||
if (self.salvage == 0)
|
||||
{
|
||||
self.read_error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Console.Error.WriteLine("Ran out of CAB input blocks prematurely");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Read a block
|
||||
self.read_error = ReadBlock(sys, self.d, ref outlen, ignore_cksum, ignore_blocksize);
|
||||
if (self.read_error != MSPACK_ERR.MSPACK_ERR_OK) return -1;
|
||||
self.d.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 ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_QUANTUM)
|
||||
{
|
||||
*self.d.i_end++ = 0xFF;
|
||||
}
|
||||
|
||||
// Is this the last block?
|
||||
if (self.d.block >= self.d.folder.num_blocks)
|
||||
{
|
||||
if ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK) == MSCAB_COMP.MSCAB_COMP_LZX)
|
||||
{
|
||||
// Special LZX hack -- on the last block, inform LZX of the
|
||||
// size of the output data stream.
|
||||
lzxd_set_output_length((lzxd_stream)self.d.state, self.d.outlen);
|
||||
}
|
||||
}
|
||||
} /* if (avail) */
|
||||
} /* while (todo > 0) */
|
||||
return bytes - todo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// cabd_sys_write is the internal writer function which the decompressors
|
||||
/// use. it either writes data to disk (self.d.outfh) with the real
|
||||
/// sys.write() function, or does nothing with the data when
|
||||
/// self.d.outfh == null. advances self.d.offset
|
||||
/// </summary>
|
||||
public override int write(mspack_file file, void* buffer, int bytes)
|
||||
{
|
||||
Decompressor self = (Decompressor)file;
|
||||
self.d.offset += (uint)bytes;
|
||||
if (self.d.outfh != null)
|
||||
{
|
||||
return self.system.write(self.d.outfh, buffer, bytes);
|
||||
}
|
||||
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 MSPACK_ERR ReadBlock(mspack_system sys, mscabd_decompress_state d, ref int @out, int ignore_cksum, int ignore_blocksize)
|
||||
{
|
||||
FixedArray<byte> hdr = new FixedArray<byte>(cfdata_SIZEOF);
|
||||
uint cksum;
|
||||
int len, full_len;
|
||||
|
||||
// Reset the input block pointer and end of block pointer
|
||||
d.i_ptr = d.i_end = d.input;
|
||||
|
||||
do
|
||||
{
|
||||
// Read the block header
|
||||
if (sys.read(d.infh, hdr, cfdata_SIZEOF) != cfdata_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// Skip any reserved block headers
|
||||
if (d.data.cab.block_resv != 0 &&
|
||||
sys.seek(d.infh, d.data.cab.block_resv, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Blocks must not be over CAB_INPUTMAX in size
|
||||
len = EndGetI16(hdr, cfdata_CompressedSize);
|
||||
full_len = (int)(d.i_end - d.i_ptr + len); // Include cab-spanning blocks */
|
||||
if (full_len > CAB_INPUTMAX)
|
||||
{
|
||||
System.Console.Error.WriteLine($"Block size {full_len} > CAB_INPUTMAX");
|
||||
// In salvage mode, blocks can be 65535 bytes but no more than that
|
||||
if (ignore_blocksize == 0 || full_len > CAB_INPUTMAX_SALVAGE)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks must not expand to more than CAB_BLOCKMAX
|
||||
if (EndGetI16(hdr, cfdata_UncompressedSize) > CAB_BLOCKMAX)
|
||||
{
|
||||
System.Console.Error.WriteLine("block size > CAB_BLOCKMAX");
|
||||
if (ignore_blocksize == 0) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Read the block data
|
||||
if (sys.read(d.infh, d.i_end, len) != len)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// Perform checksum test on the block (if one is stored)
|
||||
if ((cksum = EndGetI32(hdr, cfdata_CheckSum)) != 0)
|
||||
{
|
||||
uint sum2 = Checksum(d.i_end, (uint)len, 0);
|
||||
if (Checksum(hdr, 4, 4, sum2) != cksum)
|
||||
{
|
||||
if (ignore_cksum == 0) return MSPACK_ERR.MSPACK_ERR_CHECKSUM;
|
||||
sys.message(d.infh, "WARNING; bad block checksum found");
|
||||
}
|
||||
}
|
||||
|
||||
// Advance end of block pointer to include newly read data
|
||||
d.i_end += len;
|
||||
|
||||
// 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.
|
||||
// otherwise, this is the last part of the block, and no more block
|
||||
// reading needs to be done.
|
||||
|
||||
// EXIT POINT OF LOOP -- uncompressed size != 0
|
||||
if ((@out = EndGetI16(hdr, cfdata_UncompressedSize)) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
// Otherwise, advance to next cabinet
|
||||
|
||||
// Close current file handle
|
||||
sys.close(d.infh);
|
||||
d.infh = null;
|
||||
|
||||
// Aadvance to next member in the cabinet set
|
||||
if ((d.data = d.data.next) == null)
|
||||
{
|
||||
sys.message(d.infh, "WARNING; ran out of cabinets in set. Are any missing?");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Open next cab file
|
||||
d.incab = d.data.cab;
|
||||
if ((d.infh = sys.open(d.incab.filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) == null)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
}
|
||||
|
||||
// Seek to start of data blocks
|
||||
if (sys.seek(d.infh, d.data.offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
// Not reached
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
private static uint Checksum(FixedArray<byte> data, int ptr, uint bytes, uint cksum)
|
||||
{
|
||||
uint len, ul = 0;
|
||||
|
||||
for (len = bytes >> 2; len-- > 0; ptr += 4)
|
||||
{
|
||||
cksum ^= EndGetI32(data, ptr);
|
||||
}
|
||||
|
||||
switch (bytes & 3)
|
||||
{
|
||||
case 3: ul |= (uint)(data[ptr++] << 16); goto case 2;
|
||||
case 2: ul |= (uint)(data[ptr++] << 8); goto case 1;
|
||||
case 1: ul |= data[ptr]; break;
|
||||
}
|
||||
cksum ^= ul;
|
||||
|
||||
return cksum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.CAB
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public class Compressor : BaseCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new CAB compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new CABSystem();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.CAB
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/* structure offsets */
|
||||
public const byte cfhead_Signature = 0x00;
|
||||
public const byte cfhead_CabinetSize = 0x08;
|
||||
public const byte cfhead_FileOffset = 0x10;
|
||||
public const byte cfhead_MinorVersion = 0x18;
|
||||
public const byte cfhead_MajorVersion = 0x19;
|
||||
public const byte cfhead_NumFolders = 0x1A;
|
||||
public const byte cfhead_NumFiles = 0x1C;
|
||||
public const byte cfhead_Flags = 0x1E;
|
||||
public const byte cfhead_SetID = 0x20;
|
||||
public const byte cfhead_CabinetIndex = 0x22;
|
||||
public const byte cfhead_SIZEOF = 0x24;
|
||||
public const byte cfheadext_HeaderReserved = 0x00;
|
||||
public const byte cfheadext_FolderReserved = 0x02;
|
||||
public const byte cfheadext_DataReserved = 0x03;
|
||||
public const byte cfheadext_SIZEOF = 0x04;
|
||||
public const byte cffold_DataOffset = 0x00;
|
||||
public const byte cffold_NumBlocks = 0x04;
|
||||
public const byte cffold_CompType = 0x06;
|
||||
public const byte cffold_SIZEOF = 0x08;
|
||||
public const byte cffile_UncompressedSize = 0x00;
|
||||
public const byte cffile_FolderOffset = 0x04;
|
||||
public const byte cffile_FolderIndex = 0x08;
|
||||
public const byte cffile_Date = 0x0A;
|
||||
public const byte cffile_Time = 0x0C;
|
||||
public const byte cffile_Attribs = 0x0E;
|
||||
public const byte cffile_SIZEOF = 0x10;
|
||||
public const byte cfdata_CheckSum = 0x00;
|
||||
public const byte cfdata_CompressedSize = 0x04;
|
||||
public const byte cfdata_UncompressedSize = 0x06;
|
||||
public const byte cfdata_SIZEOF = 0x08;
|
||||
|
||||
/* flags */
|
||||
public const ushort cffoldCOMPTYPE_MASK = 0x000f;
|
||||
public const ushort cffileCONTINUED_FROM_PREV = 0xFFFD;
|
||||
public const ushort cffileCONTINUED_TO_NEXT = 0xFFFE;
|
||||
public const ushort cffileCONTINUED_PREV_AND_NEXT = 0xFFFF;
|
||||
|
||||
/* CAB data blocks are <= 32768 bytes in uncompressed form. Uncompressed
|
||||
* blocks have zero growth. MSZIP guarantees that it won't grow above
|
||||
* uncompressed size by more than 12 bytes. LZX guarantees it won't grow
|
||||
* more than 6144 bytes. Quantum has no documentation, but the largest
|
||||
* block seen in the wild is 337 bytes above uncompressed size.
|
||||
*/
|
||||
public const int CAB_BLOCKMAX = 32768;
|
||||
public const int CAB_INPUTMAX = CAB_BLOCKMAX + 6144;
|
||||
|
||||
/* input buffer needs to be CAB_INPUTMAX + 1 byte to allow for max-sized block
|
||||
* plus 1 trailer byte added by cabd_sys_read_block() for Quantum alignment.
|
||||
*
|
||||
* When MSCABD_PARAM_SALVAGE is set, block size is not checked so can be
|
||||
* up to 65535 bytes, so max input buffer size needed is 65535 + 1
|
||||
*/
|
||||
public const int CAB_INPUTMAX_SALVAGE = 65535;
|
||||
public const int CAB_INPUTBUF = CAB_INPUTMAX_SALVAGE + 1;
|
||||
|
||||
/* There are no more than 65535 data blocks per folder, so a folder cannot
|
||||
* be more than 32768*65535 bytes in length. As files cannot span more than
|
||||
* one folder, this is also their max offset, length and offset+length limit.
|
||||
*/
|
||||
public const int CAB_FOLDERMAX = 65535;
|
||||
public const int CAB_LENGTHMAX = CAB_BLOCKMAX * CAB_FOLDERMAX;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.CAB.Constants;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe static class cab
|
||||
{
|
||||
#region decomp
|
||||
|
||||
/// <summary>
|
||||
/// cabd_free_decomp frees decompression state, according to which method
|
||||
/// was used.
|
||||
/// </summary>
|
||||
public static MSPACK_ERR cabd_init_decomp(CAB.Decompressor self, MSCAB_COMP ct)
|
||||
{
|
||||
mspack_file fh = self;
|
||||
|
||||
self.d.comp_type = ct;
|
||||
|
||||
switch ((MSCAB_COMP)((int)ct & cffoldCOMPTYPE_MASK))
|
||||
{
|
||||
case MSCAB_COMP.MSCAB_COMP_NONE:
|
||||
self.d = new None.DecompressState(self.d);
|
||||
self.d.state = new None.State(self.d.sys, fh, fh, self.buf_size);
|
||||
break;
|
||||
case MSCAB_COMP.MSCAB_COMP_MSZIP:
|
||||
self.d = new mscabd_mszipd_decompress_state();
|
||||
self.d.state = mszipd_init(self.d.sys, fh, fh, self.buf_size, self.fix_mszip);
|
||||
break;
|
||||
case MSCAB_COMP.MSCAB_COMP_QUANTUM:
|
||||
self.d = new mscabd_qtmd_decompress_state();
|
||||
self.d.state = qtmd_init(self.d.sys, fh, fh, ((int)ct >> 8) & 0x1f, self.buf_size);
|
||||
break;
|
||||
case MSCAB_COMP.MSCAB_COMP_LZX:
|
||||
self.d = new mscabd_lzxd_decompress_state();
|
||||
self.d.state = lzxd_init(self.d.sys, fh, fh, ((int)ct >> 8) & 0x1f, 0, self.buf_size, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return self.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
return self.error = (self.d.state != null) ? MSPACK_ERR.MSPACK_ERR_OK : MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// cabd_init_decomp initialises decompression state, according to which
|
||||
/// decompression method was used. relies on self.d.folder being the same
|
||||
/// as when initialised.
|
||||
/// </summary>
|
||||
public static void cabd_free_decomp(CAB.Decompressor self)
|
||||
{
|
||||
if (self == null || self.d == null || self.d.state == null) return;
|
||||
|
||||
switch ((MSCAB_COMP)((int)self.d.comp_type & cffoldCOMPTYPE_MASK))
|
||||
{
|
||||
case MSCAB_COMP.MSCAB_COMP_MSZIP: mszipd_free((mszipd_stream)self.d.state); break;
|
||||
case MSCAB_COMP.MSCAB_COMP_QUANTUM: qtmd_free((qtmd_stream)self.d.state); break;
|
||||
case MSCAB_COMP.MSCAB_COMP_LZX: lzxd_free((lzxd_stream)self.d.state); break;
|
||||
}
|
||||
|
||||
//self.d.decompress = null;
|
||||
self.d.state = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a single cabinet file.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
///
|
||||
/// If this cabinet is part of a merged cabinet set, the #files and #folders
|
||||
/// fields are common to all cabinets in the set, and will be identical.
|
||||
/// </summary>
|
||||
/// <see cref="mscab_decompressor::open()"/>
|
||||
/// <see cref="mscab_decompressor::close()"/>
|
||||
/// <see cref="mscab_decompressor::search()"/>
|
||||
public unsafe class mscabd_cabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// The next cabinet in a chained list, if this cabinet was opened with
|
||||
/// mscab_decompressor::search(). May be null to mark the end of the
|
||||
/// list.
|
||||
/// </summary>
|
||||
public mscabd_cabinet next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the cabinet. More correctly, the filename of the
|
||||
/// physical file that the cabinet resides in. This is given by the
|
||||
/// library user and may be in any format.
|
||||
/// </summary>
|
||||
public string filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file offset of cabinet within the physical file it resides in.
|
||||
/// </summary>
|
||||
public long base_offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the cabinet file in bytes.
|
||||
/// </summary>
|
||||
public uint length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The previous cabinet in a cabinet set, or null.
|
||||
/// </summary>
|
||||
public mscabd_cabinet prevcab { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next cabinet in a cabinet set, or null.
|
||||
/// </summary>
|
||||
public mscabd_cabinet nextcab { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the previous cabinet in a cabinet set, or null.
|
||||
/// </summary>
|
||||
public string prevname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the next cabinet in a cabinet set, or null.
|
||||
/// </summary>
|
||||
public string nextname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the disk containing the previous cabinet in a cabinet
|
||||
/// set, or null.
|
||||
/// </summary>
|
||||
public string previnfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the disk containing the next cabinet in a cabinet set,
|
||||
/// or null.
|
||||
/// </summary>
|
||||
public string nextinfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all files in the cabinet or cabinet set.
|
||||
/// </summary>
|
||||
public mscabd_file files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all folders in the cabinet or cabinet set.
|
||||
/// </summary>
|
||||
public mscabd_folder folders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The set ID of the cabinet. All cabinets in the same set should have
|
||||
/// the same set ID.
|
||||
/// </summary>
|
||||
public ushort set_id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The index number of the cabinet within the set. Numbering should
|
||||
/// start from 0 for the first cabinet in the set, and increment by 1 for
|
||||
/// each following cabinet.
|
||||
/// </summary>
|
||||
public ushort set_index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes reserved in the header area of the cabinet.
|
||||
///
|
||||
/// If this is non-zero and flags has MSCAB_HDR_RESV set, this data can
|
||||
/// be read by the calling application. It is of the given length,
|
||||
/// located at offset (base_offset + MSCAB_HDR_RESV_OFFSET) in the
|
||||
/// cabinet file.
|
||||
/// </summary>
|
||||
public ushort header_resv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header flags.
|
||||
/// </summary>
|
||||
/// <see cref="prevname"/>
|
||||
/// <see cref="previnfo"/>
|
||||
/// <see cref="nextname"/>
|
||||
/// <see cref="nextinfo"/>
|
||||
/// <see cref="header_resv"/>
|
||||
public MSCAB_HDR flags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset to data blocks
|
||||
/// </summary>
|
||||
public long blocks_off { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space in data blocks
|
||||
/// </summary>
|
||||
public int block_resv { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using SabreTools.Compression.libmspack.CAB;
|
||||
using static SabreTools.Compression.libmspack.CAB.Constants;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe abstract class mscabd_decompress_state
|
||||
{
|
||||
/// <summary>
|
||||
/// Current folder we're extracting from
|
||||
/// </summary>
|
||||
public mscabd_folder folder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current folder split we're in
|
||||
/// </summary>
|
||||
public mscabd_folder_data data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed offset within folder
|
||||
/// </summary>
|
||||
public uint offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which block are we decompressing?
|
||||
/// </summary>
|
||||
public uint block { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cumulative sum of block output sizes
|
||||
/// </summary>
|
||||
public long outlen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Special I/O code for decompressor
|
||||
/// </summary>
|
||||
public CABSystem sys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of compression used by folder
|
||||
/// </summary>
|
||||
public MSCAB_COMP comp_type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompressor state
|
||||
/// </summary>
|
||||
public object state { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet where input data comes from
|
||||
/// </summary>
|
||||
public mscabd_cabinet incab { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input file handle
|
||||
/// </summary>
|
||||
public mspack_file infh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output file handle
|
||||
/// </summary>
|
||||
public mspack_file outfh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input data consumed
|
||||
/// </summary>
|
||||
public byte* i_ptr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input data end
|
||||
/// </summary>
|
||||
public byte* i_end { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// One input block of data
|
||||
/// </summary>
|
||||
public FixedArray<byte> input { get; set; } = new FixedArray<byte>(CAB_INPUTBUF);
|
||||
|
||||
/// <summary>
|
||||
/// Decompressor code
|
||||
/// </summary>
|
||||
public abstract MSPACK_ERR decompress(object data, long offset);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a single file in a cabinet or cabinet set.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class mscabd_file
|
||||
{
|
||||
/// <summary>
|
||||
/// The next file in the cabinet or cabinet set, or null if this is the
|
||||
/// final file.
|
||||
/// </summary>
|
||||
public mscabd_file next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the file.
|
||||
///
|
||||
/// A null terminated string of up to 255 bytes in length, it may be in
|
||||
/// either ISO-8859-1 or UTF8 format, depending on the file attributes.
|
||||
/// </summary>
|
||||
public string filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The uncompressed length of the file, in bytes.
|
||||
/// </summary>
|
||||
public uint length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File attributes.
|
||||
/// </summary>
|
||||
public MSCAB_ATTRIB attribs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified time, hour field.
|
||||
/// </summary>
|
||||
public char time_h { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified time, minute field.
|
||||
/// </summary>
|
||||
public char time_m { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified time, second field.
|
||||
/// </summary>
|
||||
public char time_s { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified date, day field.
|
||||
/// </summary>
|
||||
public char date_d { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified date, month field.
|
||||
/// </summary>
|
||||
public char date_m { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File's last modified date, year field.
|
||||
/// </summary>
|
||||
public int date_y;
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the folder that contains this file.
|
||||
/// </summary>
|
||||
public mscabd_folder folder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The uncompressed offset of this file in its folder.
|
||||
/// </summary>
|
||||
public uint offset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a single folder in a cabinet or cabinet set.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
///
|
||||
/// A folder is a single compressed stream of data. When uncompressed, it
|
||||
/// holds the data of one or more files. A folder may be split across more
|
||||
/// than one cabinet.
|
||||
/// </summary>
|
||||
public class mscabd_folder
|
||||
{
|
||||
/// <summary>
|
||||
/// A pointer to the next folder in this cabinet or cabinet set, or null
|
||||
/// if this is the final folder.
|
||||
/// </summary>
|
||||
public mscabd_folder next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The compression format used by this folder.
|
||||
///
|
||||
/// The macro MSCABD_COMP_METHOD() should be used on this field to get
|
||||
/// the algorithm used. The macro MSCABD_COMP_LEVEL() should be used to get
|
||||
/// the "compression level".
|
||||
/// </summary>
|
||||
/// <see cref="MSCABD_COMP_METHOD()"/>
|
||||
/// <see cref="MSCABD_COMP_LEVEL()"/>
|
||||
public MSCAB_COMP comp_type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of data blocks used by this folder. This includes
|
||||
/// data blocks present in other files, if this folder spans more than
|
||||
/// one cabinet.
|
||||
/// </summary>
|
||||
public uint num_blocks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Where are the data blocks?
|
||||
/// </summary>
|
||||
public mscabd_folder_data data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// First file needing backwards merge
|
||||
/// </summary>
|
||||
public mscabd_file merge_prev { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// First file needing forwards merge
|
||||
/// </summary>
|
||||
public mscabd_file merge_next { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// There is one of these for every cabinet a folder spans
|
||||
/// </summary>
|
||||
public class mscabd_folder_data
|
||||
{
|
||||
public mscabd_folder_data next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet file of this folder span
|
||||
/// </summary>
|
||||
public mscabd_cabinet cab { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet offset of first datablock
|
||||
/// </summary>
|
||||
public long offset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.CHM
|
||||
{
|
||||
/// <summary>
|
||||
/// A compressor for .CHM (Microsoft HTMLHelp) files.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
/// <see cref="mspack.DestroyCHMCompressor(Compressor)"/>
|
||||
public class Compressor : BaseCompressor
|
||||
{
|
||||
public string temp_file { get; private set; }
|
||||
|
||||
public int use_temp_file { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CHM compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a CHM help file.
|
||||
///
|
||||
/// The help file will contain up to two sections, an Uncompressed
|
||||
/// section and potentially an MSCompressed (LZX compressed)
|
||||
/// section.
|
||||
///
|
||||
/// While the contents listing of a CHM file is always in lexical order,
|
||||
/// the file list passed in will be taken as the correct order for files
|
||||
/// within the sections. It is in your interest to place similar files
|
||||
/// together for better compression.
|
||||
///
|
||||
/// There are two modes of generation, to use a temporary file or not to
|
||||
/// use one. See use_temporary_file() for the behaviour of generate() in
|
||||
/// these two different modes.
|
||||
/// </summary>
|
||||
/// <param name="file_list">
|
||||
/// An array of mschmc_file structures, terminated
|
||||
/// with an entry whose mschmc_file::section field is
|
||||
/// #MSCHMC_ENDLIST. The order of the list is
|
||||
/// preserved within each section. The length of any
|
||||
/// mschmc_file::chm_filename string cannot exceed
|
||||
/// roughly 4096 bytes. Each source file must be able
|
||||
/// to supply as many bytes as given in the
|
||||
/// mschmc_file::length field.
|
||||
/// </param>
|
||||
/// <param name="output_file">
|
||||
/// The file to write the generated CHM helpfile to.
|
||||
/// This is passed directly to mspack_system::open()
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
/// <see cref="use_temporary_file(int in string)"/>
|
||||
/// <see cref="set_param(MSCHMC_PARAM, int)"/>
|
||||
public MSPACK_ERR generate(mschmc_file[] file_list, in string output_file) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether a temporary file is used during CHM generation.
|
||||
///
|
||||
/// The CHM file format includes data about the compressed section (such
|
||||
/// as its overall size) that is stored in the output CHM file prior to
|
||||
/// the compressed section itself. This unavoidably requires that the
|
||||
/// compressed section has to be generated, before these details can be
|
||||
/// set. There are several ways this can be handled. Firstly, the
|
||||
/// compressed section could be generated entirely in memory before
|
||||
/// writing any of the output CHM file. This approach is not used in
|
||||
/// libmspack, as the compressed section can exceed the addressable
|
||||
/// memory space on most architectures.
|
||||
///
|
||||
/// libmspack has two options, either to write these unknowable sections
|
||||
/// with blank data, generate the compressed section, then re-open the
|
||||
/// output file for update once the compressed section has been
|
||||
/// completed, or to write the compressed section to a temporary file,
|
||||
/// then write the entire output file at once, performing a simple
|
||||
/// file-to-file copy for the compressed section.
|
||||
///
|
||||
/// The simple solution of buffering the entire compressed section in
|
||||
/// memory can still be used, if desired. As the temporary file's
|
||||
/// filename is passed directly to mspack_system::open(), it is possible
|
||||
/// for a custom mspack_system implementation to hold this file in memory,
|
||||
/// without writing to a disk.
|
||||
///
|
||||
/// If a temporary file is set, generate() performs the following
|
||||
/// sequence of events: the temporary file is opened for writing, the
|
||||
/// compression algorithm writes to the temporary file, the temporary
|
||||
/// file is closed. Then the output file is opened for writing and the
|
||||
/// temporary file is re-opened for reading. The output file is written
|
||||
/// and the temporary file is read from. Both files are then closed. The
|
||||
/// temporary file itself is not deleted. If that is desired, the
|
||||
/// temporary file should be deleted after the completion of generate(),
|
||||
/// if it exists.
|
||||
///
|
||||
/// If a temporary file is set not to be used, generate() performs the
|
||||
/// following sequence of events: the output file is opened for writing,
|
||||
/// then it is written and closed. The output file is then re-opened for
|
||||
/// update, the appropriate sections are seek()ed to and re-written, then
|
||||
/// the output file is closed.
|
||||
/// </summary>
|
||||
/// <param name="use_temp_file">
|
||||
/// Non-zero if the temporary file should be used,
|
||||
/// zero if the temporary file should not be used.
|
||||
/// </param>
|
||||
/// <param name="temp_file">
|
||||
/// A file to temporarily write compressed data to,
|
||||
/// before opening it for reading and copying the
|
||||
/// contents to the output file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
/// <see cref="generate(mschmc_file[], in string)"/>
|
||||
public MSPACK_ERR use_temporary_file(int use_temp_file, in string temp_file) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a CHM compression engine parameter.
|
||||
///
|
||||
/// The following parameters are defined:
|
||||
///
|
||||
/// - #MSCHMC_PARAM_TIMESTAMP: Sets the "timestamp" of the CHM file
|
||||
/// generated. This is not a timestamp, see mschmd_header::timestamp
|
||||
/// for a description. If this timestamp is 0, generate() will use its
|
||||
/// own algorithm for making a unique ID, based on the lengths and
|
||||
/// names of files in the CHM itself. Defaults to 0, any value between
|
||||
/// 0 and (2^32)-1 is valid.
|
||||
/// - #MSCHMC_PARAM_LANGUAGE: Sets the "language" of the CHM file
|
||||
/// generated. This is not the language used in the CHM file, but the
|
||||
/// language setting of the user who ran the HTMLHelp compiler. It
|
||||
/// defaults to 0x0409. The valid range is between 0x0000 and 0x7F7F.
|
||||
/// - #MSCHMC_PARAM_LZXWINDOW: Sets the size of the LZX history window,
|
||||
/// which is also the interval at which the compressed data stream can be
|
||||
/// randomly accessed. The value is not a size in bytes, but a power of
|
||||
/// two. The default value is 16 (which makes the window 2^16 bytes, or
|
||||
/// 64 kilobytes), the valid range is from 15 (32 kilobytes) to 21 (2
|
||||
/// megabytes).
|
||||
/// - #MSCHMC_PARAM_DENSITY: Sets the "density" of quick reference
|
||||
/// entries stored at the end of directory listing chunk. Each chunk is
|
||||
/// 4096 bytes in size, and contains as many file entries as there is
|
||||
/// room for. At the other end of the chunk, a list of "quick reference"
|
||||
/// pointers is included. The offset of every 'N'th file entry is given a
|
||||
/// quick reference, where N = (2^density) + 1. The default density is
|
||||
/// 2. The smallest density is 0 (N=2), the maximum is 10 (N=1025). As
|
||||
/// each file entry requires at least 5 bytes, the maximum number of
|
||||
/// entries in a single chunk is roughly 800, so the maximum value 10
|
||||
/// can be used to indicate there are no quickrefs at all.
|
||||
/// - #MSCHMC_PARAM_INDEX: Sets whether or not to include quick lookup
|
||||
/// index chunk(s), in addition to normal directory listing chunks. A
|
||||
/// value of zero means no index chunks will be created, a non-zero value
|
||||
/// means index chunks will be created. The default is zero, "don't
|
||||
/// create an index".
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter to set</param>
|
||||
/// <param name="value">The value to set the parameter to</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
|
||||
/// is a problem with either parameter or value.
|
||||
/// </returns>
|
||||
/// <see cref="generate(mschmc_file[], in string)"/>
|
||||
public MSPACK_ERR set_param(MSCHMC_PARAM param, int value) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="set_param(int, int)"/>
|
||||
/// <see cref="generate(mschmc_file[], in string)"/>
|
||||
public MSPACK_ERR last_error() => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.CHM
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const ushort chmhead_Signature = 0x0000;
|
||||
public const ushort chmhead_Version = 0x0004;
|
||||
public const ushort chmhead_HeaderLen = 0x0008;
|
||||
public const ushort chmhead_Unknown1 = 0x000C;
|
||||
public const ushort chmhead_Timestamp = 0x0010;
|
||||
public const ushort chmhead_LanguageID = 0x0014;
|
||||
public const ushort chmhead_GUID1 = 0x0018;
|
||||
public const ushort chmhead_GUID2 = 0x0028;
|
||||
public const ushort chmhead_SIZEOF = 0x0038;
|
||||
|
||||
public const ushort chmhst_OffsetHS0 = 0x0000;
|
||||
public const ushort chmhst_LengthHS0 = 0x0008;
|
||||
public const ushort chmhst_OffsetHS1 = 0x0010;
|
||||
public const ushort chmhst_LengthHS1 = 0x0018;
|
||||
public const ushort chmhst_SIZEOF = 0x0020;
|
||||
public const ushort chmhst3_OffsetCS0 = 0x0020;
|
||||
public const ushort chmhst3_SIZEOF = 0x0028;
|
||||
|
||||
public const ushort chmhs0_Unknown1 = 0x0000;
|
||||
public const ushort chmhs0_Unknown2 = 0x0004;
|
||||
public const ushort chmhs0_FileLen = 0x0008;
|
||||
public const ushort chmhs0_Unknown3 = 0x0010;
|
||||
public const ushort chmhs0_Unknown4 = 0x0014;
|
||||
public const ushort chmhs0_SIZEOF = 0x0018;
|
||||
|
||||
public const ushort chmhs1_Signature = 0x0000;
|
||||
public const ushort chmhs1_Version = 0x0004;
|
||||
public const ushort chmhs1_HeaderLen = 0x0008;
|
||||
public const ushort chmhs1_Unknown1 = 0x000C;
|
||||
public const ushort chmhs1_ChunkSize = 0x0010;
|
||||
public const ushort chmhs1_Density = 0x0014;
|
||||
public const ushort chmhs1_Depth = 0x0018;
|
||||
public const ushort chmhs1_IndexRoot = 0x001C;
|
||||
public const ushort chmhs1_FirstPMGL = 0x0020;
|
||||
public const ushort chmhs1_LastPMGL = 0x0024;
|
||||
public const ushort chmhs1_Unknown2 = 0x0028;
|
||||
public const ushort chmhs1_NumChunks = 0x002C;
|
||||
public const ushort chmhs1_LanguageID = 0x0030;
|
||||
public const ushort chmhs1_GUID = 0x0034;
|
||||
public const ushort chmhs1_Unknown3 = 0x0044;
|
||||
public const ushort chmhs1_Unknown4 = 0x0048;
|
||||
public const ushort chmhs1_Unknown5 = 0x004C;
|
||||
public const ushort chmhs1_Unknown6 = 0x0050;
|
||||
public const ushort chmhs1_SIZEOF = 0x0054;
|
||||
|
||||
public const ushort pmgl_Signature = 0x0000;
|
||||
public const ushort pmgl_QuickRefSize = 0x0004;
|
||||
public const ushort pmgl_Unknown1 = 0x0008;
|
||||
public const ushort pmgl_PrevChunk = 0x000C;
|
||||
public const ushort pmgl_NextChunk = 0x0010;
|
||||
public const ushort pmgl_Entries = 0x0014;
|
||||
public const ushort pmgl_headerSIZEOF = 0x0014;
|
||||
|
||||
public const ushort pmgi_Signature = 0x0000;
|
||||
public const ushort pmgi_QuickRefSize = 0x0004;
|
||||
public const ushort pmgi_Entries = 0x0008;
|
||||
public const ushort pmgi_headerSIZEOF = 0x000C;
|
||||
|
||||
public const ushort lzxcd_Length = 0x0000;
|
||||
public const ushort lzxcd_Signature = 0x0004;
|
||||
public const ushort lzxcd_Version = 0x0008;
|
||||
public const ushort lzxcd_ResetInterval = 0x000C;
|
||||
public const ushort lzxcd_WindowSize = 0x0010;
|
||||
public const ushort lzxcd_CacheSize = 0x0014;
|
||||
public const ushort lzxcd_Unknown1 = 0x0018;
|
||||
public const ushort lzxcd_SIZEOF = 0x001C;
|
||||
|
||||
public const ushort lzxrt_Unknown1 = 0x0000;
|
||||
public const ushort lzxrt_NumEntries = 0x0004;
|
||||
public const ushort lzxrt_EntrySize = 0x0008;
|
||||
public const ushort lzxrt_TableOffset = 0x000C;
|
||||
public const ushort lzxrt_UncompLen = 0x0010;
|
||||
public const ushort lzxrt_CompLen = 0x0018;
|
||||
public const ushort lzxrt_FrameLen = 0x0020;
|
||||
public const ushort lzxrt_Entries = 0x0028;
|
||||
public const ushort lzxrt_headerSIZEOF = 0x0028;
|
||||
}
|
||||
}
|
||||
@@ -1,922 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using static SabreTools.Compression.libmspack.CHM.Constants;
|
||||
using static SabreTools.Compression.libmspack.macros;
|
||||
using static SabreTools.Compression.libmspack.system;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.CHM
|
||||
{
|
||||
/// <summary>
|
||||
/// A decompressor for .CHM (Microsoft HTMLHelp) files
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
/// <see cref="mspack.DestroyCHMDecomperssor(Decompressor)"/>
|
||||
public unsafe class Decompressor : BaseDecompressor
|
||||
{
|
||||
public mschmd_decompress_state d { get; private set; }
|
||||
|
||||
// Filenames of the system files used for decompression.
|
||||
// Content and ControlData are essential.
|
||||
// ResetTable is preferred, but SpanInfo can be used if not available
|
||||
private const string content_name = "::DataSpace/Storage/MSCompressed/Content";
|
||||
private const string control_name = "::DataSpace/Storage/MSCompressed/ControlData";
|
||||
private const string spaninfo_name = "::DataSpace/Storage/MSCompressed/SpanInfo";
|
||||
private const string rtable_name = "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable";
|
||||
|
||||
// The GUIDs found in CHM header
|
||||
private static readonly byte[] guids = new byte[32]
|
||||
{
|
||||
// {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}
|
||||
0x10, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11,
|
||||
0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC,
|
||||
|
||||
// {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}
|
||||
0x11, 0xFD, 0x01, 0x7C, 0xAA, 0x7B, 0xD0, 0x11,
|
||||
0x9E, 0x0C, 0x00, 0xA0, 0xC9, 0x22, 0xE6, 0xEC
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CHM decompressor
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
d = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys an existing CHM decompressor
|
||||
/// </summary>
|
||||
~Decompressor()
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
if (this.d != null)
|
||||
{
|
||||
if (this.d.infh != null) sys.close(this.d.infh);
|
||||
if (this.d.state != null) lzxd_free(this.d.state);
|
||||
//sys.free(this.d);
|
||||
}
|
||||
//sys.free(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a CHM helpfile and reads its contents.
|
||||
///
|
||||
/// If the file opened is a valid CHM helpfile, all headers will be read
|
||||
/// and a mschmd_header structure will be returned, with a full list of
|
||||
/// files.
|
||||
///
|
||||
/// In the case of an error occuring, null is returned and the error code
|
||||
/// is available from last_error().
|
||||
///
|
||||
/// The filename pointer should be considered "in use" until close() is
|
||||
/// called on the CHM helpfile.
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// The filename of the CHM helpfile. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>A pointer to a mschmd_header structure, or null on failure</returns>
|
||||
/// <see cref="close(mschmd_header)"/>
|
||||
public mschmd_header open(in string filename)
|
||||
{
|
||||
return chmd_real_open(filename, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a previously opened CHM helpfile.
|
||||
///
|
||||
/// This closes a CHM helpfile, frees the mschmd_header and all
|
||||
/// mschmd_file structures associated with it (if any). This works on
|
||||
/// both helpfiles opened with open() and helpfiles opened with
|
||||
/// fast_open().
|
||||
///
|
||||
/// The CHM header pointer is now invalid and cannot be used again. All
|
||||
/// mschmd_file pointers referencing that CHM are also now invalid, and
|
||||
/// cannot be used again.
|
||||
/// </summary>
|
||||
/// <param name="chm">The CHM helpfile to close</param>
|
||||
/// <see cref="open(in string)"/>
|
||||
/// <see cref="fast_open(in string)"/>
|
||||
public void close(mschmd_header chm)
|
||||
{
|
||||
mschmd_file fi, nfi;
|
||||
mspack_system sys;
|
||||
uint i;
|
||||
|
||||
sys = this.system;
|
||||
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
// Free files
|
||||
for (fi = chm.files; fi != null; fi = nfi)
|
||||
{
|
||||
nfi = fi.next;
|
||||
//sys.free(fi);
|
||||
}
|
||||
for (fi = chm.sysfiles; fi != null; fi = nfi)
|
||||
{
|
||||
nfi = fi.next;
|
||||
//sys.free(fi);
|
||||
}
|
||||
|
||||
// If this CHM was being decompressed, free decompression state
|
||||
if (this.d != null && (this.d.chm == chm))
|
||||
{
|
||||
if (this.d.infh != null) sys.close(this.d.infh);
|
||||
if (this.d.state != null) lzxd_free(this.d.state);
|
||||
//sys.free(this.d);
|
||||
this.d = null;
|
||||
}
|
||||
|
||||
// If this CHM had a chunk cache, free it and contents
|
||||
if (chm.chunk_cache != null)
|
||||
{
|
||||
for (i = 0; i < chm.num_chunks; i++) sys.free(chm.chunk_cache[i]);
|
||||
sys.free(chm.chunk_cache);
|
||||
}
|
||||
|
||||
//sys.free(chm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the basic CHM file headers. If the "entire" parameter is
|
||||
/// non-zero, all file entries will also be read. fills out a pre-existing
|
||||
/// mschmd_header structure, allocates memory for files as necessary
|
||||
/// </summary>
|
||||
private MSPACK_ERR chmd_read_headers(mspack_system sys, mspack_file fh, mschmd_header chm, int entire)
|
||||
{
|
||||
uint errors, num_chunks;
|
||||
FixedArray<byte> buf = new FixedArray<byte>(0x54);
|
||||
FixedArray<byte> chunk = null;
|
||||
byte* name, p, end;
|
||||
mschmd_file fi, link = null;
|
||||
long offset_hs0, filelen;
|
||||
int num_entries;
|
||||
MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
// Initialise pointers
|
||||
chm.files = null;
|
||||
chm.sysfiles = null;
|
||||
chm.chunk_cache = null;
|
||||
chm.sec0.chm = chm;
|
||||
chm.sec0.id = 0;
|
||||
chm.sec1.chm = chm;
|
||||
chm.sec1.id = 1;
|
||||
chm.sec1.content = null;
|
||||
chm.sec1.control = null;
|
||||
chm.sec1.spaninfo = null;
|
||||
chm.sec1.rtable = null;
|
||||
|
||||
// Read the first header
|
||||
if (sys.read(fh, buf, chmhead_SIZEOF) != chmhead_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// Check ITSF signature
|
||||
if (EndGetI32(buf, chmhead_Signature) != 0x46535449)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
}
|
||||
|
||||
// Check both header GUIDs
|
||||
if (!buf.ToArray().Skip(chmhead_GUID1).Take(guids.Length).SequenceEqual(guids))
|
||||
{
|
||||
Console.Error.WriteLine("Incorrect GUIDs");
|
||||
return MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
}
|
||||
|
||||
chm.version = EndGetI32(buf, chmhead_Version);
|
||||
chm.timestamp = EndGetM32(buf, chmhead_Timestamp);
|
||||
chm.language = EndGetI32(buf, chmhead_LanguageID);
|
||||
if (chm.version > 3)
|
||||
{
|
||||
sys.message(fh, "WARNING; CHM version > 3");
|
||||
}
|
||||
|
||||
// Read the header section table
|
||||
if (sys.read(fh, buf, chmhst3_SIZEOF) != chmhst3_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// chmhst3_OffsetCS0 does not exist in version 1 or 2 CHM files.
|
||||
// The offset will be corrected later, once HS1 is read.
|
||||
if (read_off64(&offset_hs0, &buf[chmhst_OffsetHS0], sys, fh) ||
|
||||
read_off64(&chm.dir_offset, &buf[chmhst_OffsetHS1], sys, fh) ||
|
||||
read_off64(&chm.sec0.offset, &buf[chmhst3_OffsetCS0], sys, fh))
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Seek to header section 0
|
||||
if (sys.seek(fh, offset_hs0, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Read header section 0
|
||||
if (sys.read(fh, buf, chmhs0_SIZEOF) != chmhs0_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
if (read_off64(&chm.length, &buf[chmhs0_FileLen], sys, fh))
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Compare declared CHM file size against actual size
|
||||
if (mspack_sys_filelen(sys, fh, &filelen) == 0)
|
||||
{
|
||||
if (chm.length > filelen)
|
||||
{
|
||||
sys.message(fh, $"WARNING; file possibly truncated by {chm.length - filelen} bytes");
|
||||
}
|
||||
else if (chm.length < filelen)
|
||||
{
|
||||
sys.message(fh, $"WARNING; possible {filelen - chm.length} extra bytes at end of file");
|
||||
}
|
||||
}
|
||||
|
||||
// Seek to header section 1
|
||||
if (sys.seek(fh, chm.dir_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Read header section 1
|
||||
if (sys.read(fh, buf, chmhs1_SIZEOF) != chmhs1_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
chm.dir_offset = sys.tell(fh);
|
||||
chm.chunk_size = EndGetI32(buf, chmhs1_ChunkSize);
|
||||
chm.density = EndGetI32(buf, chmhs1_Density);
|
||||
chm.depth = EndGetI32(buf, chmhs1_Depth);
|
||||
chm.index_root = EndGetI32(buf, chmhs1_IndexRoot);
|
||||
chm.num_chunks = EndGetI32(buf, chmhs1_NumChunks);
|
||||
chm.first_pmgl = EndGetI32(buf, chmhs1_FirstPMGL);
|
||||
chm.last_pmgl = EndGetI32(buf, chmhs1_LastPMGL);
|
||||
|
||||
if (chm.version < 3)
|
||||
{
|
||||
// Versions before 3 don't have chmhst3_OffsetCS0
|
||||
chm.sec0.offset = chm.dir_offset + (chm.chunk_size * chm.num_chunks);
|
||||
}
|
||||
|
||||
// Check if content offset or file size is wrong
|
||||
if (chm.sec0.offset > chm.length)
|
||||
{
|
||||
Console.Error.WriteLine("content section begins after file has ended");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Ensure there are chunks and that chunk size is
|
||||
// large enough for signature and num_entries
|
||||
if (chm.chunk_size < (pmgl_Entries + 2))
|
||||
{
|
||||
Console.Error.WriteLine("chunk size not large enough");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
if (chm.num_chunks == 0)
|
||||
{
|
||||
Console.Error.WriteLine("no chunks");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// The chunk_cache data structure is not great; large values for num_chunks
|
||||
// or num_chunks*chunk_size can exhaust all memory. Until a better chunk
|
||||
// cache is implemented, put arbitrary limits on num_chunks and chunk size.
|
||||
if (chm.num_chunks > 100000)
|
||||
{
|
||||
Console.Error.WriteLine("more than 100,000 chunks");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
if (chm.chunk_size > 8192)
|
||||
{
|
||||
Console.Error.WriteLine("chunk size over 8192 (get in touch if this is valid)");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
if ((long)chm.chunk_size * (long)chm.num_chunks > chm.length)
|
||||
{
|
||||
Console.Error.WriteLine("chunks larger than entire file");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Common sense checks on header section 1 fields
|
||||
if (chm.chunk_size != 4096)
|
||||
{
|
||||
sys.message(fh, "WARNING; chunk size is not 4096");
|
||||
}
|
||||
if (chm.first_pmgl != 0)
|
||||
{
|
||||
sys.message(fh, "WARNING; first PMGL chunk is not zero");
|
||||
}
|
||||
if (chm.first_pmgl > chm.last_pmgl)
|
||||
{
|
||||
Console.Error.WriteLine("first pmgl chunk is after last pmgl chunk");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
if (chm.index_root != 0xFFFFFFFF && chm.index_root >= chm.num_chunks)
|
||||
{
|
||||
Console.Error.WriteLine("index_root outside valid range");
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// If we are doing a quick read, stop here!
|
||||
if (entire == 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
// Seek to the first PMGL chunk, and reduce the number of chunks to read
|
||||
if (chm.first_pmgl != 0)
|
||||
{
|
||||
long pmgl_offset = (long)chm.first_pmgl * (long)chm.chunk_size;
|
||||
if (sys.seek(fh, pmgl_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
}
|
||||
num_chunks = chm.last_pmgl - chm.first_pmgl + 1;
|
||||
|
||||
chunk = new FixedArray<byte>((int)chm.chunk_size);
|
||||
|
||||
// Read and process all chunks from FirstPMGL to LastPMGL
|
||||
errors = 0;
|
||||
while (num_chunks-- > 0)
|
||||
{
|
||||
// Read next chunk
|
||||
if (sys.read(fh, chunk, (int)chm.chunk_size) != (int)chm.chunk_size)
|
||||
{
|
||||
sys.free(chunk);
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// Process only directory (PMGL) chunks
|
||||
if (EndGetI32(chunk, pmgl_Signature) != 0x4C474D50) continue;
|
||||
|
||||
if (EndGetI32(chunk, pmgl_QuickRefSize) < 2)
|
||||
{
|
||||
sys.message(fh, "WARNING; PMGL quickref area is too small");
|
||||
}
|
||||
if (EndGetI32(chunk, pmgl_QuickRefSize) >
|
||||
(chm.chunk_size - pmgl_Entries))
|
||||
{
|
||||
sys.message(fh, "WARNING; PMGL quickref area is too large");
|
||||
}
|
||||
|
||||
p = (byte*)chunk.Pointer + pmgl_Entries;
|
||||
end = (byte*)chunk.Pointer + chm.chunk_size - 2;
|
||||
num_entries = EndGetI16(chunk, (int)(chm.chunk_size - 2));
|
||||
|
||||
while (num_entries-- > 0)
|
||||
{
|
||||
uint name_len, section;
|
||||
long offset, length;
|
||||
name_len = read_encint(&p, end, &err);
|
||||
if (err != MSPACK_ERR.MSPACK_ERR_OK || (name_len > (uint)(end - p))) goto encint_err;
|
||||
name = p; p += name_len;
|
||||
section = read_encint(&p, end, &err);
|
||||
offset = read_encint(&p, end, &err);
|
||||
length = read_encint(&p, end, &err);
|
||||
if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err;
|
||||
|
||||
// Ignore blank or one-char (e.g. "/") filenames we'd return as blank */
|
||||
if (name_len < 2 || name[0] == 0x00 || name[1] == 0x00) continue;
|
||||
|
||||
// Empty files and directory names are stored as a file entry at
|
||||
// offset 0 with length 0. We want to keep empty files, but not
|
||||
// directory names, which end with a "/"
|
||||
if ((offset == 0) && (length == 0))
|
||||
{
|
||||
if ((name_len > 0) && (name[name_len - 1] == '/')) continue;
|
||||
}
|
||||
|
||||
if (section > 1)
|
||||
{
|
||||
sys.message(fh, $"Invalid section number '{section}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
fi = new mschmd_file();
|
||||
fi.next = null;
|
||||
fi.section = (section == 0 ? (mschmd_section)chm.sec0 : (mschmd_section)chm.sec1);
|
||||
fi.offset = offset;
|
||||
fi.length = length;
|
||||
|
||||
char[] filenameArr = new char[name_len];
|
||||
Marshal.Copy((IntPtr)name, filenameArr, 0, (int)name_len);
|
||||
filenameArr[(int)name_len] = '\0';
|
||||
fi.filename = new string(filenameArr);
|
||||
|
||||
if (name[0] == ':' && name[1] == ':')
|
||||
{
|
||||
// System file
|
||||
if (name_len == 40 && fi.filename.StartsWith(content_name))
|
||||
{
|
||||
chm.sec1.content = fi;
|
||||
}
|
||||
else if (name_len == 44 && fi.filename.StartsWith(control_name))
|
||||
{
|
||||
chm.sec1.control = fi;
|
||||
}
|
||||
else if (name_len == 41 && fi.filename.StartsWith(spaninfo_name))
|
||||
{
|
||||
chm.sec1.spaninfo = fi;
|
||||
}
|
||||
else if (name_len == 105 && fi.filename.StartsWith(rtable_name))
|
||||
{
|
||||
chm.sec1.rtable = fi;
|
||||
}
|
||||
fi.next = chm.sysfiles;
|
||||
chm.sysfiles = fi;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal file
|
||||
if (link != null) link.next = fi; else chm.files = fi;
|
||||
link = fi;
|
||||
}
|
||||
}
|
||||
|
||||
// This is reached either when num_entries runs out, or if
|
||||
// an ENCINT is badly encoded
|
||||
encint_err:
|
||||
if (num_entries >= 0)
|
||||
{
|
||||
Console.Error.WriteLine("bad encint before all entries could be read");
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
sys.free(chunk);
|
||||
return (errors > 0) ? MSPACK_ERR.MSPACK_ERR_DATAFORMAT : MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a file from a CHM helpfile.
|
||||
///
|
||||
/// This extracts a file from a CHM helpfile and writes it to the given
|
||||
/// filename. The filename of the file, mscabd_file::filename, is not
|
||||
/// used by extract(), but can be used by the caller as a guide for
|
||||
/// constructing an appropriate filename.
|
||||
///
|
||||
/// This method works both with files found in the mschmd_header::files
|
||||
/// and mschmd_header::sysfiles list and mschmd_file structures generated
|
||||
/// on the fly by fast_find().
|
||||
/// </summary>
|
||||
/// <param name="file">The file to be decompressed</param>
|
||||
/// <param name="filename">The filename of the file being written to</param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR extract(mschmd_file file, in string filename) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
///
|
||||
/// This is useful for open() and fast_open(), which do not return an
|
||||
/// error code directly.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="open(in string)"/>
|
||||
/// <see cref="extract(mschmd_file, in string)"/>
|
||||
public MSPACK_ERR last_error() => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Opens a CHM helpfile quickly.
|
||||
///
|
||||
/// If the file opened is a valid CHM helpfile, only essential headers
|
||||
/// will be read. A mschmd_header structure will be still be returned, as
|
||||
/// with open(), but the mschmd_header::files field will be null. No
|
||||
/// files details will be automatically read. The fast_find() method
|
||||
/// must be used to obtain file details.
|
||||
///
|
||||
/// In the case of an error occuring, null is returned and the error code
|
||||
/// is available from last_error().
|
||||
///
|
||||
/// The filename pointer should be considered "in use" until close() is
|
||||
/// called on the CHM helpfile.
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// The filename of the CHM helpfile. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>A pointer to a mschmd_header structure, or null on failure</returns>
|
||||
/// <see cref="open(in string)"/>
|
||||
/// <see cref="close(mschmd_header)"/>
|
||||
/// <see cref="fast_find(mschmd_header, in string, ref mschmd_file, int)"/>
|
||||
/// <see cref="extract(mschmd_file, in string)"/>
|
||||
public mschmd_header fast_open(in string filename)
|
||||
{
|
||||
return chmd_real_open(filename, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The real implementation of chmd_open() and chmd_fast_open(). It simply
|
||||
/// passes the "entire" parameter to chmd_read_headers(), which will then
|
||||
/// either read all headers, or a bare mininum.
|
||||
/// </summary>
|
||||
private mschmd_header chmd_real_open(in string filename, int entire)
|
||||
{
|
||||
mschmd_header chm = null;
|
||||
MSPACK_ERR error;
|
||||
|
||||
mspack_system sys = this.system;
|
||||
|
||||
mspack_file fh;
|
||||
if ((fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) != null)
|
||||
{
|
||||
chm = new mschmd_header();
|
||||
chm.filename = filename;
|
||||
error = chmd_read_headers(sys, fh, chm, entire);
|
||||
if (error != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
{
|
||||
// If the error is DATAFORMAT, and there are some results, return
|
||||
// partial results with a warning, rather than nothing
|
||||
if (error == MSPACK_ERR.MSPACK_ERR_DATAFORMAT && (chm.files != null || chm.sysfiles != null))
|
||||
{
|
||||
sys.message(fh, "WARNING; contents are corrupt");
|
||||
error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
close(chm);
|
||||
chm = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.error = error;
|
||||
sys.close(fh);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
}
|
||||
return chm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds file details quickly.
|
||||
///
|
||||
/// Instead of reading all CHM helpfile headers and building a list of
|
||||
/// files, fast_open() and fast_find() are intended for finding file
|
||||
/// details only when they are needed. The CHM file format includes an
|
||||
/// on-disk file index to allow this.
|
||||
///
|
||||
/// Given a case-sensitive filename, fast_find() will search the on-disk
|
||||
/// index for that file.
|
||||
///
|
||||
/// If the file was found, the caller-provided mschmd_file structure will
|
||||
/// be filled out like so:
|
||||
/// - section: the correct value for the found file
|
||||
/// - offset: the correct value for the found file
|
||||
/// - length: the correct value for the found file
|
||||
/// - all other structure elements: null or 0
|
||||
///
|
||||
/// If the file was not found, MSPACK_ERR_OK will still be returned as the
|
||||
/// result, but the caller-provided structure will be filled out like so:
|
||||
/// - section: null
|
||||
/// - offset: 0
|
||||
/// - length: 0
|
||||
/// - all other structure elements: null or 0
|
||||
///
|
||||
/// This method is intended to be used in conjunction with CHM helpfiles
|
||||
/// opened with fast_open(), but it also works with helpfiles opened
|
||||
/// using the regular open().
|
||||
/// </summary>
|
||||
/// <param name="chm">The CHM helpfile to search for the file</param>
|
||||
/// <param name="filename">The filename of the file to search for</param>
|
||||
/// <param name="f_ptr">A pointer to a caller-provded mschmd_file structure</param>
|
||||
/// <param name="f_size"><tt>sizeof(mschmd_file)</tt></param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
/// <see cref="open(in string)"/>
|
||||
/// <see cref="close(mschmd_header)"/>
|
||||
/// <see cref="fast_find(mschmd_header, in string, ref mschmd_file, int)"/>
|
||||
/// <see cref="extract(mschmd_file, in string)"/>
|
||||
public MSPACK_ERR fast_find(mschmd_header chm, in string filename, ref mschmd_file f_ptr, int f_size)
|
||||
{
|
||||
mspack_system sys;
|
||||
mspack_file fh;
|
||||
|
||||
// p and end are initialised to prevent MSVC warning about "potentially"
|
||||
// uninitialised usage. This is provably untrue, but MS won't fix:
|
||||
// https://developercommunity.visualstudio.com/content/problem/363489/c4701-false-positive-warning.html
|
||||
FixedArray<byte> chunk;
|
||||
byte* p = null, end = null;
|
||||
MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
int result = -1;
|
||||
uint n, sec;
|
||||
|
||||
if (chm == null || f_ptr == null)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_ARGS;
|
||||
}
|
||||
|
||||
sys = this.system;
|
||||
|
||||
// Clear the results structure
|
||||
f_ptr = new mschmd_file();
|
||||
|
||||
if ((fh = sys.open(chm.filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ)) == null)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
}
|
||||
|
||||
// Go through PMGI chunk hierarchy to reach PMGL chunk
|
||||
if (chm.index_root < chm.num_chunks)
|
||||
{
|
||||
n = chm.index_root;
|
||||
for (; ; )
|
||||
{
|
||||
if ((chunk = read_chunk(chm, fh, n)) == null)
|
||||
{
|
||||
sys.close(fh);
|
||||
return this.error;
|
||||
}
|
||||
|
||||
// Search PMGI/PMGL chunk. exit early if no entry found
|
||||
if ((result = search_chunk(chm, chunk, filename, &p, &end)) <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Found result. loop around for next chunk if this is PMGI
|
||||
if (chunk[3] == 0x4C) break;
|
||||
|
||||
n = read_encint(&p, end, &err);
|
||||
if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// PMGL chunks only, search from first_pmgl to last_pmgl
|
||||
for (n = chm.first_pmgl; n <= chm.last_pmgl; n = EndGetI32(chunk, pmgl_NextChunk))
|
||||
{
|
||||
if ((chunk = read_chunk(chm, fh, n)) == null)
|
||||
{
|
||||
err = this.error;
|
||||
break;
|
||||
}
|
||||
|
||||
// Search PMGL chunk. exit if file found
|
||||
if ((result = search_chunk(chm, chunk, filename, &p, &end)) > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop simple infinite loops: can't visit the same chunk twice
|
||||
if (n == EndGetI32(chunk, pmgl_NextChunk))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a file, read it
|
||||
if (result > 0)
|
||||
{
|
||||
sec = read_encint(&p, end, &err);
|
||||
f_ptr.section = (sec == 0) ? (mschmd_section)chm.sec0 : (mschmd_section)chm.sec1;
|
||||
f_ptr.offset = read_encint(&p, end, &err);
|
||||
f_ptr.length = read_encint(&p, end, &err);
|
||||
if (err != MSPACK_ERR.MSPACK_ERR_OK) goto encint_err;
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
err = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
sys.close(fh);
|
||||
return this.error = err;
|
||||
|
||||
encint_err:
|
||||
Console.Error.WriteLine("Bad encint in PGMI/PGML chunk");
|
||||
sys.close(fh);
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the given chunk into memory, storing it in a chunk cache
|
||||
/// so it doesn't need to be read from disk more than once
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private FixedArray<byte> read_chunk(mschmd_header chm, mspack_file fh, uint chunk_num)
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
FixedArray<byte> buf;
|
||||
|
||||
// Check arguments - most are already checked by chmd_fast_find
|
||||
if (chunk_num >= chm.num_chunks) return null;
|
||||
|
||||
// Ensure chunk cache is available
|
||||
if (chm.chunk_cache == null)
|
||||
{
|
||||
chm.chunk_cache = new FixedArray<byte>[chm.num_chunks];
|
||||
if (chm.chunk_cache == null)
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to answer out of chunk cache
|
||||
if (chm.chunk_cache[chunk_num] != null) return chm.chunk_cache[chunk_num];
|
||||
|
||||
// Need to read chunk - allocate memory for it
|
||||
buf = new FixedArray<byte>((int)chm.chunk_size);
|
||||
|
||||
// Seek to block and read it
|
||||
if (sys.seek(fh, chm.dir_offset + (chunk_num * chm.chunk_size), MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
sys.free(buf);
|
||||
return null;
|
||||
}
|
||||
if (sys.read(fh, buf, (int)chm.chunk_size) != (int)chm.chunk_size)
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
sys.free(buf);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check the signature. Is is PMGL or PMGI?
|
||||
if (!((buf[0] == 0x50) && (buf[1] == 0x4D) && (buf[2] == 0x47) && ((buf[3] == 0x4C) || (buf[3] == 0x49))))
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
sys.free(buf);
|
||||
return null;
|
||||
}
|
||||
|
||||
// All OK. Store chunk in cache and return it
|
||||
return chm.chunk_cache[chunk_num] = buf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches a PMGI/PMGL chunk for a given filename entry. Returns -1 on
|
||||
/// data format error, 0 if entry definitely not found, 1 if entry
|
||||
/// found. In the latter case, *result and *result_end are set pointing
|
||||
/// to that entry's data (either the "next chunk" ENCINT for a PMGI or
|
||||
/// the section, offset and length ENCINTs for a PMGL).
|
||||
///
|
||||
/// In the case of PMGL chunks, the entry has definitely been
|
||||
/// found. In the case of PMGI chunks, the entry which points to the
|
||||
/// chunk that may eventually contain that entry has been found.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int search_chunk(mschmd_header chm, in FixedArray<byte> chunk, in string filename, byte** result, byte** result_end)
|
||||
{
|
||||
byte* p;
|
||||
uint qr_size, num_entries, qr_entries, qr_density, name_len;
|
||||
uint L, R, M, entries_off, is_pmgl;
|
||||
int cmp;
|
||||
MSPACK_ERR err = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
uint fname_len = (uint)filename.Length;
|
||||
|
||||
// PMGL chunk or PMGI chunk? (note: read_chunk() has already
|
||||
// checked the rest of the characters in the chunk signature)
|
||||
if (chunk[3] == 0x4C)
|
||||
{
|
||||
is_pmgl = 1;
|
||||
entries_off = pmgl_Entries;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_pmgl = 0;
|
||||
entries_off = pmgi_Entries;
|
||||
}
|
||||
|
||||
// Step 1: binary search first filename of each QR entry
|
||||
// - target filename == entry
|
||||
// found file
|
||||
// - target filename < all entries
|
||||
// file not found
|
||||
// - target filename > all entries
|
||||
// proceed to step 2 using final entry
|
||||
// - target filename between two searched entries
|
||||
// proceed to step 2
|
||||
qr_size = EndGetI32(chunk, pmgl_QuickRefSize);
|
||||
int start = (int)(chm.chunk_size - 2);
|
||||
int end = (int)(chm.chunk_size - qr_size);
|
||||
num_entries = EndGetI16(chunk, (int)(chm.chunk_size - 2));
|
||||
qr_density = (uint)(1 + (1 << (int)chm.density));
|
||||
qr_entries = (num_entries + qr_density - 1) / qr_density;
|
||||
|
||||
if (num_entries == 0)
|
||||
{
|
||||
Console.Error.WriteLine("Chunk has no entries");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (qr_size > chm.chunk_size)
|
||||
{
|
||||
Console.Error.WriteLine("Quickref size > chunk size");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*result_end = &chunk[end];
|
||||
|
||||
if (((int)qr_entries * 2) > (start - end))
|
||||
{
|
||||
Console.Error.WriteLine("WARNING; more quickrefs than quickref space");
|
||||
qr_entries = 0; // But we can live with it
|
||||
}
|
||||
|
||||
if (qr_entries > 0)
|
||||
{
|
||||
L = 0;
|
||||
R = qr_entries - 1;
|
||||
do
|
||||
{
|
||||
// Pick new midpoint
|
||||
M = (L + R) >> 1;
|
||||
|
||||
// Compare filename with entry QR points to
|
||||
p = &chunk[entries_off + (M != 0 ? EndGetI16(chunk, start - (int)(M << 1)) : 0)];
|
||||
name_len = read_encint(&p, end, &err);
|
||||
if (err != MSPACK_ERR.MSPACK_ERR_OK || (name_len > (uint)(end - p))) goto encint_err;
|
||||
cmp = compare(filename, (char*)p, fname_len, name_len);
|
||||
|
||||
if (cmp == 0) break;
|
||||
else if (cmp < 0) { if (M) R = M - 1; else return 0; }
|
||||
else if (cmp > 0) L = M + 1;
|
||||
} while (L <= R);
|
||||
M = (L + R) >> 1;
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
/* exact match! */
|
||||
p += name_len;
|
||||
*result = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* otherwise, read the group of entries for QR entry M */
|
||||
p = &chunk[entries_off + (M ? EndGetI16(chunk, start - (M << 1)) : 0)];
|
||||
num_entries -= (M * qr_density);
|
||||
if (num_entries > qr_density) num_entries = qr_density;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = &chunk[entries_off];
|
||||
}
|
||||
|
||||
/* Step 2: linear search through the set of entries reached in step 1.
|
||||
* - filename == any entry
|
||||
* found entry
|
||||
* - filename < all entries (PMGI) or any entry (PMGL)
|
||||
* entry not found, stop now
|
||||
* - filename > all entries
|
||||
* entry not found (PMGL) / maybe found (PMGI)
|
||||
* -
|
||||
*/
|
||||
*result = null;
|
||||
while (num_entries-- > 0)
|
||||
{
|
||||
name_len = read_encint(&p, end, &err);
|
||||
if (err || (name_len > (uint)(end - p))) goto encint_err;
|
||||
cmp = compare(filename, (char*)p, fname_len, name_len);
|
||||
p += name_len;
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
/* entry found */
|
||||
*result = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
/* entry not found (PMGL) / maybe found (PMGI) */
|
||||
break;
|
||||
}
|
||||
|
||||
/* read and ignore the rest of this entry */
|
||||
if (is_pmgl)
|
||||
{
|
||||
while (p < end && (*p++ & 0x80)) ; /* skip section ENCINT */
|
||||
while (p < end && (*p++ & 0x80)) ; /* skip offset ENCINT */
|
||||
while (p < end && (*p++ & 0x80)) ; /* skip length ENCINT */
|
||||
}
|
||||
else
|
||||
{
|
||||
*result = p; /* store potential final result */
|
||||
while (p < end && (*p++ & 0x80)) ; /* skip chunk number ENCINT */
|
||||
}
|
||||
}
|
||||
|
||||
/* PMGL? not found. PMGI? maybe found */
|
||||
return (is_pmgl) ? 0 : (*result ? 1 : 0);
|
||||
|
||||
encint_err:
|
||||
Console.Error.WriteLine("bad encint while searching");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a file to be placed in a CHM helpfile.
|
||||
///
|
||||
/// A contiguous array of these structures should be passed to
|
||||
/// mschm_compressor::generate(). The array list is terminated with an
|
||||
/// entry whose mschmc_file::section field is set to #MSCHMC_ENDLIST, the
|
||||
/// other fields in this entry are ignored.
|
||||
/// </summary>
|
||||
public class mschmc_file
|
||||
{
|
||||
/// <summary>
|
||||
/// One of <see cref="MSCHMC"/> values.
|
||||
/// </summary>
|
||||
public MSCHMC section { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the source file that will be added to the CHM. This
|
||||
/// is passed directly to mspack_system::open().
|
||||
/// </summary>
|
||||
public string filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full path and filename of the file within the CHM helpfile, a
|
||||
/// UTF-1 encoded null-terminated string.
|
||||
/// </summary>
|
||||
public string chm_filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the file, in bytes. This will be adhered to strictly
|
||||
/// and a read error will be issued if this many bytes cannot be read
|
||||
/// from the real file at CHM generation time.
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public class mschmd_decompress_state
|
||||
{
|
||||
/// <summary>
|
||||
/// CHM file being decompressed
|
||||
/// </summary>
|
||||
public mschmd_header chm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed length of LZX stream
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed offset within stream
|
||||
/// </summary>
|
||||
public long offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset in input file
|
||||
/// </summary>
|
||||
public long inoffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LZX decompressor state
|
||||
/// </summary>
|
||||
public lzxd_stream state { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Special I/O code for decompressor
|
||||
/// </summary>
|
||||
public mspack_system sys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input file handle
|
||||
/// </summary>
|
||||
public mspack_file infh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output file handle
|
||||
/// </summary>
|
||||
public mspack_file outfh { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a file stored in a CHM helpfile.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class mschmd_file
|
||||
{
|
||||
/// <summary>
|
||||
/// A pointer to the next file in the list, or null if this is the final
|
||||
/// file.
|
||||
/// </summary>
|
||||
public mschmd_file next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the section that this file is located in. Indirectly,
|
||||
/// it also points to the CHM helpfile the file is located in.
|
||||
/// </summary>
|
||||
public mschmd_section section { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The offset within the section data that this file is located at.
|
||||
/// </summary>
|
||||
public long offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of this file, in bytes
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of this file -- a null terminated string in UTF-8.
|
||||
/// </summary>
|
||||
public string filename { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a CHM helpfile.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public unsafe class mschmd_header
|
||||
{
|
||||
/// <summary>
|
||||
/// The version of the CHM file format used in this file.
|
||||
/// </summary>
|
||||
public uint version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The "timestamp" of the CHM helpfile.
|
||||
///
|
||||
/// It is the lower 32 bits of a 64-bit value representing the number of
|
||||
/// centiseconds since 1601-01-01 00:00:00 UTC, plus 42. It is not useful
|
||||
/// as a timestamp, but it is useful as a semi-unique ID.
|
||||
/// </summary>
|
||||
public uint timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default Language and Country ID (LCID) of the user who ran the
|
||||
/// HTMLHelp Compiler. This is not the language of the CHM file itself.
|
||||
/// </summary>
|
||||
public uint language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the CHM helpfile. This is given by the library user
|
||||
/// and may be in any format.
|
||||
/// </summary>
|
||||
public string filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the CHM helpfile, in bytes.
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all non-system files in the CHM helpfile.
|
||||
/// </summary>
|
||||
public mschmd_file files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all system files in the CHM helpfile.
|
||||
///
|
||||
/// System files are files which begin with "::". They are meta-files
|
||||
/// generated by the CHM creation process.
|
||||
/// </summary>
|
||||
public mschmd_file sysfiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The section 0 (uncompressed) data in this CHM helpfile.
|
||||
/// </summary>
|
||||
public mschmd_sec_uncompressed sec0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The section 1 (MSCompressed) data in this CHM helpfile.
|
||||
/// </summary>
|
||||
public mschmd_sec_mscompressed sec1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file offset of the first PMGL/PMGI directory chunk.
|
||||
/// </summary>
|
||||
public long dir_offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of PMGL/PMGI directory chunks in this CHM helpfile.
|
||||
/// </summary>
|
||||
public uint num_chunks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of each PMGL/PMGI chunk, in bytes.
|
||||
/// </summary>
|
||||
public uint chunk_size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The "density" of the quick-reference section in PMGL/PMGI chunks.
|
||||
/// </summary>
|
||||
public uint density { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The depth of the index tree.
|
||||
///
|
||||
/// - if 1, there are no PMGI chunks, only PMGL chunks.
|
||||
/// - if 2, there is 1 PMGI chunk. All chunk indices point to PMGL chunks.
|
||||
/// - if 3, the root PMGI chunk points to secondary PMGI chunks, which in
|
||||
/// turn point to PMGL chunks.
|
||||
/// - and so on...
|
||||
/// </summary>
|
||||
public uint depth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of the root PMGI chunk.
|
||||
///
|
||||
/// If there is no index in the CHM helpfile, this will be 0xFFFFFFFF.
|
||||
/// </summary>
|
||||
public uint index_root { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of the first PMGL chunk. Usually zero.
|
||||
/// Available only in CHM decoder version 2 and above.
|
||||
/// </summary>
|
||||
public uint first_pmgl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of the last PMGL chunk. Usually num_chunks-1.
|
||||
/// Available only in CHM decoder version 2 and above.
|
||||
/// </summary>
|
||||
public uint last_pmgl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A cache of loaded chunks, filled in by mschm_decoder::fast_find().
|
||||
/// Available only in CHM decoder version 2 and above.
|
||||
/// </summary>
|
||||
public FixedArray<byte>[] chunk_cache { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents the LZX compressed section of a CHM helpfile.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class mschmd_sec_mscompressed : mschmd_section
|
||||
{
|
||||
/// <summary>
|
||||
/// A pointer to the meta-file which represents all LZX compressed data.
|
||||
/// </summary>
|
||||
public mschmd_file content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the file which contains the LZX control data.
|
||||
/// </summary>
|
||||
public mschmd_file control { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the file which contains the LZX reset table.
|
||||
/// </summary>
|
||||
public mschmd_file rtable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the file which contains the LZX span information.
|
||||
/// Available only in CHM decoder version 2 and above.
|
||||
/// </summary>
|
||||
public mschmd_file spaninfo { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents the uncompressed section of a CHM helpfile.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class mschmd_sec_uncompressed : mschmd_section
|
||||
{
|
||||
/// <summary>
|
||||
/// The file offset of where this section begins in the CHM helpfile.
|
||||
/// </summary>
|
||||
public long offset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents a section of a CHM helpfile.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
///
|
||||
/// Not used directly, but used as a generic base type for
|
||||
/// mschmd_sec_uncompressed and mschmd_sec_mscompressed.
|
||||
/// </summary>
|
||||
public class mschmd_section
|
||||
{
|
||||
/// <summary>
|
||||
/// A pointer to the CHM helpfile that contains this section.
|
||||
/// </summary>
|
||||
public mschmd_header chm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The section ID. Either 0 for the uncompressed section
|
||||
/// mschmd_sec_uncompressed, or 1 for the LZX compressed section
|
||||
/// mschmd_sec_mscompressed. No other section IDs are known.
|
||||
/// </summary>
|
||||
public uint id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset from start of cabinet to the reserved header data (if present).
|
||||
/// </summary>
|
||||
public const int MSCAB_HDR_RESV_OFFSET = 0x28;
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
#region mspack.h
|
||||
|
||||
/// <summary>
|
||||
/// mspack_system::open() mode
|
||||
/// </summary>
|
||||
public enum MSPACK_SYS_OPEN : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Open existing file for reading
|
||||
/// </summary>
|
||||
MSPACK_SYS_OPEN_READ = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Open new file for writing
|
||||
/// </summary>
|
||||
MSPACK_SYS_OPEN_WRITE = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Open existing file for writing
|
||||
/// </summary>
|
||||
MSPACK_SYS_OPEN_UPDATE = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Open existing file for writing
|
||||
/// </summary>
|
||||
MSPACK_SYS_OPEN_APPEND = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mspack_system::seek() mode
|
||||
/// </summary>
|
||||
public enum MSPACK_SYS_SEEK : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Seek relative to start of file
|
||||
/// </summary>
|
||||
MSPACK_SYS_SEEK_START = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Seek relative to current offset
|
||||
/// </summary>
|
||||
MSPACK_SYS_SEEK_CUR = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Seek relative to end of file
|
||||
/// </summary>
|
||||
MSPACK_SYS_SEEK_END = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error code
|
||||
/// </summary>
|
||||
public enum MSPACK_ERR : int
|
||||
{
|
||||
MSPACK_ERR_OK = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Bad arguments to method
|
||||
/// </summary>
|
||||
MSPACK_ERR_ARGS = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Error opening file
|
||||
/// </summary>
|
||||
MSPACK_ERR_OPEN = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Error reading file
|
||||
/// </summary>
|
||||
MSPACK_ERR_READ = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Error writing file
|
||||
/// </summary>
|
||||
MSPACK_ERR_WRITE = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Seek error
|
||||
/// </summary>
|
||||
MSPACK_ERR_SEEK = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Out of memory
|
||||
/// </summary>
|
||||
MSPACK_ERR_NOMEMORY = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Bad "magic id" in file
|
||||
/// </summary>
|
||||
MSPACK_ERR_SIGNATURE = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Bad or corrupt file format
|
||||
/// </summary>
|
||||
MSPACK_ERR_DATAFORMAT = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Bad checksum or CRC
|
||||
/// </summary>
|
||||
MSPACK_ERR_CHECKSUM = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Error during compression
|
||||
/// </summary>
|
||||
MSPACK_ERR_CRUNCH = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Error during decompression
|
||||
/// </summary>
|
||||
MSPACK_ERR_DECRUNCH = 11,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet header flag
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MSCAB_HDR : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Cabinet has a predecessor
|
||||
/// </summary>
|
||||
MSCAB_HDR_PREVCAB = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet has a successor
|
||||
/// </summary>
|
||||
MSCAB_HDR_NEXTCAB = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet has reserved header space
|
||||
/// </summary>
|
||||
MSCAB_HDR_RESV = 0x04,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression mode
|
||||
/// </summary>
|
||||
public enum MSCAB_COMP : int
|
||||
{
|
||||
/// <summary>
|
||||
/// No compression
|
||||
/// </summary>
|
||||
MSCAB_COMP_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// MSZIP (deflate) compression
|
||||
/// </summary>
|
||||
MSCAB_COMP_MSZIP = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Quantum compression
|
||||
/// </summary>
|
||||
MSCAB_COMP_QUANTUM = 2,
|
||||
|
||||
/// <summary>
|
||||
/// LZX compression
|
||||
/// </summary>
|
||||
MSCAB_COMP_LZX = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mscabd_file::attribs attribute
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MSCAB_ATTRIB : int
|
||||
{
|
||||
/// <summary>
|
||||
/// File is read-only
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_RDONLY = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// File is hidden
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_HIDDEN = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// File is an operating system file
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_SYSTEM = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// File is "archived"
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_ARCH = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// File is an executable program
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_EXEC = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Filename is UTF8, not ISO-8859-1
|
||||
/// </summary>
|
||||
MSCAB_ATTRIB_UTF_NAME = 0x80,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mschmc_file::section value
|
||||
/// </summary>
|
||||
public enum MSCHMC : int
|
||||
{
|
||||
/// <summary>
|
||||
/// End of CHM file list
|
||||
/// </summary>
|
||||
MSCHMC_ENDLIST = 0,
|
||||
|
||||
/// <summary>
|
||||
/// This file is in the Uncompressed section
|
||||
/// </summary>
|
||||
MSCHMC_UNCOMP = 1,
|
||||
|
||||
/// <summary>
|
||||
/// This file is in the MSCompressed section
|
||||
/// </summary>
|
||||
MSCHMC_MSCOMP = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// msszddd_header::format value
|
||||
/// </summary>
|
||||
public enum MSSZDD_FMT : int
|
||||
{
|
||||
/// <summary>
|
||||
/// A regular SZDD file
|
||||
/// </summary>
|
||||
MSSZDD_FMT_NORMAL = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A special QBasic SZDD file
|
||||
/// </summary>
|
||||
MSSZDD_FMT_QBASIC = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WAJ compression type
|
||||
/// </summary>
|
||||
public enum MSKWAJ_COMP : int
|
||||
{
|
||||
/// <summary>
|
||||
/// No compression
|
||||
/// </summary>
|
||||
MSKWAJ_COMP_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// No compression, 0xFF XOR "encryption"
|
||||
/// </summary>
|
||||
MSKWAJ_COMP_XOR = 1,
|
||||
|
||||
/// <summary>
|
||||
/// LZSS (same method as SZDD)
|
||||
/// </summary>
|
||||
MSKWAJ_COMP_SZDD = 2,
|
||||
|
||||
/// <summary>
|
||||
/// LZ+Huffman compression
|
||||
/// </summary>
|
||||
MSKWAJ_COMP_LZH = 3,
|
||||
|
||||
/// <summary>
|
||||
/// MSZIP
|
||||
/// </summary>
|
||||
MSKWAJ_COMP_MSZIP = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KWAJ optional header flag
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MSKWAJ_HDR : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompressed file length is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASLENGTH = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown 2-byte structure is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASUNKNOWN1 = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown multi-sized structure is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASUNKNOWN2 = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// File name (no extension) is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASFILENAME = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// File extension is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASFILEEXT = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Extra text is included
|
||||
/// </summary>
|
||||
MSKWAJ_HDR_HASEXTRATEXT = 0x20,
|
||||
}
|
||||
|
||||
#region Parameters
|
||||
|
||||
/// <summary>
|
||||
/// mscab_decompressor::set_param() parameter
|
||||
/// </summary>
|
||||
public enum MSCABD_PARAM : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Search buffer size
|
||||
/// </summary>
|
||||
MSCABD_PARAM_SEARCHBUF = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Repair MS-ZIP streams?
|
||||
/// </summary>
|
||||
MSCABD_PARAM_FIXMSZIP = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Size of decompression buffer
|
||||
/// </summary>
|
||||
MSCABD_PARAM_DECOMPBUF = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Salvage data from bad cabinets?
|
||||
/// If enabled, open() will skip file with bad folder indices or filenames
|
||||
/// rather than reject the whole cabinet, and extract() will limit rather than
|
||||
/// reject files with invalid offsets and lengths, and bad data block checksums
|
||||
/// will be ignored. Available only in CAB decoder version 2 and above.
|
||||
/// </summary>
|
||||
MSCABD_PARAM_SALVAGE = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mschm_compressor::set_param() parameter
|
||||
/// </summary>
|
||||
public enum MSCHMC_PARAM : int
|
||||
{
|
||||
/// <summary>
|
||||
/// "timestamp" header
|
||||
/// </summary>
|
||||
MSCHMC_PARAM_TIMESTAMP = 0,
|
||||
|
||||
/// <summary>
|
||||
/// "language" header
|
||||
/// </summary>
|
||||
MSCHMC_PARAM_LANGUAGE = 1,
|
||||
|
||||
/// <summary>
|
||||
/// LZX window size
|
||||
/// </summary>
|
||||
MSCHMC_PARAM_LZXWINDOW = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Intra-chunk quickref density
|
||||
/// </summary>
|
||||
MSCHMC_PARAM_DENSITY = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Whether to create indices
|
||||
/// </summary>
|
||||
MSCHMC_PARAM_INDEX = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// msszdd_compressor::set_param() parameter
|
||||
/// </summary>
|
||||
public enum MSSZDDC_PARAM : int
|
||||
{
|
||||
/// <summary>
|
||||
/// The missing character
|
||||
/// </summary>
|
||||
MSSZDDC_PARAM_MISSINGCHAR = 0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mskwaj_compressor::set_param() parameter
|
||||
/// </summary>
|
||||
public enum MSKWAJC_PARAM : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Compression type
|
||||
/// </summary>
|
||||
MSKWAJC_PARAM_COMP_TYPE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Include the length of the uncompressed file in the header?
|
||||
/// </summary>
|
||||
MSKWAJC_PARAM_INCLUDE_LENGTH = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// msoab_decompressor::set_param() parameter
|
||||
/// </summary>
|
||||
public enum MSOABD_PARAM : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of decompression buffer
|
||||
/// </summary>
|
||||
MSOABD_PARAM_DECOMPBUF = 0,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region lzss.h
|
||||
|
||||
public enum LZSS_MODE : int
|
||||
{
|
||||
LZSS_MODE_EXPAND = 0,
|
||||
LZSS_MODE_MSHELP = 1,
|
||||
LZSS_MODE_QBASIC = 2,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class FixedArray<T> where T : struct
|
||||
{
|
||||
/// <summary>
|
||||
/// Direct access to the internal pointer
|
||||
/// </summary>
|
||||
public IntPtr Pointer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the T object
|
||||
/// </summary>
|
||||
private int sizeofT { get { return Marshal.SizeOf(typeof(T)); } }
|
||||
|
||||
/// <summary>
|
||||
/// Length of the fixed array
|
||||
/// </summary>
|
||||
private int _length;
|
||||
|
||||
public T this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i < 0 || i >= _length)
|
||||
return default;
|
||||
|
||||
return (T)Marshal.PtrToStructure(Pointer + i * sizeofT, typeof(T));
|
||||
}
|
||||
set
|
||||
{
|
||||
if (i < 0 || i >= _length)
|
||||
return;
|
||||
|
||||
Marshal.StructureToPtr(value, Pointer + i * sizeofT, false);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedArray(int length)
|
||||
{
|
||||
Pointer = Marshal.AllocHGlobal(sizeofT * length);
|
||||
_length = 0;
|
||||
}
|
||||
|
||||
~FixedArray()
|
||||
{
|
||||
Marshal.FreeHGlobal(Pointer);
|
||||
}
|
||||
|
||||
public static implicit operator T*(FixedArray<T> arr) => (T*)arr.Pointer;
|
||||
|
||||
public static implicit operator T[](FixedArray<T> arr) => arr.ToArray();
|
||||
|
||||
/// <inheritdoc cref="System.Linq.Enumerable.SequenceEqual{TSource}(System.Collections.Generic.IEnumerable{TSource}, System.Collections.Generic.IEnumerable{TSource})"/>
|
||||
public bool SequenceEqual(T[] arr)
|
||||
{
|
||||
if (arr.Length < _length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < _length; i++)
|
||||
{
|
||||
if (!this[i].Equals(arr[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the unmanaged data to an array
|
||||
/// </summary>
|
||||
/// <returns>Array created from the pointer data</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] arr = new T[_length];
|
||||
for (int i = 0; i < _length; i++)
|
||||
{
|
||||
arr[i] = this[i];
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.HLP
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public class Compressor : BaseCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new HLP compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.HLP
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public class Decompressor : BaseDecompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new HLP decompressor
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.KWAJ
|
||||
{
|
||||
/// <summary>
|
||||
/// A compressor for the KWAJ file format.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public unsafe class Compressor : BaseCompressor
|
||||
{
|
||||
public int[] param { get; private set; } = new int[2];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new KWAJ compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an input file and creates a compressed output file in the
|
||||
/// KWAJ compressed file format. The KWAJ compression format is quick
|
||||
/// but gives poor compression. It is possible for the compressed output
|
||||
/// file to be larger than the input file.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The name of the file to compressed. This is passed
|
||||
/// passed directly to mspack_system::open()
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The name of the file to write compressed data to.
|
||||
/// This is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// The length of the uncompressed file, or -1 to indicate
|
||||
/// that this should be determined automatically by using
|
||||
/// mspack_system::seek() on the input file.
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
/// <see cref="set_param(int, int)" />
|
||||
public MSPACK_ERR compress(in string input, in string output, long length) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Sets an KWAJ compression engine parameter.
|
||||
///
|
||||
/// The following parameters are defined:
|
||||
///
|
||||
/// - #MSKWAJC_PARAM_COMP_TYPE: the compression method to use. Must
|
||||
/// be one of #MSKWAJC_COMP_NONE, #MSKWAJC_COMP_XOR, #MSKWAJ_COMP_SZDD
|
||||
/// or #MSKWAJ_COMP_LZH. The default is #MSKWAJ_COMP_LZH.
|
||||
///
|
||||
/// - #MSKWAJC_PARAM_INCLUDE_LENGTH: a boolean; should the compressed
|
||||
/// output file should include the uncompressed length of the input
|
||||
/// file in the header? This adds 4 bytes to the size of the output
|
||||
/// file. A value of zero says "no", non-zero says "yes". The default
|
||||
/// is "no".
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter to set</param>
|
||||
/// <param name="value">The value to set the parameter to</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
|
||||
/// is a problem with either parameter or value.
|
||||
/// </returns>
|
||||
/// <see cref="generate()"/>
|
||||
public MSPACK_ERR set_param(MSKWAJC_PARAM param, int value) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the original filename of the file before compression,
|
||||
/// which will be stored in the header of the output file.
|
||||
///
|
||||
/// The filename should be a null-terminated string, it must be an
|
||||
/// MS-DOS "8.3" type filename (up to 8 bytes for the filename, then
|
||||
/// optionally a "." and up to 3 bytes for a filename extension).
|
||||
///
|
||||
/// If null is passed as the filename, no filename is included in the
|
||||
/// header. This is the default.
|
||||
/// </summary>
|
||||
/// <param name="filename">The original filename to use</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if the
|
||||
/// filename is too long
|
||||
/// </returns>
|
||||
public MSPACK_ERR set_filename(in string filename) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Sets arbitrary data that will be stored in the header of the
|
||||
/// output file, uncompressed. It can be up to roughly 64 kilobytes,
|
||||
/// as the overall size of the header must not exceed 65535 bytes.
|
||||
/// The data can contain null bytes if desired.
|
||||
///
|
||||
/// If null is passed as the data pointer, or zero is passed as the
|
||||
/// length, no extra data is included in the header. This is the
|
||||
/// default.
|
||||
/// </summary>
|
||||
/// <param name="data">A pointer to the data to be stored in the header</param>
|
||||
/// <param name="bytes">the length of the data in bytes</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS extra data
|
||||
/// is too long
|
||||
/// </returns>
|
||||
public MSPACK_ERR set_extra_data(void* data, int bytes) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="compress(in string, in string, long)"/>
|
||||
public MSPACK_ERR last_error() => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.KWAJ
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const byte kwajh_Signature1 = 0x00;
|
||||
public const byte kwajh_Signature2 = 0x04;
|
||||
public const byte kwajh_CompMethod = 0x08;
|
||||
public const byte kwajh_DataOffset = 0x0a;
|
||||
public const byte kwajh_Flags = 0x0c;
|
||||
public const byte kwajh_SIZEOF = 0x0e;
|
||||
|
||||
/// <summary>
|
||||
/// Input buffer size during decompression - not worth parameterising IMHO
|
||||
/// </summary>
|
||||
public const int KWAJ_INPUT_SIZE = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// Huffman codes that are 9 bits or less are decoded immediately
|
||||
/// </summary>
|
||||
public const int KWAJ_TABLEBITS = 9;
|
||||
|
||||
// Number of codes in each huffman table
|
||||
public const int KWAJ_MATCHLEN1_SYMS = 16;
|
||||
public const int KWAJ_MATCHLEN2_SYMS = 16;
|
||||
public const int KWAJ_LITLEN_SYMS = 32;
|
||||
public const int KWAJ_OFFSET_SYMS = 64;
|
||||
public const int KWAJ_LITERAL_SYMS = 256;
|
||||
|
||||
// Define decoding table sizes
|
||||
public const int KWAJ_TABLESIZE = 1 << KWAJ_TABLEBITS;
|
||||
public const int KWAJ_MATCHLEN1_TBLSIZE = KWAJ_TABLESIZE + (KWAJ_MATCHLEN1_SYMS * 2);
|
||||
public const int KWAJ_MATCHLEN2_TBLSIZE = KWAJ_TABLESIZE + (KWAJ_MATCHLEN2_SYMS * 2);
|
||||
public const int KWAJ_LITLEN_TBLSIZE = KWAJ_TABLESIZE + (KWAJ_LITLEN_SYMS * 2);
|
||||
public const int KWAJ_OFFSET_TBLSIZE = KWAJ_TABLESIZE + (KWAJ_OFFSET_SYMS * 2);
|
||||
public const int KWAJ_LITERAL_TBLSIZE = KWAJ_TABLESIZE + (KWAJ_LITERAL_SYMS * 2);
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
using System;
|
||||
using static SabreTools.Compression.libmspack.KWAJ.Constants;
|
||||
using static SabreTools.Compression.libmspack.macros;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.KWAJ
|
||||
{
|
||||
/// <summary>
|
||||
/// A decompressor for KWAJ compressed files.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public unsafe class Decompressor : BaseDecompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new KWAJ decompressor.
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys an existing KWAJ decompressor
|
||||
/// </summary>
|
||||
~Decompressor()
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
//sys.free(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a KWAJ file and reads the header.
|
||||
///
|
||||
/// If the file opened is a valid KWAJ file, all headers will be read and
|
||||
/// a mskwajd_header structure will be returned.
|
||||
///
|
||||
/// In the case of an error occuring, null is returned and the error code
|
||||
/// is available from last_error().
|
||||
///
|
||||
/// The filename pointer should be considered "in use" until close() is
|
||||
/// called on the KWAJ file.
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// The filename of the KWAJ compressed file. This is
|
||||
/// passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>A pointer to a mskwajd_header structure, or null on failure</returns>
|
||||
/// <see cref="Close(mskwajd_header)"/>
|
||||
public mskwajd_header Open(in string filename)
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
|
||||
mspack_file fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
|
||||
if (fh == null)
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
return null;
|
||||
}
|
||||
|
||||
mskwajd_header hdr = new mskwajd_header();
|
||||
hdr.fh = fh;
|
||||
|
||||
MSPACK_ERR err;
|
||||
if ((err = ReadHeaders(sys, fh, hdr)) != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
{
|
||||
Close(hdr);
|
||||
this.error = err;
|
||||
return null;
|
||||
}
|
||||
|
||||
return hdr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a previously opened KWAJ file.
|
||||
///
|
||||
/// This closes a KWAJ file and frees the mskwajd_header associated
|
||||
/// with it. The KWAJ header pointer is now invalid and cannot be
|
||||
/// used again.
|
||||
/// </summary>
|
||||
/// <param name="kwaj">The KWAJ file to close</param>
|
||||
/// <see cref="Open(in string)"/>
|
||||
public void Close(mskwajd_header kwaj)
|
||||
{
|
||||
if (this.system == null)
|
||||
return;
|
||||
|
||||
// Close the file handle associated
|
||||
this.system.close(kwaj.fh);
|
||||
|
||||
// Free the memory associated
|
||||
//this.system.free(hdr.filename);
|
||||
//this.system.free(hdr.extra);
|
||||
//this.system.free(hdr);
|
||||
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the headers of a KWAJ format file
|
||||
/// </summary>
|
||||
private MSPACK_ERR ReadHeaders(mspack_system sys, mspack_file fh, mskwajd_header hdr)
|
||||
{
|
||||
FixedArray<byte> buf = new FixedArray<byte>(16);
|
||||
int i;
|
||||
|
||||
// Read in the header
|
||||
if (sys.read(fh, buf, kwajh_SIZEOF) != kwajh_SIZEOF)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// Check for "KWAJ" signature
|
||||
if ((BitConverter.ToUInt32(buf, kwajh_Signature1) != 0x4A41574B) ||
|
||||
(BitConverter.ToUInt32(buf, kwajh_Signature2) != 0xD127F088))
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
}
|
||||
|
||||
// Basic header fields
|
||||
hdr.comp_type = (MSKWAJ_COMP)BitConverter.ToUInt16(buf, kwajh_CompMethod);
|
||||
hdr.data_offset = BitConverter.ToUInt16(buf, kwajh_DataOffset);
|
||||
hdr.headers = (MSKWAJ_HDR)BitConverter.ToUInt16(buf, kwajh_Flags);
|
||||
hdr.length = 0;
|
||||
hdr.filename = null;
|
||||
hdr.extra = null;
|
||||
hdr.extra_length = 0;
|
||||
|
||||
// Optional headers
|
||||
|
||||
// 4 bytes: length of unpacked file
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASLENGTH))
|
||||
{
|
||||
if (sys.read(fh, buf, 4) != 4)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
hdr.length = BitConverter.ToUInt32(buf, 0);
|
||||
}
|
||||
|
||||
// 2 bytes: unknown purpose
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASUNKNOWN1))
|
||||
{
|
||||
if (sys.read(fh, buf, 2) != 2)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
|
||||
// 2 bytes: length of section, then [length] bytes: unknown purpose
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASUNKNOWN2))
|
||||
{
|
||||
if (sys.read(fh, buf, 2) != 2)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
i = BitConverter.ToUInt16(buf, 0);
|
||||
if (sys.seek(fh, i, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0)
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Filename and extension
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASFILENAME | MSKWAJ_HDR.MSKWAJ_HDR_HASFILEEXT))
|
||||
{
|
||||
int len;
|
||||
|
||||
// Allocate memory for maximum length filename
|
||||
char* fn = (char*)sys.alloc(13);
|
||||
if ((hdr.extra = fn) == null)
|
||||
return MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
|
||||
// Copy filename if present
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASFILENAME))
|
||||
{
|
||||
// Read and copy up to 9 bytes of a null terminated string
|
||||
if ((len = sys.read(fh, buf, 9)) < 2)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if ((*fn++ = (char)buf[i]) == '\0')
|
||||
break;
|
||||
|
||||
// If string was 9 bytes with no null terminator, reject it
|
||||
if (i == 9 && buf[8] != '\0')
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
|
||||
// Seek to byte after string ended in file
|
||||
if (sys.seek(fh, i + 1 - len, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0)
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
|
||||
fn--; // Remove the null terminator
|
||||
}
|
||||
|
||||
// Copy extension if present
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASFILEEXT))
|
||||
{
|
||||
*fn++ = '.';
|
||||
|
||||
// Read and copy up to 4 bytes of a null terminated string
|
||||
if ((len = sys.read(fh, buf, 4)) < 2)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if ((*fn++ = (char)buf[i]) == '\0')
|
||||
break;
|
||||
|
||||
// If string was 4 bytes with no null terminator, reject it
|
||||
if (i == 4 && buf[3] != '\0')
|
||||
return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
|
||||
// Seek to byte after string ended in file
|
||||
if (sys.seek(fh, i + 1 - len, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR) != 0)
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
|
||||
fn--; // Remove the null terminator
|
||||
}
|
||||
*fn = '\0';
|
||||
}
|
||||
|
||||
// 2 bytes: extra text length then [length] bytes of extra text data
|
||||
if (hdr.headers.HasFlag(MSKWAJ_HDR.MSKWAJ_HDR_HASEXTRATEXT))
|
||||
{
|
||||
if (sys.read(fh, buf, 2) != 2)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
i = EndGetI16(buf, 0);
|
||||
hdr.extra = (char*)sys.alloc(i + 1);
|
||||
if (hdr.extra == null)
|
||||
return MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
|
||||
if (sys.read(fh, hdr.extra, i) != i)
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
hdr.extra[i] = '\0';
|
||||
hdr.extra_length = (ushort)i;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the compressed data from a KWAJ file.
|
||||
///
|
||||
/// This decompresses the compressed KWAJ data stream and writes it to
|
||||
/// an output file.
|
||||
/// </summary>
|
||||
/// <param name="kwaj">The KWAJ file to extract data from</param>
|
||||
/// <param name="filename">
|
||||
/// The filename to write the decompressed data to. This
|
||||
/// is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR Extract(mskwajd_header kwaj, in string filename)
|
||||
{
|
||||
if (kwaj == null)
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_ARGS;
|
||||
|
||||
mspack_system sys = this.system;
|
||||
mspack_file fh = kwaj.fh;
|
||||
|
||||
// Seek to the compressed data
|
||||
if (sys.seek(fh, kwaj.data_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Open file for output
|
||||
mspack_file outfh;
|
||||
if ((outfh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE)) == null)
|
||||
{
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
}
|
||||
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
// Decompress based on format
|
||||
if (kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_NONE || kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_XOR)
|
||||
{
|
||||
// NONE is a straight copy. XOR is a copy xored with 0xFF
|
||||
byte* buf = (byte*)sys.alloc(KWAJ_INPUT_SIZE);
|
||||
if (buf != null)
|
||||
{
|
||||
int read, i;
|
||||
while ((read = sys.read(fh, buf, KWAJ_INPUT_SIZE)) > 0)
|
||||
{
|
||||
if (kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_XOR)
|
||||
{
|
||||
for (i = 0; i < read; i++)
|
||||
buf[i] ^= 0xFF;
|
||||
}
|
||||
|
||||
if (sys.write(outfh, buf, read) != read)
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (read < 0)
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
sys.free(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
}
|
||||
}
|
||||
else if (kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_SZDD)
|
||||
{
|
||||
this.error = lzss_decompress(sys, fh, outfh, KWAJ_INPUT_SIZE, LZSS_MODE.LZSS_MODE_QBASIC);
|
||||
}
|
||||
else if (kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_LZH)
|
||||
{
|
||||
kwajd_stream lzh = lzh_init(sys, fh, outfh);
|
||||
this.error = (lzh != null) ? lzh_decompress(lzh) : MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
lzh_free(lzh);
|
||||
}
|
||||
else if (kwaj.comp_type == MSKWAJ_COMP.MSKWAJ_COMP_MSZIP)
|
||||
{
|
||||
mszipd_stream zip = mszipd_init(sys, fh, outfh, KWAJ_INPUT_SIZE, 0);
|
||||
this.error = (zip != null) ? mszipd_decompress_kwaj(zip) : MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
mszipd_free(zip);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Close output file
|
||||
sys.close(outfh);
|
||||
|
||||
return this.error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses an KWAJ file to an output file in one step.
|
||||
///
|
||||
/// This opens an KWAJ file as input, reads the header, then decompresses
|
||||
/// the compressed data immediately to an output file, finally closing
|
||||
/// both the input and output file. It is more convenient to use than
|
||||
/// open() then extract() then close(), if you do not need to know the
|
||||
/// KWAJ output size or output filename.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input KWAJ file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename to write the decompressed data to. This
|
||||
/// is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR Decompress(in string input, in string output)
|
||||
{
|
||||
mskwajd_header hdr;
|
||||
if ((hdr = Open(input)) == null)
|
||||
return this.error;
|
||||
|
||||
MSPACK_ERR error = Extract(hdr, output);
|
||||
Close(hdr);
|
||||
return this.error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
///
|
||||
/// This is useful for open() which does not return an
|
||||
/// error code directly.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="Open(in string)"/>
|
||||
/// <see cref="search()"/>
|
||||
public MSPACK_ERR LastError()
|
||||
{
|
||||
return this.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.KWAJ.Constants;
|
||||
using static SabreTools.Compression.libmspack.lzss;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class kwajd_stream : readbits
|
||||
{
|
||||
#region Huffman code lengths
|
||||
|
||||
public byte[] MATCHLEN1_len { get; set; } = new byte[KWAJ_MATCHLEN1_SYMS];
|
||||
|
||||
public byte[] MATCHLEN2_len { get; set; } = new byte[KWAJ_MATCHLEN2_SYMS];
|
||||
|
||||
public byte[] LITLEN_len { get; set; } = new byte[KWAJ_LITLEN_SYMS];
|
||||
|
||||
public byte[] OFFSET_len { get; set; } = new byte[KWAJ_OFFSET_SYMS];
|
||||
|
||||
public byte[] LITERAL_len { get; set; } = new byte[KWAJ_LITERAL_SYMS];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Huffman decoding tables
|
||||
|
||||
public ushort[] MATCHLEN1_table { get; set; } = new ushort[KWAJ_MATCHLEN1_TBLSIZE];
|
||||
|
||||
public ushort[] MATCHLEN2_table { get; set; } = new ushort[KWAJ_MATCHLEN2_TBLSIZE];
|
||||
|
||||
public ushort[] LITLEN_table { get; set; } = new ushort[KWAJ_LITLEN_TBLSIZE];
|
||||
|
||||
public ushort[] OFFSET_table { get; set; } = new ushort[KWAJ_OFFSET_TBLSIZE];
|
||||
|
||||
public ushort[] LITERAL_table { get; set; } = new ushort[KWAJ_LITERAL_TBLSIZE];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Input buffer
|
||||
|
||||
public new byte[] inbuf { get; set; } = new byte[KWAJ_INPUT_SIZE];
|
||||
|
||||
#endregion
|
||||
|
||||
#region History window
|
||||
|
||||
public byte[] window { get; set; } = new byte[LZSS_WINDOW_SIZE];
|
||||
|
||||
#endregion
|
||||
|
||||
public override void READ_BYTES()
|
||||
{
|
||||
if (i_ptr >= i_end)
|
||||
{
|
||||
if ((err = lzh_read_input(lzh)))
|
||||
return err;
|
||||
i_ptr = lzh.i_ptr;
|
||||
i_end = lzh.i_end;
|
||||
}
|
||||
INJECT_BITS_MSB(*i_ptr++, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents an KWAJ compressed file.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public unsafe class mskwajd_header
|
||||
{
|
||||
/// <summary>
|
||||
/// The compression type
|
||||
/// </summary>
|
||||
public MSKWAJ_COMP comp_type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The offset in the file where the compressed data stream begins
|
||||
/// </summary>
|
||||
public long data_offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating which optional headers were included.
|
||||
/// </summary>
|
||||
public MSKWAJ_HDR headers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of uncompressed data in the file, or 0 if not present.
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output filename, or null if not present
|
||||
/// </summary>
|
||||
public char* filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extra uncompressed data (usually text) in the header.
|
||||
/// This data can contain nulls so use extra_length to get the size.
|
||||
/// </summary>
|
||||
public char* extra { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of extra uncompressed data in the header
|
||||
/// </summary>
|
||||
public ushort extra_length { get; set; }
|
||||
|
||||
public mspack_file fh { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.LIT
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public class Compressor : BaseCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new LIT compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.LIT
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public class Decompressor : BaseDecompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new LIT decompressor
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class lzss
|
||||
{
|
||||
public const int LZSS_WINDOW_SIZE = 4096;
|
||||
public const byte LZSS_WINDOW_FILL = 0x20;
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses an LZSS stream.
|
||||
///
|
||||
/// Input bytes will be read in as necessary using the system->read()
|
||||
/// function with the input file handle given. This will continue until
|
||||
/// system->read() returns 0 bytes, or an error. Errors will be passed
|
||||
/// out of the function as MSPACK_ERR_READ errors. Input streams should
|
||||
/// convey an "end of input stream" by refusing to supply all the bytes
|
||||
/// that LZSS asks for when they reach the end of the stream, rather
|
||||
/// than return an error code.
|
||||
///
|
||||
/// Output bytes will be passed to the system->write() function, using
|
||||
/// the output file handle given. More than one call may be made to
|
||||
/// system->write().
|
||||
///
|
||||
/// As EXPAND.EXE (SZDD/KWAJ), Microsoft Help and QBasic have slightly
|
||||
/// different encodings for the control byte and matches, a "mode"
|
||||
/// parameter is allowed, to choose the encoding.
|
||||
/// </summary>
|
||||
/// <param name="system">
|
||||
/// An mspack_system structure used to read from
|
||||
/// the input stream and write to the output
|
||||
/// stream, also to allocate and free memory.
|
||||
/// </param>
|
||||
/// <param name="input">An input stream with the LZSS data.</param>
|
||||
/// <param name="output">An output stream to write the decoded data to.</param>
|
||||
/// <param name="input_buffer_size">The number of bytes to use as an input bitstream buffer.</param>
|
||||
/// <param name="mode">One of <see cref="LZSS_MODE"/> values</param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public static MSPACK_ERR lzss_decompress(mspack_system system, mspack_file input, mspack_file output, int input_buffer_size, LZSS_MODE mode) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class lzx
|
||||
{
|
||||
// Some constants defined by the LZX specification
|
||||
public const int LZX_MIN_MATCH = 2;
|
||||
public const int LZX_MAX_MATCH = 257;
|
||||
public const int LZX_NUM_CHARS = 256;
|
||||
public const int LZX_BLOCKTYPE_INVALID = 0; /* also blocktypes 4-7 invalid */
|
||||
public const int LZX_BLOCKTYPE_VERBATIM = 1;
|
||||
public const int LZX_BLOCKTYPE_ALIGNED = 2;
|
||||
public const int LZX_BLOCKTYPE_UNCOMPRESSED = 3;
|
||||
public const int LZX_PRETREE_NUM_ELEMENTS = 20;
|
||||
public const int LZX_ALIGNED_NUM_ELEMENTS = 8; /* aligned offset tree #elements */
|
||||
public const int LZX_NUM_PRIMARY_LENGTHS = 7; /* this one missing from spec! */
|
||||
public const int LZX_NUM_SECONDARY_LENGTHS = 249; /* length tree #elements */
|
||||
|
||||
// LZX huffman defines: tweak tablebits as desired
|
||||
public const int LZX_PRETREE_MAXSYMBOLS = LZX_PRETREE_NUM_ELEMENTS;
|
||||
public const int LZX_PRETREE_TABLEBITS = 6;
|
||||
public const int LZX_MAINTREE_MAXSYMBOLS = LZX_NUM_CHARS + 290 * 8;
|
||||
public const int LZX_MAINTREE_TABLEBITS = 12;
|
||||
public const int LZX_LENGTH_MAXSYMBOLS = LZX_NUM_SECONDARY_LENGTHS + 1;
|
||||
public const int LZX_LENGTH_TABLEBITS = 12;
|
||||
public const int LZX_ALIGNED_MAXSYMBOLS = LZX_ALIGNED_NUM_ELEMENTS;
|
||||
public const int LZX_ALIGNED_TABLEBITS = 7;
|
||||
public const int LZX_LENTABLE_SAFETY = 64; /* table decoding overruns are allowed */
|
||||
|
||||
public const int LZX_FRAME_SIZE = 32768; /* the size of a frame in LZX */
|
||||
|
||||
/// <summary>
|
||||
/// Allocates and initialises LZX decompression state for decoding an LZX
|
||||
/// stream.
|
||||
///
|
||||
/// This routine uses system->alloc() to allocate memory. If memory
|
||||
/// allocation fails, or the parameters to this function are invalid,
|
||||
/// null is returned.
|
||||
/// </summary>
|
||||
/// <param name="system">
|
||||
/// An mspack_system structure used to read from
|
||||
/// the input stream and write to the output
|
||||
/// stream, also to allocate and free memory.
|
||||
/// </param>
|
||||
/// <param name="input">An input stream with the LZX data.</param>
|
||||
/// <param name="output">An output stream to write the decoded data to.</param>
|
||||
/// <param name="window_bits">
|
||||
/// The size of the decoding window, which must be
|
||||
/// between 15 and 21 inclusive for regular LZX
|
||||
/// data, or between 17 and 25 inclusive for
|
||||
/// LZX DELTA data.
|
||||
/// </param>
|
||||
/// <param name="reset_interval">
|
||||
/// The interval at which the LZX bitstream is
|
||||
/// reset, in multiples of LZX frames (32678
|
||||
/// bytes), e.g. a value of 2 indicates the input
|
||||
/// stream resets after every 65536 output bytes.
|
||||
/// A value of 0 indicates that the bitstream never
|
||||
/// resets, such as in CAB LZX streams.
|
||||
/// </param>
|
||||
/// <param name="input_buffer_size">The number of bytes to use as an input bitstream buffer.</param>
|
||||
/// <param name="output_length">
|
||||
/// The length in bytes of the entirely
|
||||
/// decompressed output stream, if known in
|
||||
/// advance. It is used to correctly perform the
|
||||
/// Intel E8 transformation, which must stop 6
|
||||
/// bytes before the very end of the
|
||||
/// decompressed stream. It is not otherwise used
|
||||
/// or adhered to. If the full decompressed
|
||||
/// length is known in advance, set it here.
|
||||
/// If it is NOT known, use the value 0, and call
|
||||
/// lzxd_set_output_length() once it is
|
||||
/// known. If never set, 4 of the final 6 bytes
|
||||
/// of the output stream may be incorrect.
|
||||
/// </param>
|
||||
/// <param name="is_delta">
|
||||
/// Should be zero for all regular LZX data,
|
||||
/// non-zero for LZX DELTA encoded data.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A pointer to an initialised lzxd_stream structure, or null if
|
||||
/// there was not enough memory or parameters to the function were wrong.
|
||||
/// </returns>
|
||||
public static lzxd_stream lzxd_init(mspack_system system, mspack_file input, mspack_file output, int window_bits, int reset_interval, int input_buffer_size, long output_length, char is_delta) => null;
|
||||
|
||||
/// <summary>
|
||||
/// See description of output_length in lzxd_init()
|
||||
/// </summary>
|
||||
public static void lzxd_set_output_length(lzxd_stream lzx, long output_length) { }
|
||||
|
||||
/// <summary>
|
||||
/// Reads LZX DELTA reference data into the window and allows
|
||||
/// lzxd_decompress() to reference it.
|
||||
///
|
||||
/// Call this before the first call to lzxd_decompress().
|
||||
/// </summary>
|
||||
/// <param name="lzx">The LZX stream to apply this reference data to</param>
|
||||
/// <param name="system">
|
||||
/// An mspack_system implementation to use with the
|
||||
/// input param. Only read() will be called.
|
||||
/// </param>
|
||||
/// <param name="input">
|
||||
/// An input file handle to read reference data using
|
||||
/// system->read().
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// The length of the reference data. Cannot be longer
|
||||
/// than the LZX window size.
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public static MSPACK_ERR lzxd_set_reference_data(lzxd_stream lzx, mspack_system system, mspack_file input, uint length) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses entire or partial LZX streams.
|
||||
///
|
||||
/// The number of bytes of data that should be decompressed is given as the
|
||||
/// out_bytes parameter. If more bytes are decoded than are needed, they
|
||||
/// will be kept over for a later invocation.
|
||||
///
|
||||
/// The output bytes will be passed to the system->write() function given in
|
||||
/// lzxd_init(), using the output file handle given in lzxd_init(). More than
|
||||
/// one call may be made to system->write().
|
||||
///
|
||||
/// Input bytes will be read in as necessary using the system->read()
|
||||
/// function given in lzxd_init(), using the input file handle given in
|
||||
/// lzxd_init(). This will continue until system->read() returns 0 bytes,
|
||||
/// or an error. Errors will be passed out of the function as
|
||||
/// MSPACK_ERR_READ errors. Input streams should convey an "end of input
|
||||
/// stream" by refusing to supply all the bytes that LZX asks for when they
|
||||
/// reach the end of the stream, rather than return an error code.
|
||||
///
|
||||
/// If any error code other than MSPACK_ERR_OK is returned, the stream
|
||||
/// should be considered unusable and lzxd_decompress() should not be
|
||||
/// called again on this stream.
|
||||
/// </summary>
|
||||
/// <param name="lzx">LZX decompression state, as allocated by lzxd_init().</param>
|
||||
/// <param name="out_bytes">The number of bytes of data to decompress.</param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public static MSPACK_ERR lzxd_decompress(lzxd_stream lzx, long out_bytes) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Frees all state associated with an LZX data stream. This will call
|
||||
/// system->free() using the system pointer given in lzxd_init().
|
||||
/// </summary>
|
||||
/// <param name="lzx">LZX decompression state to free.</param>
|
||||
public static void lzxd_free(lzxd_stream lzx) { }
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.lzx;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class lzxd_stream : readbits
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of bytes actually output
|
||||
/// </summary>
|
||||
public long offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overall decompressed length of stream
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decoding window
|
||||
/// </summary>
|
||||
public byte* window { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Window size
|
||||
/// </summary>
|
||||
public uint window_size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LZX DELTA reference data size
|
||||
/// </summary>
|
||||
public uint ref_data_size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of match_offset entries in table
|
||||
/// </summary>
|
||||
public uint num_offsets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current frame offset within in window
|
||||
/// </summary>
|
||||
public uint frame_posn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of 32kb frames processed
|
||||
/// </summary>
|
||||
public uint frame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which frame do we reset the compressor?
|
||||
/// </summary>
|
||||
public uint reset_interval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For the LRU offset system
|
||||
/// </summary>
|
||||
public uint R0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For the LRU offset system
|
||||
/// </summary>
|
||||
public uint R1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For the LRU offset system
|
||||
/// </summary>
|
||||
public uint R2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed length of this LZX block
|
||||
/// </summary>
|
||||
public uint block_length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed bytes still left to decode
|
||||
/// </summary>
|
||||
public uint block_remaining { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Magic header value used for transform
|
||||
/// </summary>
|
||||
public int intel_filesize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Has intel E8 decoding started?
|
||||
/// </summary>
|
||||
public byte intel_started { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of the current block
|
||||
/// </summary>
|
||||
public byte block_type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Have we started decoding at all yet?
|
||||
/// </summary>
|
||||
public byte header_read { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Have we reached the end of input?
|
||||
/// </summary>
|
||||
public new byte input_end { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Does stream follow LZX DELTA spec?
|
||||
/// </summary>
|
||||
public byte is_delta { get; set; }
|
||||
|
||||
#region Huffman code lengths
|
||||
|
||||
public byte[] PRETREE_len { get; set; } = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public byte[] MAINTREE_len { get; set; } = new byte[LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public byte[] LENGTH_len { get; set; } = new byte[LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public byte[] ALIGNED_len { get; set; } = new byte[LZX_ALIGNED_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Huffman decoding tables
|
||||
|
||||
public ushort[] PRETREE_table { get; set; } = new ushort[(1 << LZX_PRETREE_TABLEBITS) + (LZX_PRETREE_MAXSYMBOLS * 2)];
|
||||
|
||||
public ushort[] MAINTREE_table { get; set; } = new ushort[(1 << LZX_MAINTREE_TABLEBITS) + (LZX_MAINTREE_MAXSYMBOLS * 2)];
|
||||
|
||||
public ushort[] LENGTH_table { get; set; } = new ushort[(1 << LZX_LENGTH_TABLEBITS) + (LZX_LENGTH_MAXSYMBOLS * 2)];
|
||||
|
||||
public ushort[] ALIGNED_table { get; set; } = new ushort[(1 << LZX_ALIGNED_TABLEBITS) + (LZX_ALIGNED_MAXSYMBOLS * 2)];
|
||||
|
||||
public byte LENGTH_empty { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// This is used purely for doing the intel E8 transform
|
||||
/// </summary>
|
||||
public byte[] e8_buf { get; set; } = new byte[LZX_FRAME_SIZE];
|
||||
|
||||
public override void READ_BYTES()
|
||||
{
|
||||
byte b0, b1;
|
||||
READ_IF_NEEDED(ref i_ptr, ref i_end);
|
||||
b0 = *i_ptr++;
|
||||
READ_IF_NEEDED(ref i_ptr, ref i_end);
|
||||
b1 = *i_ptr++;
|
||||
INJECT_BITS_MSB((b1 << 8) | b0, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class mszip
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of LZ history window
|
||||
/// </summary>
|
||||
public const int MSZIP_FRAME_SIZE = 32768;
|
||||
|
||||
/// <summary>
|
||||
/// Literal/length huffman tree
|
||||
/// </summary>
|
||||
public const int MSZIP_LITERAL_MAXSYMBOLS = 288;
|
||||
|
||||
public const int MSZIP_LITERAL_TABLEBITS = 9;
|
||||
|
||||
/// <summary>
|
||||
/// Distance huffman tree
|
||||
/// </summary>
|
||||
public const int MSZIP_DISTANCE_MAXSYMBOLS = 32;
|
||||
|
||||
public const int MSZIP_DISTANCE_TABLEBITS = 6;
|
||||
|
||||
// If there are less direct lookup entries than symbols, the longer
|
||||
// code pointers will be <= maxsymbols. This must not happen, or we
|
||||
// will decode entries badly
|
||||
public const int MSZIP_LITERAL_TABLESIZE = MSZIP_LITERAL_MAXSYMBOLS * 4;
|
||||
public const int MSZIP_DISTANCE_TABLESIZE = 1 << MSZIP_DISTANCE_TABLEBITS + (MSZIP_DISTANCE_MAXSYMBOLS * 2);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates MS-ZIP decompression stream for decoding the given stream.
|
||||
///
|
||||
/// - uses system->alloc() to allocate memory
|
||||
///
|
||||
/// - returns null if not enough memory
|
||||
///
|
||||
/// - input_buffer_size is how many bytes to use as an input bitstream buffer
|
||||
///
|
||||
/// - if repair_mode is non-zero, errors in decompression will be skipped
|
||||
/// and 'holes' left will be filled with zero bytes. This allows at least
|
||||
/// a partial recovery of erroneous data.
|
||||
/// </summary>
|
||||
/// <param name="system"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="output"></param>
|
||||
/// <param name="input_buffer_size"></param>
|
||||
/// <param name="repair_mode"></param>
|
||||
/// <returns></returns>
|
||||
public static mszipd_stream mszipd_init(mspack_system system, mspack_file input, mspack_file output, int input_buffer_size, int repair_mode) => null;
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses, or decompresses more of, an MS-ZIP stream.
|
||||
///
|
||||
/// - out_bytes of data will be decompressed and the function will return
|
||||
/// with an MSPACK_ERR_OK return code.
|
||||
///
|
||||
/// - decompressing will stop as soon as out_bytes is reached. if the true
|
||||
/// amount of bytes decoded spills over that amount, they will be kept for
|
||||
/// a later invocation of mszipd_decompress().
|
||||
///
|
||||
/// - the output bytes will be passed to the system->write() function given in
|
||||
/// mszipd_init(), using the output file handle given in mszipd_init(). More
|
||||
/// than one call may be made to system->write()
|
||||
///
|
||||
/// - MS-ZIP will read input bytes as necessary using the system->read()
|
||||
/// function given in mszipd_init(), using the input file handle given in
|
||||
/// mszipd_init(). This will continue until system->read() returns 0 bytes,
|
||||
/// or an error.
|
||||
/// </summary>
|
||||
/// <param name="zip"></param>
|
||||
/// <param name="out_bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static MSPACK_ERR mszipd_decompress(mszipd_stream zip, long out_bytes) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much
|
||||
/// like mszipd_decompress(), but doesn't take an out_bytes parameter
|
||||
/// </summary>
|
||||
/// <param name="zip"></param>
|
||||
/// <returns></returns>
|
||||
public static MSPACK_ERR mszipd_decompress_kwaj(mszipd_stream zip) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Frees all stream associated with an MS-ZIP data stream
|
||||
///
|
||||
/// - calls system->free() using the system pointer given in mszipd_init()
|
||||
/// </summary>
|
||||
/// <param name="zip"></param>
|
||||
public static void mszipd_free(mszipd_stream zip) { }
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.lzx;
|
||||
using static SabreTools.Compression.libmspack.mszip;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class mszipd_stream : readbits
|
||||
{
|
||||
/// <summary>
|
||||
/// inflate() will call this whenever the window should be emptied.
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public int flush_window(uint val) => 0;
|
||||
|
||||
public int repair_mode { get; set; }
|
||||
|
||||
public int bytes_output { get; set; }
|
||||
|
||||
#region Huffman code lengths
|
||||
|
||||
public byte[] LITERAL_len { get; set; } = new byte[MSZIP_LITERAL_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
public byte[] DISTANCE_len { get; set; } = new byte[MSZIP_DISTANCE_MAXSYMBOLS + LZX_LENTABLE_SAFETY];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Huffman decoding tables
|
||||
|
||||
public ushort[] LITERAL_table { get; set; } = new ushort[(1 << MSZIP_LITERAL_TABLESIZE) + (LZX_PRETREE_MAXSYMBOLS * 2)];
|
||||
|
||||
public ushort[] DISTANCE_table { get; set; } = new ushort[(1 << MSZIP_DISTANCE_TABLESIZE) + (LZX_MAINTREE_MAXSYMBOLS * 2)];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 32kb history window
|
||||
/// </summary>
|
||||
public byte[] window { get; set; } = new byte[MSZIP_FRAME_SIZE];
|
||||
|
||||
public override void READ_BYTES()
|
||||
{
|
||||
READ_IF_NEEDED;
|
||||
INJECT_BITS_LSB(*i_ptr++, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.None
|
||||
{
|
||||
public unsafe class DecompressState : mscabd_decompress_state
|
||||
{
|
||||
public DecompressState()
|
||||
{
|
||||
this.comp_type = MSCAB_COMP.MSCAB_COMP_NONE;
|
||||
this.state = null;
|
||||
}
|
||||
|
||||
public DecompressState(mscabd_decompress_state oldstate)
|
||||
{
|
||||
this.comp_type = MSCAB_COMP.MSCAB_COMP_NONE;
|
||||
this.state = null;
|
||||
if (oldstate != null)
|
||||
{
|
||||
this.folder = oldstate.folder;
|
||||
this.data = oldstate.data;
|
||||
this.offset = oldstate.offset;
|
||||
this.block = oldstate.block;
|
||||
this.outlen = oldstate.outlen;
|
||||
this.sys = oldstate.sys;
|
||||
this.incab = oldstate.incab;
|
||||
this.infh = oldstate.infh;
|
||||
this.outfh = oldstate.outfh;
|
||||
this.i_ptr = oldstate.i_ptr;
|
||||
this.i_end = oldstate.i_end;
|
||||
this.input = oldstate.input;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe MSPACK_ERR decompress(object data, long bytes)
|
||||
{
|
||||
State s = data as State;
|
||||
while (bytes > 0)
|
||||
{
|
||||
int run = (bytes > s.BufferSize) ? s.BufferSize : (int)bytes;
|
||||
if (s.InternalSystem.read(s.Input, s.Buffer, run) != run) return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
if (s.InternalSystem.write(s.Output, s.Buffer, run) != run) return MSPACK_ERR.MSPACK_ERR_WRITE;
|
||||
bytes -= run;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.None
|
||||
{
|
||||
/// <summary>
|
||||
/// The "not compressed" method decompressor
|
||||
/// </summary>
|
||||
public unsafe class State
|
||||
{
|
||||
public mspack_system InternalSystem { get; private set; }
|
||||
|
||||
public mspack_file Input { get; private set; }
|
||||
|
||||
public mspack_file Output { get; private set; }
|
||||
|
||||
public FixedArray<byte> Buffer { get; private set; }
|
||||
|
||||
public int BufferSize { get; private set; }
|
||||
|
||||
public State(mspack_system sys, mspack_file infh, mspack_file outfh, int bufsize)
|
||||
{
|
||||
this.InternalSystem = sys;
|
||||
this.Input = infh;
|
||||
this.Output = outfh;
|
||||
this.Buffer = new FixedArray<byte>(bufsize);
|
||||
this.BufferSize = bufsize;
|
||||
}
|
||||
|
||||
~State()
|
||||
{
|
||||
mspack_system sys = this.InternalSystem;
|
||||
sys.free(this.Buffer);
|
||||
//sys.free(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.OAB
|
||||
{
|
||||
/// <summary>
|
||||
/// A compressor for the Offline Address Book (OAB) format.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class Compressor : BaseCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new OAB compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys an existing OAB compressor
|
||||
/// </summary>
|
||||
~Compressor()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress a full OAB file.
|
||||
///
|
||||
/// The input file will be read and the compressed contents written to the
|
||||
/// output file.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename of the output file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR compress(in string input, in string output) => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Generate a compressed incremental OAB patch file.
|
||||
///
|
||||
/// The two uncompressed files "input" and "base" will be read, and an
|
||||
/// incremental patch to generate "input" from "base" will be written to
|
||||
/// the output file.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input file containing the new
|
||||
/// version of its contents. This is passed directly
|
||||
/// to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="base">
|
||||
/// The filename of the original base file containing
|
||||
/// the old version of its contents, against which the
|
||||
/// incremental patch shall generated. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename of the output file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR compress_incremental(in string input, in string @base, in string output) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.macros;
|
||||
using static SabreTools.Compression.libmspack.oab;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.OAB
|
||||
{
|
||||
/// <summary>
|
||||
/// A decompressor for .LZX (Offline Address Book) files
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
/// <see cref="mspack_create_oab_decompressor()"/>
|
||||
/// <see cref="mspack_destroy_oab_decompressor()"/>
|
||||
public unsafe class Decompressor : BaseDecompressor
|
||||
{
|
||||
public int buf_size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OAB decompressor
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new OABSystem();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
this.buf_size = 4096;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a full Offline Address Book file.
|
||||
///
|
||||
/// If the input file is a valid compressed Offline Address Book file,
|
||||
/// it will be read and the decompressed contents will be written to
|
||||
/// the output file.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename of the output file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR decompress(in string input, in string output)
|
||||
{
|
||||
mspack_system sys;
|
||||
mspack_file infh = null;
|
||||
mspack_file outfh = null;
|
||||
FixedArray<byte> buf = null;
|
||||
FixedArray<byte> hdrbuf = new FixedArray<byte>(oabhead_SIZEOF);
|
||||
uint block_max, target_size;
|
||||
lzxd_stream lzx = null;
|
||||
OABSystem oabd_sys;
|
||||
uint window_bits;
|
||||
MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
sys = this.system;
|
||||
|
||||
infh = sys.open(input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
|
||||
if (infh == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
if (sys.read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
if (EndGetI32(hdrbuf, oabhead_VersionHi) != 3 ||
|
||||
EndGetI32(hdrbuf, oabhead_VersionLo) != 1)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
block_max = EndGetI32(hdrbuf, oabhead_BlockMax);
|
||||
target_size = EndGetI32(hdrbuf, oabhead_TargetSize);
|
||||
|
||||
outfh = sys.open(output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE);
|
||||
if (outfh == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
buf = new FixedArray<byte>(this.buf_size);
|
||||
if (buf == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
oabd_sys = sys as OABSystem;
|
||||
|
||||
oabd_file in_ofh = new oabd_file();
|
||||
in_ofh.orig_sys = sys;
|
||||
in_ofh.orig_file = infh;
|
||||
|
||||
oabd_file out_ofh = new oabd_file();
|
||||
out_ofh.orig_sys = sys;
|
||||
out_ofh.orig_file = outfh;
|
||||
|
||||
while (target_size > 0)
|
||||
{
|
||||
uint blk_csize, blk_dsize, blk_crc, blk_flags;
|
||||
|
||||
if (sys.read(infh, buf, oabblk_SIZEOF) != oabblk_SIZEOF)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
goto outlbl;
|
||||
}
|
||||
blk_flags = EndGetI32(buf, oabblk_Flags);
|
||||
blk_csize = EndGetI32(buf, oabblk_CompSize);
|
||||
blk_dsize = EndGetI32(buf, oabblk_UncompSize);
|
||||
blk_crc = EndGetI32(buf, oabblk_CRC);
|
||||
|
||||
if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
if (blk_flags == 0)
|
||||
{
|
||||
// Uncompressed block
|
||||
if (blk_dsize != blk_csize)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
goto outlbl;
|
||||
}
|
||||
ret = copy_fh(sys, infh, outfh, (int)blk_dsize, buf, this.buf_size);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// LZX compressed block
|
||||
window_bits = 17;
|
||||
|
||||
while (window_bits < 25 && (1 << (int)window_bits) < blk_dsize)
|
||||
window_bits++;
|
||||
|
||||
in_ofh.available = (int)blk_csize;
|
||||
out_ofh.crc = 0xffffffff;
|
||||
|
||||
lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, 0, this.buf_size, blk_dsize, 1);
|
||||
if (lzx == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
ret = lzxd_decompress(lzx, blk_dsize);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
goto outlbl;
|
||||
|
||||
lzxd_free(lzx);
|
||||
lzx = null;
|
||||
|
||||
// Consume any trailing padding bytes before the next block
|
||||
ret = copy_fh(sys, infh, null, in_ofh.available, buf, this.buf_size);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl;
|
||||
|
||||
if (out_ofh.crc != blk_crc)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_CHECKSUM;
|
||||
goto outlbl;
|
||||
}
|
||||
}
|
||||
target_size -= blk_dsize;
|
||||
}
|
||||
|
||||
outlbl:
|
||||
if (lzx != null) lzxd_free(lzx);
|
||||
if (outfh != null) sys.close(outfh);
|
||||
if (infh != null) sys.close(infh);
|
||||
sys.free(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses an Offline Address Book with an incremental patch file.
|
||||
///
|
||||
/// This requires both a full UNCOMPRESSED Offline Address Book file to
|
||||
/// act as the "base", and a compressed incremental patch file as input.
|
||||
/// If the input file is valid, it will be decompressed with reference to
|
||||
/// the base file, and the decompressed contents will be written to the
|
||||
/// output file.
|
||||
///
|
||||
/// There is no way to tell what the right base file is for the given
|
||||
/// incremental patch, but if you get it wrong, this will usually result
|
||||
/// in incorrect data being decompressed, which will then fail a checksum
|
||||
/// test.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="base">
|
||||
/// The filename of the base file to which the
|
||||
/// incremental patch shall be applied. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename of the output file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR.MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR decompress_incremental(in string input, in string @base, in string output)
|
||||
{
|
||||
mspack_file infh = null, basefh = null, outfh = null;
|
||||
lzxd_stream lzx = null;
|
||||
FixedArray<byte> buf = null;
|
||||
uint window_bits, window_size;
|
||||
MSPACK_ERR ret = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
mspack_system sys = this.system;
|
||||
|
||||
infh = sys.open(input, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
|
||||
if (infh == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
FixedArray<byte> hdrbuf = new FixedArray<byte>(patchhead_SIZEOF);
|
||||
if (sys.read(infh, hdrbuf, patchhead_SIZEOF) != patchhead_SIZEOF)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
if (EndGetI32(hdrbuf, patchhead_VersionHi) != 3 ||
|
||||
EndGetI32(hdrbuf, patchhead_VersionLo) != 2)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
uint block_max = EndGetI32(hdrbuf, patchhead_BlockMax);
|
||||
uint target_size = EndGetI32(hdrbuf, patchhead_TargetSize);
|
||||
|
||||
// We use it for reading block headers too
|
||||
if (block_max < patchblk_SIZEOF)
|
||||
block_max = patchblk_SIZEOF;
|
||||
|
||||
basefh = sys.open(@base, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
|
||||
if (basefh == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
outfh = sys.open(output, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE);
|
||||
if (outfh == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
buf = new FixedArray<byte>(this.buf_size);
|
||||
if (buf == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
OABSystem oabd_sys = sys as OABSystem;
|
||||
|
||||
oabd_file in_ofh = new oabd_file();
|
||||
in_ofh.orig_sys = sys;
|
||||
in_ofh.orig_file = infh;
|
||||
|
||||
oabd_file out_ofh = new oabd_file();
|
||||
out_ofh.orig_sys = sys;
|
||||
out_ofh.orig_file = outfh;
|
||||
|
||||
while (target_size > 0)
|
||||
{
|
||||
if (sys.read(infh, buf, patchblk_SIZEOF) != patchblk_SIZEOF)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
uint blk_csize = EndGetI32(buf, patchblk_PatchSize);
|
||||
uint blk_dsize = EndGetI32(buf, patchblk_TargetSize);
|
||||
uint blk_ssize = EndGetI32(buf, patchblk_SourceSize);
|
||||
uint blk_crc = EndGetI32(buf, patchblk_CRC);
|
||||
|
||||
if (blk_dsize > block_max || blk_dsize > target_size ||
|
||||
blk_ssize > block_max)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
|
||||
window_size = (uint)((blk_ssize + 32767) & ~32767);
|
||||
window_size += blk_dsize;
|
||||
window_bits = 17;
|
||||
|
||||
while (window_bits < 25 && (1 << (int)window_bits) < window_size)
|
||||
window_bits++;
|
||||
|
||||
in_ofh.available = (int)blk_csize;
|
||||
out_ofh.crc = 0xffffffff;
|
||||
|
||||
lzx = lzxd_init(&oabd_sys, (void*)&in_ofh, (void*)&out_ofh, window_bits, 0, 4096, blk_dsize, 1);
|
||||
if (lzx == null)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_NOMEMORY;
|
||||
goto outlbl;
|
||||
}
|
||||
ret = lzxd_set_reference_data(lzx, sys, basefh, blk_ssize);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
goto outlbl;
|
||||
|
||||
ret = lzxd_decompress(lzx, blk_dsize);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
goto outlbl;
|
||||
|
||||
lzxd_free(lzx);
|
||||
lzx = null;
|
||||
|
||||
// Consume any trailing padding bytes before the next block
|
||||
ret = copy_fh(sys, infh, null, in_ofh.available, buf, this.buf_size);
|
||||
if (ret != MSPACK_ERR.MSPACK_ERR_OK) goto outlbl;
|
||||
|
||||
if (out_ofh.crc != blk_crc)
|
||||
{
|
||||
ret = MSPACK_ERR.MSPACK_ERR_CHECKSUM;
|
||||
goto outlbl;
|
||||
}
|
||||
|
||||
target_size -= blk_dsize;
|
||||
}
|
||||
|
||||
outlbl:
|
||||
if (lzx != null) lzxd_free(lzx);
|
||||
if (outfh != null) sys.close(outfh);
|
||||
if (basefh != null) sys.close(basefh);
|
||||
if (infh != null) sys.close(infh);
|
||||
//if (buf != null) sys.free(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static MSPACK_ERR copy_fh(mspack_system sys, mspack_file infh, mspack_file outfh, int bytes_to_copy, byte* buf, int buf_size)
|
||||
{
|
||||
while (bytes_to_copy > 0)
|
||||
{
|
||||
int run = buf_size;
|
||||
if (run > bytes_to_copy)
|
||||
{
|
||||
run = bytes_to_copy;
|
||||
}
|
||||
if (sys.read(infh, buf, run) != run)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
if (outfh != null && sys.write(outfh, buf, run) != run)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_WRITE;
|
||||
}
|
||||
bytes_to_copy -= run;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an OAB decompression engine parameter. Available only in OAB
|
||||
/// decompressor version 2 and above.
|
||||
///
|
||||
/// - #MSOABD_PARAM_DECOMPBUF: How many bytes should be used as an input
|
||||
/// buffer by decompressors? The minimum value is 16. The default value
|
||||
/// is 4096.
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter to set</param>
|
||||
/// <param name="value">The value to set the parameter to</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR.MSPACK_ERR_OK if all is OK, or MSPACK_ERR.MSPACK_ERR_ARGS if there
|
||||
/// is a problem with either parameter or value.
|
||||
/// </returns>
|
||||
public MSPACK_ERR set_param(MSOABD_PARAM param, int value)
|
||||
{
|
||||
if (param == MSOABD_PARAM.MSOABD_PARAM_DECOMPBUF && value >= 16)
|
||||
{
|
||||
// Must be at least 16 bytes (patchblk_SIZEOF, oabblk_SIZEOF)
|
||||
this.buf_size = value;
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_ARGS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.OAB
|
||||
{
|
||||
public unsafe class OABSystem : mspack_default_system
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int read(mspack_file base_file, void* buf, int size)
|
||||
{
|
||||
oabd_file file = (oabd_file)base_file;
|
||||
int bytes_read;
|
||||
|
||||
if (size > file.available)
|
||||
size = file.available;
|
||||
|
||||
bytes_read = file.orig_sys.read(file.orig_file, buf, size);
|
||||
if (bytes_read < 0)
|
||||
return bytes_read;
|
||||
|
||||
file.available -= bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int write(mspack_file base_file, void* buf, int size)
|
||||
{
|
||||
oabd_file file = (oabd_file)base_file;
|
||||
int bytes_written = file.orig_sys.write(file.orig_file, buf, size);
|
||||
|
||||
if (bytes_written > 0)
|
||||
file.crc = mspack.crc32(file.crc, buf, bytes_written);
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class oab
|
||||
{
|
||||
public const int oabhead_VersionHi = 0x0000;
|
||||
public const int oabhead_VersionLo = 0x0004;
|
||||
public const int oabhead_BlockMax = 0x0008;
|
||||
public const int oabhead_TargetSize = 0x000c;
|
||||
public const int oabhead_SIZEOF = 0x0010;
|
||||
|
||||
public const int oabblk_Flags = 0x0000;
|
||||
public const int oabblk_CompSize = 0x0004;
|
||||
public const int oabblk_UncompSize = 0x0008;
|
||||
public const int oabblk_CRC = 0x000c;
|
||||
public const int oabblk_SIZEOF = 0x0010;
|
||||
|
||||
public const int patchhead_VersionHi = 0x0000;
|
||||
public const int patchhead_VersionLo = 0x0004;
|
||||
public const int patchhead_BlockMax = 0x0008;
|
||||
public const int patchhead_SourceSize = 0x000c;
|
||||
public const int patchhead_TargetSize = 0x0010;
|
||||
public const int patchhead_SourceCRC = 0x0014;
|
||||
public const int patchhead_TargetCRC = 0x0018;
|
||||
public const int patchhead_SIZEOF = 0x001c;
|
||||
|
||||
public const int patchblk_PatchSize = 0x0000;
|
||||
public const int patchblk_TargetSize = 0x0004;
|
||||
public const int patchblk_SourceSize = 0x0008;
|
||||
public const int patchblk_CRC = 0x000c;
|
||||
public const int patchblk_SIZEOF = 0x0010;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public class oabd_file : mspack_file
|
||||
{
|
||||
public mspack_system orig_sys { get; set; }
|
||||
|
||||
public mspack_file orig_file { get; set; }
|
||||
|
||||
public uint crc { get; set; }
|
||||
|
||||
public int available { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe static class qtm
|
||||
{
|
||||
public const int QTM_FRAME_SIZE = 32768;
|
||||
|
||||
#region Quantum static data tables
|
||||
|
||||
/*
|
||||
* Quantum uses 'position slots' to represent match offsets. For every
|
||||
* match, a small 'position slot' number and a small offset from that slot
|
||||
* are encoded instead of one large offset.
|
||||
*
|
||||
* position_base[] is an index to the position slot bases
|
||||
*
|
||||
* extra_bits[] states how many bits of offset-from-base data is needed.
|
||||
*
|
||||
* length_base[] and length_extra[] are equivalent in function, but are
|
||||
* used for encoding selector 6 (variable length match) match lengths,
|
||||
* instead of match offsets.
|
||||
*
|
||||
* They are generated with the following code:
|
||||
* unsigned int i, offset;
|
||||
* for (i = 0, offset = 0; i < 42; i++) {
|
||||
* position_base[i] = offset;
|
||||
* extra_bits[i] = ((i < 2) ? 0 : (i - 2)) >> 1;
|
||||
* offset += 1 << extra_bits[i];
|
||||
* }
|
||||
* for (i = 0, offset = 0; i < 26; i++) {
|
||||
* length_base[i] = offset;
|
||||
* length_extra[i] = (i < 2 ? 0 : i - 2) >> 2;
|
||||
* offset += 1 << length_extra[i];
|
||||
* }
|
||||
* length_base[26] = 254; length_extra[26] = 0;
|
||||
*/
|
||||
|
||||
private static readonly uint[] position_base = new uint[42]
|
||||
{
|
||||
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, 786432, 1048576, 1572864
|
||||
};
|
||||
private static readonly byte[] extra_bits = 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
|
||||
};
|
||||
|
||||
private static readonly byte[] length_base = new byte[27]
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 22, 26,
|
||||
30, 38, 46, 54, 62, 78, 94, 110, 126, 158, 190, 222, 254
|
||||
};
|
||||
|
||||
private static readonly byte[] length_extra = 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
|
||||
|
||||
private static void qtmd_update_model(qtmd_model model)
|
||||
{
|
||||
qtmd_modelsym tmp;
|
||||
int i, j;
|
||||
|
||||
if (--model.shiftsleft > 0)
|
||||
{
|
||||
for (i = model.entries - 1; i >= 0; i--)
|
||||
{
|
||||
/* -1, not -2; the 0 entry saves this */
|
||||
model.syms[i].cumfreq >>= 1;
|
||||
if (model.syms[i].cumfreq <= model.syms[i + 1].cumfreq)
|
||||
{
|
||||
model.syms[i].cumfreq = (ushort)(model.syms[i + 1].cumfreq + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
model.shiftsleft = 50;
|
||||
for (i = 0; i < model.entries; i++)
|
||||
{
|
||||
/* no -1, want to include the 0 entry */
|
||||
/* this converts cumfreqs into frequencies, then shifts right */
|
||||
model.syms[i].cumfreq -= model.syms[i + 1].cumfreq;
|
||||
model.syms[i].cumfreq++; /* avoid losing things entirely */
|
||||
model.syms[i].cumfreq >>= 1;
|
||||
}
|
||||
|
||||
/* now sort by frequencies, decreasing order -- this must be an
|
||||
* inplace selection sort, or a sort with the same (in)stability
|
||||
* characteristics */
|
||||
for (i = 0; i < model.entries - 1; i++)
|
||||
{
|
||||
for (j = i + 1; j < model.entries; j++)
|
||||
{
|
||||
if (model.syms[i].cumfreq < model.syms[j].cumfreq)
|
||||
{
|
||||
tmp = model.syms[i];
|
||||
model.syms[i] = model.syms[j];
|
||||
model.syms[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* then convert frequencies back to cumfreq */
|
||||
for (i = model.entries - 1; i >= 0; i--)
|
||||
{
|
||||
model.syms[i].cumfreq += model.syms[i + 1].cumfreq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates Quantum decompression state for decoding the given stream.
|
||||
///
|
||||
/// - returns null if window_bits is outwith the range 10 to 21 (inclusive).
|
||||
///
|
||||
/// - uses system.alloc() to allocate memory
|
||||
///
|
||||
/// - returns null if not enough memory
|
||||
///
|
||||
/// - window_bits is the size of the Quantum window, from 1Kb (10) to 2Mb (21).
|
||||
///
|
||||
/// - input_buffer_size is the number of bytes to use to store bitstream data.
|
||||
/// </summary>
|
||||
/// <param name="system"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="output"></param>
|
||||
/// <param name="window_bits"></param>
|
||||
/// <param name="input_buffer_size"></param>
|
||||
/// <returns></returns>
|
||||
public static qtmd_stream qtmd_init(mspack_system system, mspack_file input, mspack_file output, int window_bits, int input_buffer_size)
|
||||
{
|
||||
uint window_size = (uint)(1 << window_bits);
|
||||
int i;
|
||||
|
||||
if (system == null) return null;
|
||||
|
||||
// Quantum supports window sizes of 2^10 (1Kb) through 2^21 (2Mb)
|
||||
if (window_bits < 10 || window_bits > 21) return null;
|
||||
|
||||
// Round up input buffer size to multiple of two
|
||||
input_buffer_size = (input_buffer_size + 1) & -2;
|
||||
if (input_buffer_size < 2) return null;
|
||||
|
||||
// Allocate decompression state
|
||||
qtmd_stream qtm = new qtmd_stream();
|
||||
|
||||
// Allocate decompression window and input buffer
|
||||
qtm.window = (byte*)system.alloc((int)window_size);
|
||||
qtm.inbuf = (byte*)system.alloc((int)input_buffer_size);
|
||||
if (qtm.window == null || qtm.inbuf == null)
|
||||
{
|
||||
system.free(qtm.window);
|
||||
system.free(qtm.inbuf);
|
||||
//system.free(qtm);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Initialise decompression state
|
||||
qtm.sys = system;
|
||||
qtm.input = input;
|
||||
qtm.output = output;
|
||||
qtm.inbuf_size = (uint)input_buffer_size;
|
||||
qtm.window_size = window_size;
|
||||
qtm.window_posn = 0;
|
||||
qtm.frame_todo = QTM_FRAME_SIZE;
|
||||
qtm.header_read = 0;
|
||||
qtm.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
qtm.i_ptr = qtm.i_end = &qtm.inbuf[0];
|
||||
qtm.o_ptr = qtm.o_end = &qtm.window[0];
|
||||
qtm.input_end = 0;
|
||||
qtm.bits_left = 0;
|
||||
qtm.bit_buffer = 0;
|
||||
|
||||
// Initialise arithmetic coding models
|
||||
// - model 4 depends on window size, ranges from 20 to 24
|
||||
// - model 5 depends on window size, ranges from 20 to 36
|
||||
// - model 6pos depends on window size, ranges from 20 to 42
|
||||
i = window_bits * 2;
|
||||
qtm.model0 = new qtmd_model(qtm.m0sym, 0, 64);
|
||||
qtm.model1 = new qtmd_model(qtm.m1sym, 64, 64);
|
||||
qtm.model2 = new qtmd_model(qtm.m2sym, 128, 64);
|
||||
qtm.model3 = new qtmd_model(qtm.m3sym, 192, 64);
|
||||
qtm.model4 = new qtmd_model(qtm.m4sym, 0, (i > 24) ? 24 : i);
|
||||
qtm.model5 = new qtmd_model(qtm.m5sym, 0, (i > 36) ? 36 : i);
|
||||
qtm.model6 = new qtmd_model(qtm.m6sym, 0, i);
|
||||
qtm.model6len = new qtmd_model(qtm.m6lsym, 0, 27);
|
||||
qtm.model7 = new qtmd_model(qtm.m7sym, 0, 7);
|
||||
|
||||
// All ok
|
||||
return qtm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses, or decompresses more of, a Quantum stream.
|
||||
///
|
||||
/// - out_bytes of data will be decompressed and the function will return
|
||||
/// with an MSPACK_ERR_OK return code.
|
||||
///
|
||||
/// - decompressing will stop as soon as out_bytes is reached. if the true
|
||||
/// amount of bytes decoded spills over that amount, they will be kept for
|
||||
/// a later invocation of qtmd_decompress().
|
||||
///
|
||||
/// - the output bytes will be passed to the system.write() function given in
|
||||
/// qtmd_init(), using the output file handle given in qtmd_init(). More
|
||||
/// than one call may be made to system.write()
|
||||
///
|
||||
/// - Quantum will read input bytes as necessary using the system.read()
|
||||
/// function given in qtmd_init(), using the input file handle given in
|
||||
/// qtmd_init(). This will continue until system.read() returns 0 bytes,
|
||||
/// or an error.
|
||||
/// </summary>
|
||||
/// <param name="qtm"></param>
|
||||
/// <param name="out_bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static MSPACK_ERR qtmd_decompress(qtmd_stream qtm, long out_bytes) => MSPACK_ERR.MSPACK_ERR_OK;
|
||||
|
||||
/// <summary>
|
||||
/// Frees all state associated with a Quantum data stream
|
||||
///
|
||||
/// - calls system.free() using the system pointer given in qtmd_init()
|
||||
/// </summary>
|
||||
/// <param name="qtm"></param>
|
||||
public static void qtmd_free(qtmd_stream qtm)
|
||||
{
|
||||
mspack_system sys;
|
||||
if (qtm != null)
|
||||
{
|
||||
sys = qtm.sys;
|
||||
//sys.free(qtm.window);
|
||||
//sys.free(qtm.inbuf);
|
||||
//sys.free(qtm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class qtmd_model
|
||||
{
|
||||
public int shiftsleft { get; set; }
|
||||
|
||||
public int entries { get; set; }
|
||||
|
||||
public qtmd_modelsym[] syms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a model to decode symbols from [start] to [start]+[len]-1
|
||||
/// </summary>
|
||||
public qtmd_model(qtmd_modelsym[] syms, int start, int len)
|
||||
{
|
||||
this.shiftsleft = 4;
|
||||
this.entries = len;
|
||||
this.syms = syms;
|
||||
|
||||
for (int i = 0; i <= len; i++)
|
||||
{
|
||||
syms[i].sym = (ushort)(start + i); // Actual symbol
|
||||
syms[i].cumfreq = (ushort)(len - i); // Current frequency of that symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public class qtmd_modelsym
|
||||
{
|
||||
public ushort sym { get; set; }
|
||||
|
||||
public ushort cumfreq { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe class qtmd_stream : readbits
|
||||
{
|
||||
/// <summary>
|
||||
/// Decoding window
|
||||
/// </summary>
|
||||
public byte* window { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Window size
|
||||
/// </summary>
|
||||
public uint window_size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bytes remaining for current frame
|
||||
/// </summary>
|
||||
public uint frame_todo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// High arith coding state
|
||||
/// </summary>
|
||||
public ushort H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Low arith coding state
|
||||
/// </summary>
|
||||
public ushort L { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current arith coding state
|
||||
/// </summary>
|
||||
public ushort C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Have we started decoding a new frame?
|
||||
/// </summary>
|
||||
public byte header_read { get; set; }
|
||||
|
||||
#region Models
|
||||
|
||||
#region Four literal models, each representing 64 symbols
|
||||
|
||||
/// <summary>
|
||||
/// model0 for literals from 0 to 63 (selector = 0)
|
||||
/// </summary>
|
||||
public qtmd_model model0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// model1 for literals from 64 to 127 (selector = 1)
|
||||
/// </summary>
|
||||
public qtmd_model model1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// model2 for literals from 128 to 191 (selector = 2)
|
||||
/// </summary>
|
||||
public qtmd_model model2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// model3 for literals from 129 to 255 (selector = 3)
|
||||
/// </summary>
|
||||
public qtmd_model model3 { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Three match models
|
||||
|
||||
/// <summary>
|
||||
/// model4 for match with fixed length of 3 bytes
|
||||
/// </summary>
|
||||
public qtmd_model model4 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// model5 for match with fixed length of 4 bytes
|
||||
/// </summary>
|
||||
public qtmd_model model5 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// model6 for variable length match, encoded with model6len model
|
||||
/// </summary>
|
||||
public qtmd_model model6 { get; set; }
|
||||
|
||||
public qtmd_model model6len { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6)
|
||||
/// </summary>
|
||||
public qtmd_model model7 { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Symbol arrays for all models
|
||||
|
||||
public qtmd_modelsym[] m0sym { get; set; } = new qtmd_modelsym[64 + 1];
|
||||
|
||||
public qtmd_modelsym[] m1sym { get; set; } = new qtmd_modelsym[64 + 1];
|
||||
|
||||
public qtmd_modelsym[] m2sym { get; set; } = new qtmd_modelsym[64 + 1];
|
||||
|
||||
public qtmd_modelsym[] m3sym { get; set; } = new qtmd_modelsym[64 + 1];
|
||||
|
||||
public qtmd_modelsym[] m4sym { get; set; } = new qtmd_modelsym[24 + 1];
|
||||
|
||||
public qtmd_modelsym[] m5sym { get; set; } = new qtmd_modelsym[36 + 1];
|
||||
|
||||
public qtmd_modelsym[] m6sym { get; set; } = new qtmd_modelsym[42 + 1];
|
||||
|
||||
public qtmd_modelsym[] m6lsym { get; set; } = new qtmd_modelsym[27 + 1];
|
||||
|
||||
public qtmd_modelsym[] m7sym { get; set; } = new qtmd_modelsym[7 + 1];
|
||||
|
||||
#endregion
|
||||
|
||||
public override void READ_BYTES()
|
||||
{
|
||||
byte b0, b1;
|
||||
READ_IF_NEEDED;
|
||||
b0 = *i_ptr++;
|
||||
READ_IF_NEEDED;
|
||||
b1 = *i_ptr++;
|
||||
INJECT_BITS_MSB((b0 << 8) | b1, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack.SZDD
|
||||
{
|
||||
/// <summary>
|
||||
/// A compressor for the SZDD file format.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public abstract class Compressor : BaseCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new SZDD compressor
|
||||
/// </summary>
|
||||
public Compressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an input file and creates a compressed output file in the
|
||||
/// SZDD compressed file format. The SZDD compression format is quick
|
||||
/// but gives poor compression. It is possible for the compressed output
|
||||
/// file to be larger than the input file.
|
||||
///
|
||||
/// Conventionally, SZDD compressed files have the final character in
|
||||
/// their filename replaced with an underscore, to show they are
|
||||
/// compressed. The missing character is stored in the compressed file
|
||||
/// itself. This is due to the restricted filename conventions of MS-DOS,
|
||||
/// most operating systems, such as UNIX, simply append another file
|
||||
/// extension to the existing filename. As mspack does not deal with
|
||||
/// filenames, this is left up to you. If you wish to set the missing
|
||||
/// character stored in the file header, use set_param() with the
|
||||
/// #MSSZDDC_PARAM_MISSINGCHAR parameter.
|
||||
///
|
||||
/// "Stream" compression (where the length of the input data is not
|
||||
/// known) is not possible. The length of the input data is stored in the
|
||||
/// header of the SZDD file and must therefore be known before any data
|
||||
/// is compressed. Due to technical limitations of the file format, the
|
||||
/// maximum size of uncompressed file that will be accepted is 2147483647
|
||||
/// bytes.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The name of the file to compressed. This is passed
|
||||
/// passed directly to mspack_system::open()
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The name of the file to write compressed data to.
|
||||
/// This is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// The length of the uncompressed file, or -1 to indicate
|
||||
/// that this should be determined automatically by using
|
||||
/// mspack_system::seek() on the input file.
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
/// <see cref="set_param(int, int)"/>
|
||||
public abstract MSPACK_ERR compress(in string input, in string output, long length);
|
||||
|
||||
/// <summary>
|
||||
/// Sets an SZDD compression engine parameter.
|
||||
///
|
||||
/// The following parameters are defined:
|
||||
///
|
||||
/// - #MSSZDDC_PARAM_CHARACTER: the "missing character", the last character
|
||||
/// in the uncompressed file's filename, which is traditionally replaced
|
||||
/// with an underscore to show the file is compressed. Traditionally,
|
||||
/// this can only be a character that is a valid part of an MS-DOS,
|
||||
/// filename, but libmspack permits any character between 0x00 and 0xFF
|
||||
/// to be stored. 0x00 is the default, and it represents "no character
|
||||
/// stored".
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter to set</param>
|
||||
/// <param name="value">The value to set the parameter to</param>
|
||||
/// <returns>
|
||||
/// MSPACK_ERR_OK if all is OK, or MSPACK_ERR_ARGS if there
|
||||
/// is a problem with either parameter or value.
|
||||
/// </returns>
|
||||
/// <see cref="compress(in string, in string, long)"/>
|
||||
public abstract MSPACK_ERR set_param(MSSZDDC_PARAM param, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="compress(in string, in string, long)"/>
|
||||
public abstract MSPACK_ERR last_error();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/* This file is part of libmspack.
|
||||
* (C) 2003-2004 Stuart Caie.
|
||||
*
|
||||
* libmspack is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License (LGPL) version 2.1
|
||||
*
|
||||
* For further details, see the file COPYING.LIB distributed with libmspack
|
||||
*/
|
||||
|
||||
namespace SabreTools.Compression.libmspack.SZDD
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Input buffer size during decompression - not worth parameterising IMHO
|
||||
/// </summary>
|
||||
public const int SZDD_INPUT_SIZE = 2048;
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
using static SabreTools.Compression.libmspack.macros;
|
||||
using static SabreTools.Compression.libmspack.SZDD.Constants;
|
||||
|
||||
namespace SabreTools.Compression.libmspack.SZDD
|
||||
{
|
||||
/// <summary>
|
||||
/// A decompressor for SZDD compressed files.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
/// <see cref="mspack.mspack_create_szdd_decompressor()"/>
|
||||
/// <see cref="mspack.mspack_destroy_szdd_decompressor(Decompressor)"/>
|
||||
public unsafe class Decompressor : BaseDecompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new SZDD decompressor
|
||||
/// </summary>
|
||||
public Decompressor()
|
||||
{
|
||||
this.system = new mspack_default_system();
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys an existing SZDD decompressor
|
||||
/// </summary>
|
||||
~Decompressor()
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
//sys.free(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a SZDD file and reads the header.
|
||||
///
|
||||
/// If the file opened is a valid SZDD file, all headers will be read and
|
||||
/// a msszddd_header structure will be returned.
|
||||
///
|
||||
/// In the case of an error occuring, null is returned and the error code
|
||||
/// is available from last_error().
|
||||
///
|
||||
/// The filename pointer should be considered "in use" until close() is
|
||||
/// called on the SZDD file.
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// The filename of the SZDD compressed file. This is
|
||||
/// passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>A pointer to a msszddd_header structure, or null on failure</returns>
|
||||
/// <see cref="close(msszddd_header)"/>
|
||||
public msszddd_header open(in string filename)
|
||||
{
|
||||
mspack_system sys = this.system;
|
||||
|
||||
mspack_file fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
|
||||
msszddd_header hdr = new msszddd_header();
|
||||
hdr.fh = fh;
|
||||
this.error = ReadHeaders(sys, fh, hdr);
|
||||
|
||||
if (this.error != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
{
|
||||
if (fh != null) sys.close(fh);
|
||||
//sys.free(hdr);
|
||||
hdr = null;
|
||||
}
|
||||
|
||||
return hdr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a previously opened SZDD file.
|
||||
///
|
||||
/// This closes a SZDD file and frees the msszddd_header associated with
|
||||
/// it.
|
||||
///
|
||||
/// The SZDD header pointer is now invalid and cannot be used again.
|
||||
/// </summary>
|
||||
/// <param name="szdd">The SZDD file to close</param>
|
||||
public void close(msszddd_header szdd)
|
||||
{
|
||||
if (this.system == null) return;
|
||||
|
||||
// Close the file handle associated
|
||||
this.system.close(szdd.fh);
|
||||
|
||||
// Free the memory associated
|
||||
//this.system.free(hdr);
|
||||
|
||||
this.error = MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
private static readonly byte[] szdd_signature_expand = new byte[8]
|
||||
{
|
||||
0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33
|
||||
};
|
||||
|
||||
private static readonly byte[] szdd_signature_qbasic = new byte[8]
|
||||
{
|
||||
0x53, 0x5A, 0x20, 0x88, 0xF0, 0x27, 0x33, 0xD1
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reads the headers of an SZDD format file
|
||||
/// </summary>
|
||||
/// <param name="sys"></param>
|
||||
/// <param name="fh"></param>
|
||||
/// <param name="hdr"></param>
|
||||
/// <returns></returns>
|
||||
private static MSPACK_ERR ReadHeaders(mspack_system sys, mspack_file fh, msszddd_header hdr)
|
||||
{
|
||||
FixedArray<byte> buf = new FixedArray<byte>(8);
|
||||
|
||||
// Read and check signature
|
||||
if (sys.read(fh, buf, 8) != 8) return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
if (buf.SequenceEqual(szdd_signature_expand))
|
||||
{
|
||||
// Common SZDD
|
||||
hdr.format = MSSZDD_FMT.MSSZDD_FMT_NORMAL;
|
||||
|
||||
// Read the rest of the header
|
||||
if (sys.read(fh, buf, 6) != 6) return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
if (buf[0] != 0x41) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
|
||||
hdr.missing_char = (char)buf[1];
|
||||
hdr.length = EndGetI32(buf, 2);
|
||||
}
|
||||
else if (buf.SequenceEqual(szdd_signature_qbasic))
|
||||
{
|
||||
// Special QBasic SZDD
|
||||
hdr.format = MSSZDD_FMT.MSSZDD_FMT_QBASIC;
|
||||
if (sys.read(fh, buf, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ;
|
||||
hdr.missing_char = '\0';
|
||||
hdr.length = EndGetI32(buf, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SIGNATURE;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the compressed data from a SZDD file.
|
||||
///
|
||||
/// This decompresses the compressed SZDD data stream and writes it to
|
||||
/// an output file.
|
||||
/// </summary>
|
||||
/// <param name="szdd">The SZDD file to extract data from</param>
|
||||
/// <param name="filename">
|
||||
/// The filename to write the decompressed data to. This
|
||||
/// is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR extract(msszddd_header szdd, in string filename)
|
||||
{
|
||||
if (szdd == null) return this.error = MSPACK_ERR.MSPACK_ERR_ARGS;
|
||||
mspack_system sys = this.system;
|
||||
|
||||
mspack_file fh = szdd.fh;
|
||||
|
||||
// Seek to the compressed data
|
||||
long data_offset = (szdd.format == MSSZDD_FMT.MSSZDD_FMT_NORMAL) ? 14 : 12;
|
||||
if (sys.seek(fh, data_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Open file for output
|
||||
mspack_file outfh;
|
||||
if ((outfh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE)) == null)
|
||||
{
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
}
|
||||
|
||||
// Decompress the data
|
||||
this.error = lzss_decompress(sys, fh, outfh, SZDD_INPUT_SIZE,
|
||||
szdd.format == MSSZDD_FMT.MSSZDD_FMT_NORMAL
|
||||
? LZSS_MODE.LZSS_MODE_EXPAND
|
||||
: LZSS_MODE.LZSS_MODE_QBASIC);
|
||||
|
||||
// Close output file
|
||||
sys.close(outfh);
|
||||
|
||||
return this.error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses an SZDD file to an output file in one step.
|
||||
///
|
||||
/// This opens an SZDD file as input, reads the header, then decompresses
|
||||
/// the compressed data immediately to an output file, finally closing
|
||||
/// both the input and output file. It is more convenient to use than
|
||||
/// open() then extract() then close(), if you do not need to know the
|
||||
/// SZDD output size or missing character.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The filename of the input SZDD file. This is passed
|
||||
/// directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// The filename to write the decompressed data to. This
|
||||
/// is passed directly to mspack_system::open().
|
||||
/// </param>
|
||||
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
|
||||
public MSPACK_ERR decompress(in string input, in string output)
|
||||
{
|
||||
msszddd_header hdr;
|
||||
|
||||
if ((hdr = open(input)) == null) return this.error;
|
||||
MSPACK_ERR error = extract(hdr, output);
|
||||
close(hdr);
|
||||
return this.error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error code set by the most recently called method.
|
||||
///
|
||||
/// This is useful for open() which does not return an
|
||||
/// error code directly.
|
||||
/// </summary>
|
||||
/// <returns>The most recent error code</returns>
|
||||
/// <see cref="open(in string)"/>
|
||||
/// <see cref="extract(msszddd_header, in string)"/>
|
||||
/// <see cref="decompress(in string, in string)"/>
|
||||
public MSPACK_ERR last_error()
|
||||
{
|
||||
return this.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents an SZDD compressed file.
|
||||
///
|
||||
/// All fields are READ ONLY.
|
||||
/// </summary>
|
||||
public class msszddd_header
|
||||
{
|
||||
public mspack_file fh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file format
|
||||
/// </summary>
|
||||
public MSSZDD_FMT format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of data in the SZDD file once uncompressed.
|
||||
/// </summary>
|
||||
public long length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last character in the filename, traditionally replaced with an
|
||||
/// underscore to show the file is compressed. The null character is used
|
||||
/// to show that this character has not been stored (e.g. because the
|
||||
/// filename is not known). Generally, only characters that may appear in
|
||||
/// an MS-DOS filename (except ".") are valid.
|
||||
/// </summary>
|
||||
public char missing_char { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
|
||||
* code or tables extracted from it, as desired without restriction.
|
||||
*
|
||||
* First, the polynomial itself and its table of feedback terms. The
|
||||
* polynomial is
|
||||
* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
|
||||
*
|
||||
* Note that we take it "backwards" and put the highest-order term in
|
||||
* the lowest-order bit. The X^32 term is "implied"; the LSB is the
|
||||
* X^31 term, etc. The X^0 term (usually shown as "+1") results in
|
||||
* the MSB being 1
|
||||
*
|
||||
* Note that the usual hardware shift register implementation, which
|
||||
* is what we're using (we're merely optimizing it by doing eight-bit
|
||||
* chunks at a time) shifts bits into the lowest-order term. In our
|
||||
* implementation, that means shifting towards the right. Why do we
|
||||
* do it this way? Because the calculated CRC must be transmitted in
|
||||
* order from highest-order term to lowest-order term. UARTs transmit
|
||||
* characters in order from LSB to MSB. By storing the CRC this way
|
||||
* we hand it to the UART in the order low-byte to high-byte; the UART
|
||||
* sends each low-bit to hight-bit; and the result is transmission bit
|
||||
* by bit from highest- to lowest-order term without requiring any bit
|
||||
* shuffling on our part. Reception works similarly
|
||||
*
|
||||
* The feedback terms table consists of 256, 32-bit entries. Notes
|
||||
*
|
||||
* The table can be generated at runtime if desired; code to do so
|
||||
* is shown later. It might not be obvious, but the feedback
|
||||
* terms simply represent the results of eight shift/xor opera
|
||||
* tions for all combinations of data and CRC register values
|
||||
*
|
||||
* The values must be right-shifted by eight bits by the "updcrc
|
||||
* logic; the shift must be unsigned (bring in zeroes). On some
|
||||
* hardware you could probably optimize the shift in assembler by
|
||||
* using byte-swap instructions
|
||||
* polynomial $edb88320
|
||||
*/
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe static partial class mspack
|
||||
{
|
||||
private static readonly uint[] crc32_table = new uint[256]
|
||||
{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
|
||||
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
|
||||
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
|
||||
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
|
||||
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
||||
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
|
||||
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
|
||||
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
|
||||
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
|
||||
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
|
||||
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
|
||||
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
|
||||
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
|
||||
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
|
||||
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
|
||||
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
||||
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
|
||||
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
|
||||
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
|
||||
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
|
||||
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
||||
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
|
||||
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
|
||||
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
|
||||
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
|
||||
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
|
||||
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
||||
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
|
||||
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
|
||||
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
||||
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
|
||||
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
|
||||
0x2d02ef8d
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Return a 32-bit CRC of the contents of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
/// <param name="ss"></param>
|
||||
/// <param name="len"></param>
|
||||
/// <returns></returns>
|
||||
public static uint crc32(uint val, void* ss, int len)
|
||||
{
|
||||
byte* s = (byte*)ss;
|
||||
while (--len >= 0)
|
||||
{
|
||||
val = crc32_table[(val ^ *s++) & 0xff] ^ (val >> 8);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public static class macros
|
||||
{
|
||||
private static uint __egi32(FixedArray<byte> a, int n)
|
||||
{
|
||||
return (uint)((a[n + 3] << 24) | (a[n + 2] << 16) | (a[n + 1] << 8) | (a[n + 0]));
|
||||
}
|
||||
|
||||
public static ulong EndGetI64(FixedArray<byte> a, int n)
|
||||
{
|
||||
return (__egi32(a, n + 4) << 32) | __egi32(a, n + 0);
|
||||
}
|
||||
|
||||
public static uint EndGetI32(FixedArray<byte> a, int n)
|
||||
{
|
||||
return __egi32(a, n + 0);
|
||||
}
|
||||
|
||||
public static ushort EndGetI16(FixedArray<byte> a, int n)
|
||||
{
|
||||
return (ushort)((a[n + 1] << 8) | a[n + 0]);
|
||||
}
|
||||
|
||||
public static uint EndGetM32(FixedArray<byte> a, int n)
|
||||
{
|
||||
return (uint)((a[n + 0] << 24) | (a[n + 1] << 16) | (a[n + 2] << 8) | (a[n + 3]));
|
||||
}
|
||||
|
||||
public static ushort EndGetM16(FixedArray<byte> a, int n)
|
||||
{
|
||||
return (ushort)((a[n + 0] << 8) | a[n + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public class mspack_default_system : mspack_system
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override mspack_file open(in string filename, MSPACK_SYS_OPEN mode)
|
||||
{
|
||||
FileMode fmode;
|
||||
FileAccess faccess;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ: fmode = FileMode.Open; faccess = FileAccess.Read; break;
|
||||
case MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE: fmode = FileMode.OpenOrCreate; faccess = FileAccess.Write; break;
|
||||
case MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_UPDATE: fmode = FileMode.Open; faccess = FileAccess.ReadWrite; break;
|
||||
case MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_APPEND: fmode = FileMode.Append; faccess = FileAccess.ReadWrite; break;
|
||||
default: return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fh = new mspack_file_p
|
||||
{
|
||||
name = filename,
|
||||
fh = File.Open(filename, fmode, faccess),
|
||||
};
|
||||
return fh;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void close(mspack_file file)
|
||||
{
|
||||
if (file is mspack_file_p self)
|
||||
self.fh?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int read(mspack_file file, void* buffer, int bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file is mspack_file_p self && buffer != null && bytes >= 0)
|
||||
{
|
||||
var ums = new UnmanagedMemoryStream((byte*)buffer, bytes);
|
||||
self.fh.CopyTo(ums, bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe int write(mspack_file file, void* buffer, int bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file is mspack_file_p self && buffer != null && bytes >= 0)
|
||||
{
|
||||
var ums = new UnmanagedMemoryStream((byte*)buffer, bytes);
|
||||
ums.CopyTo(self.fh, bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int seek(mspack_file file, long offset, MSPACK_SYS_SEEK mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file is mspack_file_p self)
|
||||
{
|
||||
SeekOrigin origin;
|
||||
switch (mode)
|
||||
{
|
||||
case MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START: origin = SeekOrigin.Begin; break;
|
||||
case MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_CUR: origin = SeekOrigin.Current; break;
|
||||
case MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_END: origin = SeekOrigin.End; break;
|
||||
default: return -1;
|
||||
}
|
||||
|
||||
self.fh.Seek(offset, origin);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long tell(mspack_file file)
|
||||
{
|
||||
var self = file as mspack_file_p;
|
||||
return self != null ? self.fh.Position : 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void message(mspack_file file, in string format, params string[] args)
|
||||
{
|
||||
if (file != null) Console.Write((file as mspack_file_p)?.name);
|
||||
Console.Write(format, args);
|
||||
Console.Write("\n");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void* alloc(int bytes)
|
||||
{
|
||||
return (void*)new FixedArray<byte>(bytes).Pointer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void free(void* ptr)
|
||||
{
|
||||
Marshal.FreeCoTaskMem((IntPtr)ptr);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe void copy(void* src, void* dest, int bytes)
|
||||
{
|
||||
byte[] temp = new byte[bytes];
|
||||
Marshal.Copy((IntPtr)src, temp, 0, bytes);
|
||||
Marshal.Copy(temp, 0, (IntPtr)dest, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which represents an open file handle. The contents of this
|
||||
/// structure are determined by the implementation of the
|
||||
/// mspack_system::open() method.
|
||||
/// </summary>
|
||||
public unsafe abstract class mspack_file { }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public class mspack_file_p : mspack_file
|
||||
{
|
||||
public Stream fh { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure which abstracts file I/O and memory management.
|
||||
///
|
||||
/// The library always uses the mspack_system structure for interaction
|
||||
/// with the file system and to allocate, free and copy all memory. It also
|
||||
/// uses it to send literal messages to the library user.
|
||||
///
|
||||
/// When the library is compiled normally, passing null to a compressor or
|
||||
/// decompressor constructor will result in a default mspack_system being
|
||||
/// used, where all methods are implemented with the standard C library.
|
||||
///
|
||||
/// However, all constructors support being given a custom created
|
||||
/// mspack_system structure, with the library user's own methods. This
|
||||
/// allows for more abstract interaction, such as reading and writing files
|
||||
/// directly to memory, or from a network socket or pipe.
|
||||
///
|
||||
/// Implementors of an mspack_system structure should read all
|
||||
/// documentation entries for every structure member, and write methods
|
||||
/// which conform to those standards.
|
||||
/// </summary>
|
||||
public unsafe abstract class mspack_system
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens a file for reading, writing, appending or updating
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// The file to be opened. It is passed directly from the
|
||||
/// library caller without being modified, so it is up to
|
||||
/// the caller what this parameter actually represents.
|
||||
/// </param>
|
||||
/// <param name="mode">One of <see cref="MSPACK_SYS_OPEN"/> values</param>
|
||||
/// <returns>
|
||||
/// A pointer to a mspack_file structure. This structure officially
|
||||
/// contains no members, its true contents are up to the
|
||||
/// mspack_system implementor. It should contain whatever is needed
|
||||
/// for other mspack_system methods to operate. Returning the null
|
||||
/// pointer indicates an error condition.
|
||||
/// </returns>
|
||||
/// <see cref="close(mspack_file)"/>
|
||||
/// <see cref="read(mspack_file, void*, int)"/>
|
||||
/// <see cref="write(mspack_file, void*, int)"/>
|
||||
/// <see cref="seek(mspack_file, int, MSPACK_SYS_SEEK)"/>
|
||||
/// <see cref="tell(mspack_file)"/>
|
||||
/// <see cref="message(mspack_file, in string, string[])"/>
|
||||
public abstract mspack_file open(in string filename, MSPACK_SYS_OPEN mode);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a previously opened file. If any memory was allocated for this
|
||||
/// particular file handle, it should be freed at this time.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to close</param>
|
||||
/// <see cref="open(in string, MSPACK_SYS_OPEN)"/>
|
||||
public abstract void close(mspack_file file);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a given number of bytes from an open file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to read from</param>
|
||||
/// <param name="buffer">The location where the read bytes should be stored</param>
|
||||
/// <param name="bytes">The number of bytes to read from the file</param>
|
||||
/// <returns>
|
||||
/// The number of bytes successfully read (this can be less than
|
||||
/// the number requested), zero to mark the end of file, or less
|
||||
/// than zero to indicate an error. The library does not "retry"
|
||||
/// reads and assumes short reads are due to EOF, so you should
|
||||
/// avoid returning short reads because of transient errors.
|
||||
/// </returns>
|
||||
/// <see cref="open(in string, MSPACK_SYS_OPEN)"/>
|
||||
/// <see cref="write(mspack_file, void*, int)"/>
|
||||
public abstract int read(mspack_file file, void* buffer, int bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a given number of bytes to an open file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to write to</param>
|
||||
/// <param name="buffer">The location where the written bytes should be read from</param>
|
||||
/// <param name="bytes">The number of bytes to write to the file</param>
|
||||
/// <returns>
|
||||
/// The number of bytes successfully written, this can be less
|
||||
/// than the number requested. Zero or less can indicate an error
|
||||
/// where no bytes at all could be written. All cases where less
|
||||
/// bytes were written than requested are considered by the library
|
||||
/// to be an error.
|
||||
/// </returns>
|
||||
/// <see cref="open(in string, MSPACK_SYS_OPEN)"/>
|
||||
/// <see cref="read(mspack_file, void*, int)"/>
|
||||
public abstract int write(mspack_file file, void* buffer, int bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Seeks to a specific file offset within an open file.
|
||||
///
|
||||
/// Sometimes the library needs to know the length of a file. It does
|
||||
/// this by seeking to the end of the file with seek(file, 0,
|
||||
/// MSPACK_SYS_SEEK_END), then calling tell(). Implementations may want
|
||||
/// to make a special case for this.
|
||||
///
|
||||
/// Due to the potentially varying 32/64 bit datatype off_t on some
|
||||
/// architectures, the #MSPACK_SYS_SELFTEST macro MUST be used before
|
||||
/// using the library. If not, the error caused by the library passing an
|
||||
/// inappropriate stackframe to seek() is subtle and hard to trace.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to be seeked</param>
|
||||
/// <param name="offset">An offset to seek, measured in bytes</param>
|
||||
/// <param name="mode">One of <see cref="MSPACK_SYS_SEEK"/> values</param>
|
||||
/// <returns>Zero for success, non-zero for an error</returns>
|
||||
/// <see cref="open(in string, MSPACK_SYS_OPEN)"/>
|
||||
/// <see cref="tell(mspack_file)"/>
|
||||
public abstract int seek(mspack_file file, long offset, MSPACK_SYS_SEEK mode);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current file position (in bytes) of the given file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file whose file position is wanted</param>
|
||||
/// <returns>The current file position of the file</returns>
|
||||
/// <see cref="open(in string, MSPACK_SYS_OPEN)"/>
|
||||
/// <see cref="seek(mspack_file, int, MSPACK_SYS_SEEK)"/>
|
||||
public abstract long tell(mspack_file file);
|
||||
|
||||
/// <summary>
|
||||
/// Used to send messages from the library to the user.
|
||||
///
|
||||
/// Occasionally, the library generates warnings or other messages in
|
||||
/// plain english to inform the human user. These are informational only
|
||||
/// and can be ignored if not wanted.
|
||||
/// </summary>
|
||||
/// <param name="file">
|
||||
/// May be a file handle returned from open() if this message
|
||||
/// pertains to a specific open file, or null if not related to
|
||||
/// a specific file.
|
||||
/// </param>
|
||||
/// <param name="format">
|
||||
/// a printf() style format string. It does NOT include a
|
||||
/// trailing newline.
|
||||
/// </param>
|
||||
public abstract void message(mspack_file file, in string format, params string[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory
|
||||
/// </summary>
|
||||
/// <param name="bytes">The number of bytes to allocate</param>
|
||||
/// <returns>
|
||||
/// A pointer to the requested number of bytes, or null if
|
||||
/// not enough memory is available
|
||||
/// </returns>
|
||||
/// <see cref="free(void*)"/>
|
||||
public abstract void* alloc(int bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Frees memory
|
||||
/// </summary>
|
||||
/// <param name="ptr">The memory to be freed. null is accepted and ignored.</param>
|
||||
/// <see cref="alloc(int)"/>
|
||||
public abstract void free(void* ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Copies from one region of memory to another.
|
||||
///
|
||||
/// The regions of memory are guaranteed not to overlap, are usually less
|
||||
/// than 256 bytes, and may not be aligned. Please note that the source
|
||||
/// parameter comes before the destination parameter, unlike the standard
|
||||
/// C function memcpy().
|
||||
/// </summary>
|
||||
/// <param name="src">The region of memory to copy from</param>
|
||||
/// <param name="dest">The region of memory to copy to</param>
|
||||
/// <param name="bytes">The size of the memory region, in bytes</param>
|
||||
public abstract void copy(void* src, void* dest, int bytes);
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe abstract class readbits
|
||||
{
|
||||
/// <summary>
|
||||
/// I/O routines
|
||||
/// </summary>
|
||||
public mspack_system sys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input file handle
|
||||
/// </summary>
|
||||
public mspack_file input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output file handle
|
||||
/// </summary>
|
||||
public mspack_file output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decompression offset within window
|
||||
/// </summary>
|
||||
public uint window_posn { get; set; }
|
||||
|
||||
#region I/O buffering
|
||||
|
||||
public byte* inbuf { get; set; }
|
||||
|
||||
public byte* i_ptr { get; set; }
|
||||
|
||||
public byte* i_end { get; set; }
|
||||
|
||||
public byte* o_ptr { get; set; }
|
||||
|
||||
public byte* o_end { get; set; }
|
||||
|
||||
public int input_end { get; set; }
|
||||
|
||||
public uint bit_buffer { get; set; }
|
||||
|
||||
public uint bits_left { get; set; }
|
||||
|
||||
public uint inbuf_size { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public MSPACK_ERR error { get; set; }
|
||||
|
||||
/// <see href="https://github.com/kyz/libmspack/blob/master/libmspack/mspack/readbits.h"/>
|
||||
#region readbits.h
|
||||
|
||||
private const int BITBUF_WIDTH = 64;
|
||||
|
||||
private static readonly ushort[] lsb_bit_mask = new ushort[17]
|
||||
{
|
||||
0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
|
||||
0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
|
||||
};
|
||||
|
||||
public void INIT_BITS()
|
||||
{
|
||||
this.i_ptr = inbuf;
|
||||
this.i_end = inbuf;
|
||||
this.bit_buffer = 0;
|
||||
this.bits_left = 0;
|
||||
this.input_end = 0;
|
||||
}
|
||||
|
||||
public void STORE_BITS(byte* i_ptr, byte* i_end, uint bit_buffer, uint bits_left)
|
||||
{
|
||||
this.i_ptr = i_ptr;
|
||||
this.i_end = i_end;
|
||||
this.bit_buffer = bit_buffer;
|
||||
this.bits_left = bits_left;
|
||||
}
|
||||
|
||||
public void RESTORE_BITS(out byte* i_ptr, out byte* i_end, out uint bit_buffer, out uint bits_left)
|
||||
{
|
||||
i_ptr = this.i_ptr;
|
||||
i_end = this.i_end;
|
||||
bit_buffer = this.bit_buffer;
|
||||
bits_left = this.bits_left;
|
||||
}
|
||||
|
||||
public void ENSURE_BITS(byte nbits, ref uint bits_left)
|
||||
{
|
||||
while (bits_left < nbits)
|
||||
{
|
||||
this.READ_BYTES();
|
||||
}
|
||||
}
|
||||
|
||||
#region MSB
|
||||
|
||||
public void READ_BITS_MSB(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
this.ENSURE_BITS(nbits, ref bits_left);
|
||||
val = PEEK_BITS_MSB(nbits, bit_buffer);
|
||||
REMOVE_BITS_MSB(nbits, ref bit_buffer, ref bits_left);
|
||||
}
|
||||
|
||||
public void READ_MANY_BITS_MSB(out int val, byte bits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
byte needed = bits;
|
||||
byte bitrun;
|
||||
val = 0;
|
||||
while (needed > 0)
|
||||
{
|
||||
if (bits_left < (int)(BITBUF_WIDTH - 16))
|
||||
this.READ_BYTES();
|
||||
|
||||
bitrun = (bits_left < needed) ? (byte)bits_left : needed;
|
||||
val = (val << bitrun) | PEEK_BITS_MSB(bitrun, bit_buffer);
|
||||
REMOVE_BITS_MSB(bitrun, ref bit_buffer, ref bits_left);
|
||||
needed -= bitrun;
|
||||
}
|
||||
}
|
||||
|
||||
public int PEEK_BITS_MSB(byte nbits, uint bit_buffer)
|
||||
{
|
||||
return (int)(bit_buffer >> (BITBUF_WIDTH - nbits));
|
||||
}
|
||||
|
||||
public void REMOVE_BITS_MSB(byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
bit_buffer <<= nbits;
|
||||
bits_left -= nbits;
|
||||
}
|
||||
|
||||
public void INJECT_BITS_MSB(uint bitdata, byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
bit_buffer |= (uint)(int)(bitdata << (int)(BITBUF_WIDTH - nbits - bits_left));
|
||||
bits_left += nbits;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LSB
|
||||
|
||||
public void READ_BITS_LSB(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
this.ENSURE_BITS(nbits, ref bits_left);
|
||||
val = PEEK_BITS_LSB(nbits, bit_buffer);
|
||||
REMOVE_BITS_LSB(nbits, ref bit_buffer, ref bits_left);
|
||||
}
|
||||
|
||||
public void READ_MANY_BITS_LSB(out int val, byte bits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
byte needed = bits;
|
||||
byte bitrun;
|
||||
val = 0;
|
||||
while (needed > 0)
|
||||
{
|
||||
if (bits_left < (int)(BITBUF_WIDTH - 16))
|
||||
this.READ_BYTES();
|
||||
|
||||
bitrun = (bits_left < needed) ? (byte)bits_left : needed;
|
||||
val = (val << bitrun) | PEEK_BITS_LSB(bitrun, bit_buffer);
|
||||
REMOVE_BITS_LSB(bitrun, ref bit_buffer, ref bits_left);
|
||||
needed -= bitrun;
|
||||
}
|
||||
}
|
||||
|
||||
public int PEEK_BITS_LSB(byte nbits, uint bit_buffer)
|
||||
{
|
||||
return (int)(bit_buffer & ((uint)(1 << nbits) - 1));
|
||||
}
|
||||
|
||||
public void REMOVE_BITS_LSB(byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
bit_buffer >>= nbits;
|
||||
bits_left -= nbits;
|
||||
}
|
||||
|
||||
public void INJECT_BITS_LSB(uint bitdata, byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
bit_buffer |= bitdata << (int)bits_left;
|
||||
bits_left += nbits;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LSB_T
|
||||
|
||||
public int PEEK_BITS_LSB_T(byte nbits, uint bit_buffer)
|
||||
{
|
||||
return (int)(bit_buffer & lsb_bit_mask[nbits]);
|
||||
}
|
||||
|
||||
public void READ_BITS_LSB_T(out int val, byte nbits, ref uint bit_buffer, ref uint bits_left)
|
||||
{
|
||||
this.ENSURE_BITS(nbits, ref bits_left);
|
||||
val = PEEK_BITS_LSB_T(nbits, bit_buffer);
|
||||
REMOVE_BITS_LSB(nbits, ref bit_buffer, ref bits_left);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract void READ_BYTES();
|
||||
|
||||
public MSPACK_ERR READ_IF_NEEDED(ref byte* i_ptr, ref byte* i_end)
|
||||
{
|
||||
if (i_ptr >= i_end)
|
||||
{
|
||||
if (ReadInput() != MSPACK_ERR.MSPACK_ERR_OK)
|
||||
return this.error;
|
||||
|
||||
i_ptr = this.i_ptr;
|
||||
i_end = this.i_end;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
private MSPACK_ERR ReadInput()
|
||||
{
|
||||
int read = this.sys.read(this.input, this.inbuf, (int)this.inbuf_size);
|
||||
if (read < 0) return this.error = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
|
||||
/* we might overrun the input stream by asking for bits we don't use,
|
||||
* so fake 2 more bytes at the end of input */
|
||||
if (read == 0)
|
||||
{
|
||||
if (this.input_end != 0)
|
||||
{
|
||||
System.Console.Error.WriteLine("Out of input bytes");
|
||||
return this.error = MSPACK_ERR.MSPACK_ERR_READ;
|
||||
}
|
||||
else
|
||||
{
|
||||
read = 2;
|
||||
this.inbuf[0] = this.inbuf[1] = 0;
|
||||
this.input_end = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update i_ptr and i_end
|
||||
this.i_ptr = this.inbuf;
|
||||
this.i_end = this.inbuf + read;
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe static class readhuff
|
||||
{
|
||||
public const int HUFF_MAXBITS = 16;
|
||||
|
||||
#region MSB
|
||||
|
||||
/// <summary>
|
||||
/// This function was originally coded by David Tritscher.
|
||||
/// It builds a fast huffman decoding table from
|
||||
/// 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 nsyms-1]</param>
|
||||
/// <param name="table">
|
||||
/// The table to fill up with decoded symbols and pointers.
|
||||
/// Should be ((1<<nbits) + (nsyms*2)) in length.
|
||||
/// </param>
|
||||
/// <returns>Returns 0 for OK or 1 for error</returns>
|
||||
public static int make_decode_table_MSB(uint nsyms, uint nbits, byte* length, ushort* table)
|
||||
{
|
||||
ushort sym, next_symbol;
|
||||
uint leaf, fill;
|
||||
byte bit_num;
|
||||
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
|
||||
|
||||
// Fill entries for codes short enough for a direct mapping
|
||||
for (bit_num = 1; bit_num <= nbits; bit_num++)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] != bit_num) continue;
|
||||
leaf = pos;
|
||||
|
||||
if ((pos += bit_mask) > table_mask) return 1; // Table overrun
|
||||
|
||||
// Fill all possible lookups of this symbol with the symbol itself
|
||||
for (fill = bit_mask; fill-- > 0;) table[leaf++] = sym;
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
}
|
||||
|
||||
// Exit with success if table is now complete
|
||||
if (pos == table_mask) return 0;
|
||||
|
||||
// Mark all remaining table entries as unused
|
||||
for (sym = (ushort)pos; sym < table_mask; sym++)
|
||||
{
|
||||
table[sym] = 0xFFFF;
|
||||
}
|
||||
|
||||
// next_symbol = base of allocation for long codes
|
||||
next_symbol = (ushort)(((table_mask >> 1) < nsyms) ? nsyms : (table_mask >> 1));
|
||||
|
||||
// Give ourselves room for codes to grow by up to 16 more bits.
|
||||
// codes now start at bit nbits+16 and end at (nbits+16-codelength)
|
||||
pos <<= 16;
|
||||
table_mask <<= 16;
|
||||
bit_mask = 1 << 15;
|
||||
|
||||
for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] != bit_num) continue;
|
||||
if (pos >= table_mask) return 1; // Table overflow
|
||||
|
||||
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] == 0xFFFF)
|
||||
{
|
||||
table[(next_symbol << 1)] = 0xFFFF;
|
||||
table[(next_symbol << 1) + 1] = 0xFFFF;
|
||||
table[leaf] = 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;
|
||||
pos += bit_mask;
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
}
|
||||
|
||||
// Full table?
|
||||
return (pos == table_mask) ? 0 : 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LSB
|
||||
|
||||
/// <summary>
|
||||
/// This function was originally coded by David Tritscher.
|
||||
/// It builds a fast huffman decoding table from
|
||||
/// 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 nsyms-1]</param>
|
||||
/// <param name="table">
|
||||
/// The table to fill up with decoded symbols and pointers.
|
||||
/// Should be ((1<<nbits) + (nsyms*2)) in length.
|
||||
/// </param>
|
||||
/// <returns>Returns 0 for OK or 1 for error</returns>
|
||||
public static int make_decode_table_LSB(uint nsyms, uint nbits, byte* length, ushort* table)
|
||||
{
|
||||
ushort sym, next_symbol;
|
||||
uint leaf, fill;
|
||||
uint reverse;
|
||||
byte bit_num;
|
||||
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
|
||||
|
||||
// Fill entries for codes short enough for a direct mapping
|
||||
for (bit_num = 1; bit_num <= nbits; bit_num++)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] != bit_num) continue;
|
||||
|
||||
// Reverse the significant bits
|
||||
fill = length[sym]; reverse = pos >> (int)(nbits - fill); leaf = 0;
|
||||
do { leaf <<= 1; leaf |= reverse & 1; reverse >>= 1; } while (--fill > 0);
|
||||
|
||||
if ((pos += bit_mask) > table_mask) return 1; // Table overrun
|
||||
|
||||
// Fill all possible lookups of this symbol with the symbol itself
|
||||
fill = bit_mask; next_symbol = (ushort)(1 << bit_num);
|
||||
do { table[leaf] = sym; leaf += next_symbol; } while (--fill > 0);
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
}
|
||||
|
||||
// Exit with success if table is now complete
|
||||
if (pos == table_mask) return 0;
|
||||
|
||||
// Mark all remaining table entries as unused
|
||||
for (sym = (ushort)pos; sym < table_mask; sym++)
|
||||
{
|
||||
reverse = sym; leaf = 0; fill = nbits;
|
||||
do { leaf <<= 1; leaf |= reverse & 1; reverse >>= 1; } while (--fill > 0);
|
||||
table[leaf] = 0xFFFF;
|
||||
}
|
||||
|
||||
// next_symbol = base of allocation for long codes
|
||||
next_symbol = (ushort)(((table_mask >> 1) < nsyms) ? nsyms : (table_mask >> 1));
|
||||
|
||||
// Give ourselves room for codes to grow by up to 16 more bits.
|
||||
// codes now start at bit nbits+16 and end at (nbits+16-codelength)
|
||||
pos <<= 16;
|
||||
table_mask <<= 16;
|
||||
bit_mask = 1 << 15;
|
||||
|
||||
for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++)
|
||||
{
|
||||
for (sym = 0; sym < nsyms; sym++)
|
||||
{
|
||||
if (length[sym] != bit_num) continue;
|
||||
if (pos >= table_mask) return 1; // Table overflow
|
||||
|
||||
// leaf = the first nbits of the code, reversed
|
||||
reverse = pos >> 16; leaf = 0; fill = nbits;
|
||||
do { leaf <<= 1; leaf |= reverse & 1; reverse >>= 1; } while (--fill > 0);
|
||||
for (fill = 0; fill < (bit_num - nbits); fill++)
|
||||
{
|
||||
// If this path hasn't been taken yet, 'allocate' two entries
|
||||
if (table[leaf] == 0xFFFF)
|
||||
{
|
||||
table[(next_symbol << 1)] = 0xFFFF;
|
||||
table[(next_symbol << 1) + 1] = 0xFFFF;
|
||||
table[leaf] = 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;
|
||||
pos += bit_mask;
|
||||
}
|
||||
bit_mask >>= 1;
|
||||
}
|
||||
|
||||
// Full table?
|
||||
return (pos == table_mask) ? 0 : 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/* This file is part of libmspack.
|
||||
* (C) 2003-2018 Stuart Caie.
|
||||
*
|
||||
* libmspack is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License (LGPL) version 2.1
|
||||
*
|
||||
* For further details, see the file COPYING.LIB distributed with libmspack
|
||||
*/
|
||||
|
||||
namespace SabreTools.Compression.libmspack
|
||||
{
|
||||
public unsafe static class system
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the length of a file opened for reading
|
||||
/// </summary>
|
||||
public static MSPACK_ERR mspack_sys_filelen(mspack_system system, mspack_file file, long* length)
|
||||
{
|
||||
if (system == null || file == null || length == null) return MSPACK_ERR.MSPACK_ERR_OPEN;
|
||||
|
||||
// Get current offset
|
||||
long current = system.tell(file);
|
||||
|
||||
// Seek to end of file
|
||||
if (system.seek(file, 0, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_END) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
// Get offset of end of file
|
||||
*length = system.tell(file);
|
||||
|
||||
// Seek back to original offset
|
||||
if (system.seek(file, current, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
|
||||
{
|
||||
return MSPACK_ERR.MSPACK_ERR_SEEK;
|
||||
}
|
||||
|
||||
return MSPACK_ERR.MSPACK_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user