diff --git a/Aaru.Archives/Ha/Enums.cs b/Aaru.Archives/Ha/Enums.cs new file mode 100644 index 000000000..28c6ca16b --- /dev/null +++ b/Aaru.Archives/Ha/Enums.cs @@ -0,0 +1,27 @@ +namespace Aaru.Archives; + +public sealed partial class Ha +{ +#region Nested type: MdiSource + + enum MdiSource : byte + { + MSDOS = 1, + UNIX = 2 + } + +#endregion + +#region Nested type: Method + + enum Method : byte + { + Copy = 0, + ASC = 1, + HSC = 2, + Directory = 14, + Special = 15 + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Ha/Ha.cs b/Aaru.Archives/Ha/Ha.cs index bb5b28af0..88c542210 100644 --- a/Aaru.Archives/Ha/Ha.cs +++ b/Aaru.Archives/Ha/Ha.cs @@ -1,10 +1,15 @@ using System; +using System.Collections.Generic; +using System.IO; using Aaru.CommonTypes.Interfaces; namespace Aaru.Archives; public sealed partial class Ha : IArchive { + List _entries; + Stream _stream; + #region IArchive Members /// @@ -14,7 +19,7 @@ public sealed partial class Ha : IArchive /// public string Author => Authors.NataliaPortillo; /// - public bool Opened { get; } + public bool Opened { get; private set; } /// public ArchiveSupportedFeature ArchiveFeatures => ArchiveSupportedFeature.HasEntryTimestamp | ArchiveSupportedFeature.SupportsCompression | @@ -22,7 +27,7 @@ public sealed partial class Ha : IArchive ArchiveSupportedFeature.SupportsSubdirectories | ArchiveSupportedFeature.HasExplicitDirectories; /// - public int NumberOfEntries { get; } + public int NumberOfEntries => Opened ? _entries.Count : -1; #endregion } \ No newline at end of file diff --git a/Aaru.Archives/Ha/Open.cs b/Aaru.Archives/Ha/Open.cs new file mode 100644 index 000000000..5a2fe8567 --- /dev/null +++ b/Aaru.Archives/Ha/Open.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Helpers; + +namespace Aaru.Archives; + +public sealed partial class Ha +{ +#region IArchive Members + + /// + public ErrorNumber Open(IFilter filter, Encoding encoding) + { + encoding ??= Encoding.UTF8; + + if(filter.DataForkLength < Marshal.SizeOf()) return ErrorNumber.InvalidArgument; + + _stream = filter.GetDataForkStream(); + _stream.Position = 0; + + byte[] hdr = new byte[Marshal.SizeOf()]; + + _stream.ReadExactly(hdr, 0, hdr.Length); + + HaHeader header = Marshal.ByteArrayToStructureLittleEndian(hdr); + + // Not a valid magic + if(header.magic != HA_MAGIC) return ErrorNumber.InvalidArgument; + + _entries = []; + + int fhLen = Marshal.SizeOf(); + byte[] fhBuf = new byte[fhLen]; + byte[] pathBuf = new byte[16384]; + byte[] nameBuf = new byte[256]; + int i; // Guard + + while(_stream.Position + fhLen < _stream.Length) + { + _stream.ReadExactly(fhBuf, 0, fhLen); + FHeader fh = Marshal.ByteArrayToStructureLittleEndian(fhBuf); + + for(i = 0; i < pathBuf.Length; i++) + { + int b = _stream.ReadByte(); + + if(b < 0) return ErrorNumber.InvalidArgument; // Makes no sense here + + if(b == 0xFF) + { + pathBuf[i] = 0x2F; + + continue; + } + + if(b == 0) + { + pathBuf[i] = 0; + + break; + } + + pathBuf[i] = (byte)b; + } + + if(i == pathBuf.Length) return ErrorNumber.InvalidArgument; // Got beyond the buffer length + + for(i = 0; i < nameBuf.Length; i++) + { + int b = _stream.ReadByte(); + + if(b < 0) return ErrorNumber.InvalidArgument; // Makes no sense here + + if(b == 0) + { + nameBuf[i] = 0; + + break; + } + + nameBuf[i] = (byte)b; + } + + if(i == nameBuf.Length) return ErrorNumber.InvalidArgument; // Got beyond the buffer length + + int mdiLen = _stream.ReadByte(); + + byte[] mdi = new byte[mdiLen]; + _stream.ReadExactly(mdi, 0, mdiLen); + + string path = StringHandlers.CToString(pathBuf, encoding); + string name = StringHandlers.CToString(nameBuf, encoding); + + // Remove drive letter + if(path.Length > 3 && path[1] == ':') path = path[2..]; + + // ... or leading slash + if(path.Length > 0 && path[0] == '/') path = path[1..]; + + // replace slash with system separator + path = path.Replace('/', Path.DirectorySeparatorChar); + + var entry = new Entry + { + Method = (Method)(fh.VerType & 0x0F), + Compressed = fh.clen, + Uncompressed = fh.olen, + LastWrite = DateHandlers.UnixToDateTime(fh.time), + DataOffset = _stream.Position, + Filename = Path.Combine(path, name) + }; + + switch((MdiSource)mdi[0]) + { + case MdiSource.MSDOS: + entry.Attributes = (FileAttributes)mdi[1]; + + break; + case MdiSource.UNIX: + { + UnixMdi unixMdi = Marshal.ByteArrayToStructureLittleEndian(mdi); + entry.Mode = unixMdi.attr; + entry.Uid = unixMdi.user; + entry.Gid = unixMdi.group; + + if(entry.Method == Method.Directory) entry.Attributes = FileAttributes.Directory; + + break; + } + } + + + _entries.Add(entry); + + _stream.Position += entry.Compressed; + } + + Opened = true; + + return ErrorNumber.NoError; + } + + /// + public void Close() + { + // Already closed + if(!Opened) return; + + _stream?.Close(); + + _stream = null; + Opened = false; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Ha/Structs.cs b/Aaru.Archives/Ha/Structs.cs index 8922bbb88..d20532eaf 100644 --- a/Aaru.Archives/Ha/Structs.cs +++ b/Aaru.Archives/Ha/Structs.cs @@ -1,9 +1,29 @@ +using System; +using System.IO; using System.Runtime.InteropServices; namespace Aaru.Archives; public sealed partial class Ha { +#region Nested type: Entry + + struct Entry + { + public Method Method; + public uint Compressed; + public uint Uncompressed; + public DateTime LastWrite; + public FileAttributes Attributes; + public long DataOffset; + public string Filename; + public ushort Mode; + public ushort Uid; + public ushort Gid; + } + +#endregion + #region Nested type: FHeader [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] @@ -13,14 +33,12 @@ public sealed partial class Ha public readonly byte VerType; // Compressed length - public readonly ushort clen; + public readonly uint clen; // Original length - public readonly ushort olen; + public readonly uint olen; - // Unclear if DOS packed date or what - public readonly ushort date; - public readonly ushort time; + public readonly int time; // CRC32 public readonly uint crc; @@ -43,5 +61,18 @@ public sealed partial class Ha public readonly ushort count; } +#endregion + +#region Nested type: UnixMdi + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + readonly struct UnixMdi + { + public readonly byte type; + public readonly ushort attr; + public readonly ushort user; + public readonly ushort group; + } + #endregion } \ No newline at end of file diff --git a/Aaru.Archives/Ha/Unimplemented.cs b/Aaru.Archives/Ha/Unimplemented.cs index be6a1bc43..4b3a36c6e 100644 --- a/Aaru.Archives/Ha/Unimplemented.cs +++ b/Aaru.Archives/Ha/Unimplemented.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; @@ -12,15 +11,6 @@ public sealed partial class Ha { #region IArchive Members - /// - public ErrorNumber Open(IFilter filter, Encoding encoding) => throw new NotImplementedException(); - - /// - public void Close() - { - throw new NotImplementedException(); - } - /// public ErrorNumber GetFilename(int entryNumber, out string fileName) => throw new NotImplementedException();