From 2b8dcbbd0d1559fdd7471ca90656f6888f17ea60 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 17 Apr 2026 12:16:18 +0100 Subject: [PATCH] Add support for StuffIt compression streams (Arsenic, Compress, Huffman, Lzah, Method13, Method14, MW, Rle90) --- Aaru.Compression/Aaru.Compression.csproj | 8 ++ Aaru.Compression/StuffIt/ArsenicStream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/CompressStream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/HuffmanStream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/LzahStream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/MWStream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/Method13Stream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/Method14Stream.cs | 111 +++++++++++++++++++++ Aaru.Compression/StuffIt/Rle90Stream.cs | 111 +++++++++++++++++++++ 9 files changed, 896 insertions(+) create mode 100644 Aaru.Compression/StuffIt/ArsenicStream.cs create mode 100644 Aaru.Compression/StuffIt/CompressStream.cs create mode 100644 Aaru.Compression/StuffIt/HuffmanStream.cs create mode 100644 Aaru.Compression/StuffIt/LzahStream.cs create mode 100644 Aaru.Compression/StuffIt/MWStream.cs create mode 100644 Aaru.Compression/StuffIt/Method13Stream.cs create mode 100644 Aaru.Compression/StuffIt/Method14Stream.cs create mode 100644 Aaru.Compression/StuffIt/Rle90Stream.cs diff --git a/Aaru.Compression/Aaru.Compression.csproj b/Aaru.Compression/Aaru.Compression.csproj index 12f543459..f9d628cf6 100644 --- a/Aaru.Compression/Aaru.Compression.csproj +++ b/Aaru.Compression/Aaru.Compression.csproj @@ -75,6 +75,14 @@ + + + + + + + + diff --git a/Aaru.Compression/StuffIt/ArsenicStream.cs b/Aaru.Compression/StuffIt/ArsenicStream.cs new file mode 100644 index 000000000..ceccc7a7e --- /dev/null +++ b/Aaru.Compression/StuffIt/ArsenicStream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class ArsenicStream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public ArsenicStream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_arsenic_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("Arsenic decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_arsenic_decode_buffer(byte[] dst_buffer, ref nint dst_size, + byte[] src_buffer, nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/CompressStream.cs b/Aaru.Compression/StuffIt/CompressStream.cs new file mode 100644 index 000000000..b8bbb6273 --- /dev/null +++ b/Aaru.Compression/StuffIt/CompressStream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class CompressStream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public CompressStream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_compress_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("Compress decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_compress_decode_buffer(byte[] dst_buffer, ref nint dst_size, + byte[] src_buffer, nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/HuffmanStream.cs b/Aaru.Compression/StuffIt/HuffmanStream.cs new file mode 100644 index 000000000..beb87a6a0 --- /dev/null +++ b/Aaru.Compression/StuffIt/HuffmanStream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class HuffmanStream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public HuffmanStream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_huffman_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("Huffman decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_huffman_decode_buffer(byte[] dst_buffer, ref nint dst_size, + byte[] src_buffer, nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/LzahStream.cs b/Aaru.Compression/StuffIt/LzahStream.cs new file mode 100644 index 000000000..944300a85 --- /dev/null +++ b/Aaru.Compression/StuffIt/LzahStream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class LzahStream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public LzahStream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_lzah_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("LZAH decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_lzah_decode_buffer(byte[] dst_buffer, ref nint dst_size, byte[] src_buffer, + nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/MWStream.cs b/Aaru.Compression/StuffIt/MWStream.cs new file mode 100644 index 000000000..14d0b815f --- /dev/null +++ b/Aaru.Compression/StuffIt/MWStream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class MWStream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public MWStream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_mw_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("MW decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_mw_decode_buffer(byte[] dst_buffer, ref nint dst_size, byte[] src_buffer, + nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/Method13Stream.cs b/Aaru.Compression/StuffIt/Method13Stream.cs new file mode 100644 index 000000000..e4e2dc152 --- /dev/null +++ b/Aaru.Compression/StuffIt/Method13Stream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class Method13Stream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public Method13Stream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_method13_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("Method 13 decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_method13_decode_buffer(byte[] dst_buffer, ref nint dst_size, + byte[] src_buffer, nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/Method14Stream.cs b/Aaru.Compression/StuffIt/Method14Stream.cs new file mode 100644 index 000000000..626c54acd --- /dev/null +++ b/Aaru.Compression/StuffIt/Method14Stream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class Method14Stream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public Method14Stream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_method14_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("Method 14 decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_method14_decode_buffer(byte[] dst_buffer, ref nint dst_size, + byte[] src_buffer, nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file diff --git a/Aaru.Compression/StuffIt/Rle90Stream.cs b/Aaru.Compression/StuffIt/Rle90Stream.cs new file mode 100644 index 000000000..ae1945000 --- /dev/null +++ b/Aaru.Compression/StuffIt/Rle90Stream.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +// ReSharper disable LocalizableElement + +namespace Aaru.Compression.StuffIt; + +public partial class Rle90Stream : Stream +{ + readonly byte[] _decoded; + readonly long _length; + long _position; + + public Rle90Stream(Stream compressedStream, long decompressedLength) + { + if(compressedStream == null) throw new ArgumentNullException(nameof(compressedStream)); + if(!compressedStream.CanRead) throw new ArgumentException("Stream must be readable", nameof(compressedStream)); + if(decompressedLength < 0) throw new ArgumentOutOfRangeException(nameof(decompressedLength)); + + // Read full compressed data into memory + compressedStream.Position = 0; + var inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + var outLen = (nint)decompressedLength; + + // Call native decompressor + int err = AARU_stuffit_rle90_decode_buffer(_decoded, ref outLen, inBuf, inBuf.Length); + + if(err != 0) throw new InvalidOperationException("RLE90 decompression failed"); + + // Adjust actual length in case it differs + _length = outLen; + _position = 0; + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => _length; + + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + [LibraryImport("libAaru.Compression.Native")] + public static partial int AARU_stuffit_rle90_decode_buffer(byte[] dst_buffer, ref nint dst_size, byte[] src_buffer, + nint src_size); + + public override void Flush() + { + // no-op + } + + /// + /// Reads up to bytes from the decompressed buffer + /// into , starting at . + /// + public override int Read(byte[] buffer, int offset, int count) + { + if(buffer == null) throw new ArgumentNullException(nameof(buffer)); + if(offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if(count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if(offset + count > buffer.Length) throw new ArgumentException("offset+count exceeds buffer length"); + + long remaining = _length - _position; + + if(remaining <= 0) return 0; + + var toRead = (int)Math.Min(count, remaining); + Array.Copy(_decoded, _position, buffer, offset, toRead); + _position += toRead; + + return toRead; + } + + /// + /// Sets the current position within the decompressed buffer. + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the buffer"); + + _position = newPos; + + return _position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot resize decompressed buffer"); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } +} \ No newline at end of file