Added ARC's crunched methods 5, 6, 7 & 8

This commit is contained in:
Twan van Dongen
2025-03-14 18:58:34 +01:00
parent a4715a10e7
commit 5926db85df
12 changed files with 333 additions and 75 deletions

View File

@@ -59,10 +59,10 @@ namespace SharpCompress.Common.Arc
1 or 2 => CompressionType.None,
3 => CompressionType.RLE90,
4 => CompressionType.Squeezed,
//5 or 6 or 7 or 8 => CompressionType.Crunched,
//9 => CompressionType.Squashed,
//10 => CompressionType.Crushed,
//11 => CompressionType.Distilled,
5 or 6 or 7 or 8 => CompressionType.Crunched,
9 => CompressionType.Squashed,
10 => CompressionType.Crushed,
11 => CompressionType.Distilled,
_ => CompressionType.Unknown,
};
}

View File

@@ -8,6 +8,7 @@ using SharpCompress.Common.GZip;
using SharpCompress.Common.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.RLE90;
using SharpCompress.Compressors.Squeezed;
using SharpCompress.IO;
@@ -52,6 +53,13 @@ namespace SharpCompress.Common.Arc
case CompressionType.Squeezed:
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Crunched:
compressedStream = new ArcLzwStream(
_stream,
(int)Header.CompressedSize,
true
);
break;
default:
throw new NotSupportedException(
"CompressionMethod: " + Header.CompressionMethod

View File

@@ -24,4 +24,8 @@ public enum CompressionType
Explode,
Squeezed,
RLE90,
Crunched,
Squashed,
Crushed,
Distilled,
}

View File

@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Compressors.RLE90;
using SharpCompress.Compressors.Squeezed;
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[0] != BITS)
{
throw new InvalidDataException($"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)
{
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();
}

View File

@@ -0,0 +1,56 @@
using System;
public partial class ArcLzwStream
{
public class BitReader
{
private readonly byte[] data;
private int bitPosition;
private int bytePosition;
public BitReader(byte[] inputData)
{
data = inputData;
bitPosition = 0;
bytePosition = 0;
}
public int? ReadBits(int bitCount)
{
if (bitCount <= 0 || bitCount > 16)
throw new ArgumentOutOfRangeException(
nameof(bitCount),
"Bit count must be between 1 and 16"
);
if (bytePosition >= data.Length)
return null;
int result = 0;
int bitsRead = 0;
while (bitsRead < bitCount)
{
if (bytePosition >= data.Length)
return null;
int bitsAvailable = 8 - bitPosition;
int bitsToRead = Math.Min(bitCount - bitsRead, bitsAvailable);
int mask = (1 << bitsToRead) - 1;
result |= ((data[bytePosition] >> bitPosition) & mask) << bitsRead;
bitPosition += bitsToRead;
bitsRead += bitsToRead;
if (bitPosition >= 8)
{
bitPosition = 0;
bytePosition++;
}
}
return (ushort)result;
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
namespace SharpCompress.Compressors.Squeezed
namespace SharpCompress.Compressors.RLE90
{
public static class RLE
{
@@ -16,7 +16,7 @@ namespace SharpCompress.Compressors.Squeezed
public static List<byte> UnpackRLE(byte[] compressedBuffer)
{
var result = new List<byte>(compressedBuffer.Length * 2); // Optimized initial capacity
bool countMode = false;
var countMode = false;
byte last = 0;
foreach (var c in compressedBuffer)

View File

@@ -19,7 +19,6 @@ namespace SharpCompress.Compressors.RLE90
_stream = stream;
_compressedSize = compressedSize;
}
public override bool CanRead => true;
public override bool CanSeek => false;
@@ -47,46 +46,10 @@ namespace SharpCompress.Compressors.RLE90
using var binaryReader = new BinaryReader(_stream);
byte[] compressedBuffer = binaryReader.ReadBytes(_compressedSize);
int bufferIndex = offset;
bool inCountState = false;
byte last = 0;
var unpacked = RLE.UnpackRLE(compressedBuffer);
unpacked.CopyTo(buffer);
foreach (byte c in compressedBuffer)
{
if (inCountState)
{
if (c == 0)
{
if (bufferIndex < buffer.Length)
buffer[bufferIndex++] = DLE;
}
else
{
int repeatCount = Math.Min(c - 1, buffer.Length - bufferIndex);
for (int i = 0; i < repeatCount; i++)
buffer[bufferIndex++] = last;
}
inCountState = false;
}
else
{
if (c == DLE)
{
inCountState = true;
}
else
{
if (bufferIndex < buffer.Length)
buffer[bufferIndex++] = c;
last = c;
}
}
if (bufferIndex >= offset + count)
break; // Stop when we fill the requested buffer size
}
return bufferIndex - offset;
return unpacked.Count;
}
public override long Seek(long offset, SeekOrigin origin) =>

View File

@@ -1,36 +1,48 @@
using System;
using System.IO;
namespace SharpCompress.Compressors.Squeezed
public class BitReader
{
// Helper BitReader class for reading individual bits
public class BitReader
private readonly Stream _stream;
private int _bitBuffer;
private int _bitCount;
public BitReader(Stream stream)
{
private readonly Stream _stream;
private int _bitBuffer;
private int _bitCount;
_stream = stream;
_bitBuffer = 0;
_bitCount = 0;
}
public BitReader(Stream stream)
public bool ReadBit()
{
if (_bitCount == 0)
{
_stream = stream;
_bitBuffer = 0;
_bitCount = 0;
int nextByte = _stream.ReadByte();
if (nextByte == -1)
throw new EndOfStreamException();
_bitBuffer = nextByte;
_bitCount = 8;
}
public bool ReadBit()
{
if (_bitCount == 0)
{
int nextByte = _stream.ReadByte();
if (nextByte == -1)
throw new EndOfStreamException();
_bitBuffer = nextByte;
_bitCount = 8;
}
bool bit = (_bitBuffer & 1) != 0;
_bitBuffer >>= 1;
_bitCount--;
return bit;
}
bool bit = (_bitBuffer & 1) != 0;
_bitBuffer >>= 1;
_bitCount--;
return bit;
public int ReadBits(int count)
{
if (count < 1 || count > 32)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be between 1 and 32.");
}
int value = 0;
for (int i = 0; i < count; i++)
{
value = (value << 1) | (ReadBit() ? 1 : 0);
}
return value;
}
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Compressors.RLE90;
using ZstdSharp.Unsafe;
namespace SharpCompress.Compressors.Squeezed

View File

@@ -23,12 +23,26 @@ namespace SharpCompress.Test.Arc
public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None);
[Fact]
public void Arc_SqueezedAndPacked_Read()
public void Arc_Squeezed_Read()
{
//archive contains two different compression methods, hence the need for a specific test function
using (
Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Arc.squeezed.arc"))
)
ProcessArchive("Arc.squeezed.arc");
}
[Fact]
public void Arc_Crunched_Read()
{
ProcessArchive("Arc.crunched.arc");
}
[Fact]
public void Arc_Squashed_Read()
{
ProcessArchive("Arc.squashed.arc");
}
private void ProcessArchive(string archiveName)
{
// Process a given archive by its name
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, archiveName)))
using (IReader reader = ArcReader.Open(stream))
{
while (reader.MoveToNextEntry())
@@ -42,6 +56,7 @@ namespace SharpCompress.Test.Arc
}
}
}
VerifyFilesByExtension();
}
}

Binary file not shown.

Binary file not shown.