diff --git a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs
index 01243c92..824d90eb 100644
--- a/src/SharpCompress/Common/Arc/ArcEntryHeader.cs
+++ b/src/SharpCompress/Common/Arc/ArcEntryHeader.cs
@@ -28,6 +28,7 @@ namespace SharpCompress.Common.Arc
{
return null;
}
+ DataStartPosition = stream.Position;
return LoadFrom(headerBytes);
}
@@ -56,8 +57,8 @@ namespace SharpCompress.Common.Arc
return value switch
{
1 or 2 => CompressionType.None,
- //3 => CompressionType.RLE90,
- //4 => CompressionType.Squeezed,
+ 3 => CompressionType.RLE90,
+ 4 => CompressionType.Squeezed,
//5 or 6 or 7 or 8 => CompressionType.Crunched,
//9 => CompressionType.Squashed,
//10 => CompressionType.Crushed,
diff --git a/src/SharpCompress/Common/Arc/ArcFilePart.cs b/src/SharpCompress/Common/Arc/ArcFilePart.cs
index 7796e5e8..1308d658 100644
--- a/src/SharpCompress/Common/Arc/ArcFilePart.cs
+++ b/src/SharpCompress/Common/Arc/ArcFilePart.cs
@@ -8,6 +8,8 @@ using SharpCompress.Common.GZip;
using SharpCompress.Common.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Common.Zip.Headers;
+using SharpCompress.Compressors.RLE90;
+using SharpCompress.Compressors.Squeezed;
using SharpCompress.IO;
namespace SharpCompress.Common.Arc
@@ -31,7 +33,31 @@ namespace SharpCompress.Common.Arc
{
if (_stream != null)
{
- return new ReadOnlySubStream(_stream, Header.CompressedSize);
+ Stream compressedStream;
+ switch (Header.CompressionMethod)
+ {
+ case CompressionType.None:
+ compressedStream = new ReadOnlySubStream(
+ _stream,
+ Header.DataStartPosition,
+ Header.CompressedSize
+ );
+ break;
+ case CompressionType.RLE90:
+ compressedStream = new RunLength90Stream(
+ _stream,
+ (int)Header.CompressedSize
+ );
+ break;
+ case CompressionType.Squeezed:
+ compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
+ break;
+ default:
+ throw new NotSupportedException(
+ "CompressionMethod: " + Header.CompressionMethod
+ );
+ }
+ return compressedStream;
}
return _stream.NotNull();
}
diff --git a/src/SharpCompress/Common/CompressionType.cs b/src/SharpCompress/Common/CompressionType.cs
index 45b6aeeb..14ec5a1a 100644
--- a/src/SharpCompress/Common/CompressionType.cs
+++ b/src/SharpCompress/Common/CompressionType.cs
@@ -22,4 +22,6 @@ public enum CompressionType
Reduce3,
Reduce4,
Explode,
+ Squeezed,
+ RLE90,
}
diff --git a/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs
new file mode 100644
index 00000000..cdc4ce1f
--- /dev/null
+++ b/src/SharpCompress/Compressors/RLE90/RunLength90Stream.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Compressors.RLE90
+{
+ public class RunLength90Stream : Stream
+ {
+ private readonly Stream _stream;
+ private const byte DLE = 0x90;
+ private int _compressedSize;
+ private bool _processed = false;
+
+ public RunLength90Stream(Stream stream, int compressedSize)
+ {
+ _stream = stream;
+ _compressedSize = compressedSize;
+ }
+
+ 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;
+
+ using var binaryReader = new BinaryReader(_stream);
+ byte[] compressedBuffer = binaryReader.ReadBytes(_compressedSize);
+
+ int bufferIndex = offset;
+ bool inCountState = false;
+ byte last = 0;
+
+ 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;
+ }
+
+ 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();
+ }
+}
diff --git a/src/SharpCompress/Compressors/Squeezed/BitReader.cs b/src/SharpCompress/Compressors/Squeezed/BitReader.cs
new file mode 100644
index 00000000..bc8920df
--- /dev/null
+++ b/src/SharpCompress/Compressors/Squeezed/BitReader.cs
@@ -0,0 +1,36 @@
+using System.IO;
+
+namespace SharpCompress.Compressors.Squeezed
+{
+ // Helper BitReader class for reading individual bits
+ public class BitReader
+ {
+ private readonly Stream _stream;
+ private int _bitBuffer;
+ private int _bitCount;
+
+ public BitReader(Stream stream)
+ {
+ _stream = stream;
+ _bitBuffer = 0;
+ _bitCount = 0;
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/src/SharpCompress/Compressors/Squeezed/RLE.cs b/src/SharpCompress/Compressors/Squeezed/RLE.cs
new file mode 100644
index 00000000..5eda6432
--- /dev/null
+++ b/src/SharpCompress/Compressors/Squeezed/RLE.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SharpCompress.Compressors.Squeezed
+{
+ public static class RLE
+ {
+ private const byte DLE = 0x90;
+
+ ///
+ /// Unpacks an RLE compressed buffer.
+ /// Format: DLE , where count == 0 -> DLE
+ ///
+ /// The compressed buffer to unpack.
+ /// A list of unpacked bytes.
+ public static List UnpackRLE(byte[] compressedBuffer)
+ {
+ var result = new List(compressedBuffer.Length * 2); // Optimized initial capacity
+ bool countMode = false;
+ byte last = 0;
+
+ foreach (var c in compressedBuffer)
+ {
+ if (!countMode)
+ {
+ if (c == DLE)
+ {
+ countMode = true;
+ }
+ else
+ {
+ result.Add(c);
+ last = c;
+ }
+ }
+ else
+ {
+ countMode = false;
+ if (c == 0)
+ {
+ result.Add(DLE);
+ }
+ else
+ {
+ result.AddRange(Enumerable.Repeat(last, c - 1));
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs
new file mode 100644
index 00000000..f48ad449
--- /dev/null
+++ b/src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ZstdSharp.Unsafe;
+
+namespace SharpCompress.Compressors.Squeezed
+{
+ public class SqueezeStream : Stream
+ {
+ private readonly Stream _stream;
+ private readonly int _compressedSize;
+ private const int NUMVALS = 257;
+ private const int SPEOF = 256;
+ private bool _processed = false;
+
+ public SqueezeStream(Stream stream, int compressedSize)
+ {
+ _stream = stream;
+ _compressedSize = compressedSize;
+ }
+
+ 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;
+ using var binaryReader = new BinaryReader(_stream);
+
+ // Read numnodes (equivalent to convert_u16!(numnodes, buf))
+ var numnodes = binaryReader.ReadUInt16();
+
+ // Validation: numnodes should be within bounds
+ if (numnodes >= NUMVALS)
+ {
+ throw new InvalidDataException(
+ $"Invalid number of nodes {numnodes} (max {NUMVALS - 1})"
+ );
+ }
+
+ // Handle the case where no nodes exist
+ if (numnodes == 0)
+ {
+ return 0;
+ }
+
+ // Build dnode (tree of nodes)
+ var dnode = new int[numnodes, 2];
+ for (int j = 0; j < numnodes; j++)
+ {
+ dnode[j, 0] = binaryReader.ReadInt16();
+ dnode[j, 1] = binaryReader.ReadInt16();
+ }
+
+ // Initialize BitReader for reading bits
+ var bitReader = new BitReader(_stream);
+ var decoded = new List();
+
+ int i = 0;
+ // Decode the buffer using the dnode tree
+ while (true)
+ {
+ i = dnode[i, bitReader.ReadBit() ? 1 : 0];
+ if (i < 0)
+ {
+ i = (short)-(i + 1);
+ if (i == SPEOF)
+ {
+ break;
+ }
+ else
+ {
+ decoded.Add((byte)i);
+ i = 0;
+ }
+ }
+ }
+
+ // Unpack the decoded buffer using the RLE class
+ var unpacked = RLE.UnpackRLE(decoded.ToArray());
+ unpacked.CopyTo(buffer, 0);
+ return unpacked.Count();
+ }
+
+ 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();
+ }
+}
diff --git a/tests/SharpCompress.Test/Arc/ArcReaderTests.cs b/tests/SharpCompress.Test/Arc/ArcReaderTests.cs
index 45efe754..c23687cd 100644
--- a/tests/SharpCompress.Test/Arc/ArcReaderTests.cs
+++ b/tests/SharpCompress.Test/Arc/ArcReaderTests.cs
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
+using SharpCompress.Readers;
+using SharpCompress.Readers.Arc;
using Xunit;
namespace SharpCompress.Test.Arc
@@ -18,5 +21,28 @@ namespace SharpCompress.Test.Arc
[Fact]
public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None);
+
+ [Fact]
+ public void Arc_SqueezedAndPacked_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"))
+ )
+ using (IReader reader = ArcReader.Open(stream))
+ {
+ while (reader.MoveToNextEntry())
+ {
+ if (!reader.Entry.IsDirectory)
+ {
+ reader.WriteEntryToDirectory(
+ SCRATCH_FILES_PATH,
+ new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
+ );
+ }
+ }
+ }
+ VerifyFilesByExtension();
+ }
}
}
diff --git a/tests/TestArchives/Archives/Arc.squeezed.arc b/tests/TestArchives/Archives/Arc.squeezed.arc
new file mode 100644
index 00000000..cba07e30
Binary files /dev/null and b/tests/TestArchives/Archives/Arc.squeezed.arc differ