mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-04-05 21:51:09 +00:00
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/3037a2f7-f243-4261-802f-e8c83b4d6722
215 lines
5.6 KiB
C#
215 lines
5.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using SharpCompress.Common;
|
|
using SharpCompress.Compressors.RLE90;
|
|
|
|
namespace SharpCompress.Compressors.ArcLzw;
|
|
|
|
public partial class ArcLzwStream : Stream
|
|
{
|
|
private Stream _stream;
|
|
private bool _processed;
|
|
private bool _useCrunched;
|
|
private int _compressedSize;
|
|
|
|
private const int BITS = 12;
|
|
private const int CRUNCH_BITS = 12;
|
|
private const int SQUASH_BITS = 13;
|
|
private const int INIT_BITS = 9;
|
|
private const ushort FIRST = 257;
|
|
private const ushort CLEAR = 256;
|
|
|
|
private ushort oldcode;
|
|
private byte finchar;
|
|
private int n_bits;
|
|
private ushort maxcode;
|
|
private ushort[] prefix = new ushort[8191];
|
|
private byte[] suffix = new byte[8191];
|
|
private bool clearFlag;
|
|
private Stack<byte> stack = new Stack<byte>();
|
|
private ushort freeEnt;
|
|
private ushort maxcodemax;
|
|
|
|
public ArcLzwStream(Stream stream, int compressedSize, bool useCrunched = true)
|
|
{
|
|
_stream = stream;
|
|
_useCrunched = useCrunched;
|
|
_compressedSize = compressedSize;
|
|
|
|
oldcode = 0;
|
|
finchar = 0;
|
|
n_bits = 0;
|
|
maxcode = 0;
|
|
clearFlag = false;
|
|
freeEnt = FIRST;
|
|
maxcodemax = 0;
|
|
}
|
|
|
|
private ushort? GetCode(BitReader reader)
|
|
{
|
|
if (clearFlag || freeEnt > maxcode)
|
|
{
|
|
if (freeEnt > maxcode)
|
|
{
|
|
n_bits++;
|
|
maxcode = (n_bits == BITS) ? maxcodemax : (ushort)((1 << n_bits) - 1);
|
|
}
|
|
if (clearFlag)
|
|
{
|
|
clearFlag = false;
|
|
n_bits = INIT_BITS;
|
|
maxcode = (ushort)((1 << n_bits) - 1);
|
|
}
|
|
}
|
|
return (ushort?)reader.ReadBits(n_bits);
|
|
}
|
|
|
|
public List<byte> Decompress(byte[] input, bool useCrunched)
|
|
{
|
|
var result = new List<byte>();
|
|
int bits = useCrunched ? CRUNCH_BITS : SQUASH_BITS;
|
|
|
|
if (useCrunched)
|
|
{
|
|
if (input.Length == 0)
|
|
{
|
|
throw new InvalidFormatException("ArcLzwStream: compressed data is empty");
|
|
}
|
|
if (input[0] != BITS)
|
|
{
|
|
throw new InvalidFormatException($"File packed with {input[0]}, expected {BITS}.");
|
|
}
|
|
|
|
input = input.Skip(1).ToArray();
|
|
}
|
|
|
|
maxcodemax = (ushort)(1 << bits);
|
|
clearFlag = false;
|
|
n_bits = INIT_BITS;
|
|
maxcode = (ushort)((1 << n_bits) - 1);
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
suffix[i] = (byte)i;
|
|
}
|
|
|
|
var reader = new BitReader(input);
|
|
freeEnt = FIRST;
|
|
|
|
if (GetCode(reader) is ushort old)
|
|
{
|
|
oldcode = old;
|
|
finchar = (byte)oldcode;
|
|
result.Add(finchar);
|
|
}
|
|
|
|
while (GetCode(reader) is ushort code)
|
|
{
|
|
if (code == CLEAR)
|
|
{
|
|
Array.Clear(prefix, 0, prefix.Length);
|
|
clearFlag = true;
|
|
freeEnt = (ushort)(FIRST - 1);
|
|
|
|
if (GetCode(reader) is ushort c)
|
|
{
|
|
code = c;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
ushort incode = code;
|
|
|
|
if (code >= freeEnt)
|
|
{
|
|
stack.Push(finchar);
|
|
code = oldcode;
|
|
}
|
|
|
|
while (code >= 256)
|
|
{
|
|
if (code >= suffix.Length)
|
|
{
|
|
throw new InvalidFormatException("ArcLzwStream: code out of range");
|
|
}
|
|
stack.Push(suffix[code]);
|
|
code = prefix[code];
|
|
}
|
|
|
|
finchar = suffix[code];
|
|
stack.Push(finchar);
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
result.Add(stack.Pop());
|
|
}
|
|
code = freeEnt;
|
|
if (code < maxcodemax)
|
|
{
|
|
prefix[code] = oldcode;
|
|
suffix[code] = finchar;
|
|
freeEnt = (ushort)(code + 1);
|
|
}
|
|
|
|
oldcode = incode;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Stream base class implementation
|
|
public override bool CanRead => true;
|
|
public override bool CanSeek => false;
|
|
public override bool CanWrite => false;
|
|
public override long Length => throw new NotImplementedException();
|
|
public override long Position
|
|
{
|
|
get => _stream.Position;
|
|
set => throw new NotImplementedException();
|
|
}
|
|
|
|
public override void Flush() => throw new NotImplementedException();
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
if (_processed)
|
|
{
|
|
return 0;
|
|
}
|
|
_processed = true;
|
|
var data = new byte[_compressedSize];
|
|
_stream.Read(data, 0, _compressedSize);
|
|
var decoded = Decompress(data, _useCrunched);
|
|
var result = decoded.Count;
|
|
if (_useCrunched)
|
|
{
|
|
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
|
unpacked.CopyTo(buffer, 0);
|
|
result = unpacked.Count;
|
|
}
|
|
else
|
|
{
|
|
decoded.CopyTo(buffer, 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) =>
|
|
throw new NotImplementedException();
|
|
|
|
public override void SetLength(long value) => throw new NotImplementedException();
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) =>
|
|
throw new NotImplementedException();
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|