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