mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-23 15:34:34 +00:00
Added ARC's crunched methods 5, 6, 7 & 8
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,4 +24,8 @@ public enum CompressionType
|
||||
Explode,
|
||||
Squeezed,
|
||||
RLE90,
|
||||
Crunched,
|
||||
Squashed,
|
||||
Crushed,
|
||||
Distilled,
|
||||
}
|
||||
|
||||
199
src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs
Normal file
199
src/SharpCompress/Compressors/ArcLzw/ArcLzwStream.cs
Normal 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();
|
||||
}
|
||||
56
src/SharpCompress/Compressors/ArcLzw/BitReader.cs
Normal file
56
src/SharpCompress/Compressors/ArcLzw/BitReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
BIN
tests/TestArchives/Archives/Arc.crunched.arc
Normal file
BIN
tests/TestArchives/Archives/Arc.crunched.arc
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Arc.squashed.arc
Normal file
BIN
tests/TestArchives/Archives/Arc.squashed.arc
Normal file
Binary file not shown.
Reference in New Issue
Block a user