From 926fcf0c0a9206e9bb4ee0f6748914a6b5150cb3 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 8 Sep 2025 02:05:07 +0100 Subject: [PATCH] [HA] Implement GetEntry. --- Aaru.Archives/Ha/Files.cs | 46 ++++++++ Aaru.Archives/Ha/Unimplemented.cs | 15 --- Aaru.Compression/Aaru.Compression.csproj | 3 +- Aaru.Compression/HaStream.cs | 135 +++++++++++++++++++++++ 4 files changed, 183 insertions(+), 16 deletions(-) delete mode 100644 Aaru.Archives/Ha/Unimplemented.cs create mode 100644 Aaru.Compression/HaStream.cs diff --git a/Aaru.Archives/Ha/Files.cs b/Aaru.Archives/Ha/Files.cs index 8509695f4..fd274a24e 100644 --- a/Aaru.Archives/Ha/Files.cs +++ b/Aaru.Archives/Ha/Files.cs @@ -1,6 +1,11 @@ using System; +using System.IO; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; +using Aaru.Compression; +using Aaru.Filters; +using Aaru.Helpers.IO; using FileAttributes = System.IO.FileAttributes; namespace Aaru.Archives; @@ -137,5 +142,46 @@ public sealed partial class Ha return ErrorNumber.NoError; } + /// + public ErrorNumber GetEntry(int entryNumber, out IFilter filter) + { + filter = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + switch(_entries[entryNumber].Method) + { + case Method.Directory: + return ErrorNumber.IsDirectory; + case >= Method.Special: + return ErrorNumber.InvalidArgument; + case > Method.HSC: + return ErrorNumber.NotSupported; + } + + Stream stream = new OffsetStream(new NonClosableStream(_stream), + _entries[entryNumber].DataOffset, + _entries[entryNumber].DataOffset + _entries[entryNumber].Compressed); + + if(_entries[entryNumber].Uncompressed == 0) stream = new MemoryStream([]); + + if(_entries[entryNumber].Method == Method.ASC) + stream = new HaStream(stream, _entries[entryNumber].Uncompressed, HaStream.HaMethod.ASC); + + if(_entries[entryNumber].Method == Method.HSC) + stream = new HaStream(stream, _entries[entryNumber].Uncompressed, HaStream.HaMethod.HSC); + + filter = new ZZZNoFilter(); + ErrorNumber errno = filter.Open(stream); + + if(errno == ErrorNumber.NoError) return ErrorNumber.NoError; + + stream.Close(); + + return errno; + } + #endregion } \ No newline at end of file diff --git a/Aaru.Archives/Ha/Unimplemented.cs b/Aaru.Archives/Ha/Unimplemented.cs deleted file mode 100644 index d8639a96d..000000000 --- a/Aaru.Archives/Ha/Unimplemented.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Aaru.CommonTypes.Enums; -using Aaru.CommonTypes.Interfaces; - -namespace Aaru.Archives; - -public sealed partial class Ha -{ -#region IArchive Members - - /// - public ErrorNumber GetEntry(int entryNumber, out IFilter filter) => throw new NotImplementedException(); - -#endregion -} \ No newline at end of file diff --git a/Aaru.Compression/Aaru.Compression.csproj b/Aaru.Compression/Aaru.Compression.csproj index c4e088f59..0b7a919fc 100644 --- a/Aaru.Compression/Aaru.Compression.csproj +++ b/Aaru.Compression/Aaru.Compression.csproj @@ -46,6 +46,7 @@ + @@ -74,7 +75,7 @@ - + diff --git a/Aaru.Compression/HaStream.cs b/Aaru.Compression/HaStream.cs new file mode 100644 index 000000000..f3792a3f4 --- /dev/null +++ b/Aaru.Compression/HaStream.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Aaru.Compression; + +public partial class HaStream : Stream +{ +#region HaMethod enum + + public enum HaMethod + { + ASC = 0, + HSC = 1 + } + +#endregion + + readonly byte[] _decoded; + readonly long _length; + long _position; + + public HaStream(Stream compressedStream, long decompressedLength, HaMethod method) + { + 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; + byte[] inBuf = new byte[compressedStream.Length]; + compressedStream.ReadExactly(inBuf, 0, inBuf.Length); + + // Allocate output buffer + _decoded = new byte[decompressedLength]; + nint outLen = (nint)decompressedLength; + + // Call native decompressor + int err; + + switch(method) + { + case HaMethod.ASC: + err = ha_asc_decompress(inBuf, inBuf.Length, _decoded, ref outLen); + + break; + case HaMethod.HSC: + err = ha_hsc_decompress(inBuf, inBuf.Length, _decoded, ref outLen); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(method), "Unknown HA compression method"); + } + + if(err != 0) throw new InvalidOperationException("Ha 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 ha_asc_decompress(byte[] in_buf, nint in_len, byte[] out_buf, ref nint out_len); + + [LibraryImport("libAaru.Compression.Native")] + public static partial int ha_hsc_decompress(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; + + int 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