using System; using System.IO; using System.Runtime.InteropServices; namespace Aaru.Compression.Pak; public partial class DistillStream : Stream { readonly byte[] _decoded; readonly long _length; long _position; public DistillStream(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 = pak_decompress_distill(inBuf, inBuf.Length, _decoded, ref outLen); if(err != 0) throw new InvalidOperationException("Distill 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 pak_decompress_distill(byte[] in_buf, nint in_len, byte[] out_buf, ref nint out_len); 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"); } }