diff --git a/Aaru.Archives/Arc/Arc.cs b/Aaru.Archives/Arc/Arc.cs index 233e4754a..23ae96200 100644 --- a/Aaru.Archives/Arc/Arc.cs +++ b/Aaru.Archives/Arc/Arc.cs @@ -1,11 +1,16 @@ using System; +using System.IO; +using System.Text; using Aaru.CommonTypes.Interfaces; namespace Aaru.Archives; public sealed partial class Arc : IArchive { - const string MODULE_NAME = "arc Archive Plugin"; + const string MODULE_NAME = "arc Archive Plugin"; + Encoding _encoding; + ArchiveSupportedFeature _features; + Stream _stream; #region IArchive Members @@ -16,12 +21,15 @@ public sealed partial class Arc : IArchive /// public string Author => Authors.NataliaPortillo; /// - public bool Opened { get; } + public bool Opened { get; private set; } /// - public ArchiveSupportedFeature ArchiveFeatures => - ArchiveSupportedFeature.SupportsCompression | ArchiveSupportedFeature.SupportsFilenames; + public ArchiveSupportedFeature ArchiveFeatures => !Opened + ? ArchiveSupportedFeature.SupportsCompression | + ArchiveSupportedFeature.SupportsFilenames | + ArchiveSupportedFeature.HasEntryTimestamp + : _features; /// - public int NumberOfEntries { get; } + public int NumberOfEntries => Opened ? _entries.Count : -1; #endregion } \ No newline at end of file diff --git a/Aaru.Archives/Arc/Info.cs b/Aaru.Archives/Arc/Info.cs index 4eb2788c4..4c25cf07f 100644 --- a/Aaru.Archives/Arc/Info.cs +++ b/Aaru.Archives/Arc/Info.cs @@ -27,7 +27,7 @@ public sealed partial class Arc // Not a valid marker if(header.marker != MARKER) return false; - switch(header.method) + switch((int)header.method) { // Not a valid compression method case > 12 and < 20: @@ -41,9 +41,8 @@ public sealed partial class Arc for(int i = 0; i < 11; i++) // Not a valid filename character - { - if(header.filename[i] > 0 && header.filename[i] < 0x20) return false; - } + if(header.filename[i] > 0 && header.filename[i] < 0x20) + return false; // If the filename is not 8.3, it's probably not an ARC file, but maybe it is in MVS/UNIX? if(header.filename[11] != 0) return false; @@ -85,7 +84,7 @@ public sealed partial class Arc // Not a valid marker if(header.marker != MARKER) return; - switch(header.method) + switch((int)header.method) { // Not a valid compression method case > 12 and < 20: @@ -99,9 +98,8 @@ public sealed partial class Arc for(int i = 0; i < 11; i++) // Not a valid filename character - { - if(header.filename[i] > 0 && header.filename[i] < 0x20) return; - } + if(header.filename[i] > 0 && header.filename[i] < 0x20) + return; // If the filename is not 8.3, it's probably not an ARC file, but maybe it is in MVS/UNIX? if(header.filename[11] != 0) return; diff --git a/Aaru.Archives/Arc/Open.cs b/Aaru.Archives/Arc/Open.cs new file mode 100644 index 000000000..3796177d5 --- /dev/null +++ b/Aaru.Archives/Arc/Open.cs @@ -0,0 +1,237 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Helpers; +using Aaru.Logging; + +namespace Aaru.Archives; + +public sealed partial class Arc +{ + List _entries; + +#region IArchive Members + + /// + public ErrorNumber Open(IFilter filter, Encoding encoding) + { + 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); + + Header header = Marshal.ByteArrayToStructureLittleEndian
(hdr); + + // Not a valid marker + if(header.marker != MARKER) return ErrorNumber.InvalidArgument; + + switch((int)header.method) + { + // Not a valid compression method + case > 12 and < 20: + // Not a valid informational item + case > 22 and < 30: + // Not a valid control item + case > 31: + return ErrorNumber.InvalidArgument; + } + + // Not a valid filename character + for(int i = 0; i < 11; i++) + if(header.filename[i] > 0 && header.filename[i] < 0x20) + return ErrorNumber.InvalidArgument; + + // If the filename is not 8.3, it's probably not an ARC file, but maybe it is in MVS/UNIX? + if(header.filename[11] != 0) return ErrorNumber.InvalidArgument; + + // Compressed size is larger than file size + // Hope for the best + if(header.compressed >= _stream.Length && (int)header.method != 31) return ErrorNumber.InvalidArgument; + + Opened = true; + _encoding = encoding ?? Encoding.ASCII; + _stream.Position = 0; + _entries = []; + + _features = ArchiveSupportedFeature.SupportsCompression | + ArchiveSupportedFeature.SupportsFilenames | + ArchiveSupportedFeature.HasEntryTimestamp; + + string path = ""; + string longname = null; + string comment = null; + string attributes = null; + var br = new BinaryReader(_stream); + byte peekedByte; + + // Process headers + while(true) + { + peekedByte = br.ReadByte(); + AaruLogging.Debug(MODULE_NAME, "[navy]peekedByte[/] = [teal]0x{0:X2}[/]", peekedByte); + peekedByte = br.ReadByte(); + AaruLogging.Debug(MODULE_NAME, "[navy]peekedByte[/] = [teal]0x{0:X2}[/]", peekedByte); + + if((Method)peekedByte == Method.EndOfArchive) break; + + if((Method)peekedByte == Method.SubdirectoryEnd) + { + // Remove last directory from path + int lastSlash = path.LastIndexOf(Path.DirectorySeparatorChar); + + path = lastSlash >= 0 ? path[..lastSlash] : ""; + + continue; + } + + _stream.Position -= 2; + + // Decode header + _stream.ReadExactly(hdr, 0, hdr.Length); + header = Marshal.ByteArrayToStructureLittleEndian
(hdr); + + AaruLogging.Debug(MODULE_NAME, "[navy]header.marker[/] = [teal]0x{0:X2}[/]", header.marker); + AaruLogging.Debug(MODULE_NAME, "[navy]header.method[/] = [teal]{0}[/]", header.method); + + AaruLogging.Debug(MODULE_NAME, + "[navy]header.filename[/] = [green]\"{0}\"[/]", + StringHandlers.CToString(header.filename)); + + AaruLogging.Debug(MODULE_NAME, "[navy]header.compressed[/] = [teal]{0}[/]", header.compressed); + AaruLogging.Debug(MODULE_NAME, "[navy]header.date[/] = [teal]{0}[/]", header.date); + AaruLogging.Debug(MODULE_NAME, "[navy]header.time[/] = [teal]{0}[/]", header.time); + AaruLogging.Debug(MODULE_NAME, "[navy]header.crc[/] = [teal]0x{0:X4}[/]", header.crc); + AaruLogging.Debug(MODULE_NAME, "[navy]header.uncompressed[/] = [teal]{0}[/]", header.uncompressed); + + if(header.method == Method.FileInformation) + { + int recordsSize = header.compressed; + int recordsRead = 0; + + while(recordsRead < recordsSize) + { + ushort len = br.ReadUInt16(); + var finfoType = (FileInformationType)br.ReadByte(); + byte[] info = br.ReadBytes(len - 3); + + recordsRead += len; + + switch(finfoType) + { + case FileInformationType.Description: + comment = StringHandlers.CToString(info, _encoding); + + break; + case FileInformationType.Attributes: + attributes = StringHandlers.CToString(info, _encoding); + + break; + case FileInformationType.LongName: + longname = StringHandlers.CToString(info, _encoding); + + break; + } + } + } + + string filename; + + Entry entry; + + if(header.method == Method.Subdirectory) + { + filename = StringHandlers.CToString(header.filename, _encoding); + if(longname is not null) filename = longname; + + path = Path.Combine(path, filename); + + entry = new Entry + { + Method = header.method, + Filename = path, + Compressed = 0, + Uncompressed = 0, + LastWriteTime = DateHandlers.DosToDateTime(header.date, header.time), + DataOffset = 0, + Comment = comment, + Attributes = FileAttributes.Directory + }; + + _features |= ArchiveSupportedFeature.HasExplicitDirectories | + ArchiveSupportedFeature.SupportsSubdirectories; + + if(attributes is not null) + { + if(attributes.Contains('A')) entry.Attributes |= FileAttributes.Archive; + if(attributes.Contains('R')) entry.Attributes |= FileAttributes.ReadOnly; + if(attributes.Contains('H')) entry.Attributes |= FileAttributes.Hidden; + if(attributes.Contains('S')) entry.Attributes |= FileAttributes.System; + } + + longname = null; + comment = null; + attributes = null; + + _entries.Add(entry); + + continue; + } + + filename = StringHandlers.CToString(header.filename, _encoding); + if(longname is not null) filename = longname; + + if(header.method == Method.UnpackedOld) _stream.Position -= 4; + + entry = new Entry + { + Method = header.method, + Filename = Path.Combine(path, filename), + Compressed = header.compressed, + Uncompressed = header.uncompressed, + LastWriteTime = DateHandlers.DosToDateTime(header.date, header.time), + DataOffset = _stream.Position, + Comment = comment, + Attributes = FileAttributes.Normal + }; + + if(attributes is not null) + { + if(attributes.Contains('A')) entry.Attributes |= FileAttributes.Archive; + if(attributes.Contains('R')) entry.Attributes |= FileAttributes.ReadOnly; + if(attributes.Contains('H')) entry.Attributes |= FileAttributes.Hidden; + if(attributes.Contains('S')) entry.Attributes |= FileAttributes.System; + } + + longname = null; + comment = null; + attributes = null; + + _entries.Add(entry); + + _stream.Position += header.compressed; + } + + return ErrorNumber.NoError; + } + + /// + public void Close() + { + // Already closed + if(!Opened) return; + + _stream?.Close(); + _entries?.Clear(); + + _stream = null; + Opened = false; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arc/Structs.cs b/Aaru.Archives/Arc/Structs.cs index 5bb9bf2e7..f0d4f51b0 100644 --- a/Aaru.Archives/Arc/Structs.cs +++ b/Aaru.Archives/Arc/Structs.cs @@ -1,16 +1,34 @@ +using System; +using System.IO; using System.Runtime.InteropServices; namespace Aaru.Archives; public sealed partial class Arc { +#region Nested type: Entry + + struct Entry + { + public Method Method; + public string Filename; + public int Compressed; + public int Uncompressed; + public DateTime LastWriteTime; + public long DataOffset; + public string Comment; + public FileAttributes Attributes; + } + +#endregion + #region Nested type: Header [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] readonly struct Header { - public readonly byte marker; - public readonly byte method; + public readonly byte marker; + public readonly Method method; [MarshalAs(UnmanagedType.ByValArray, SizeConst = FNLEN)] public readonly byte[] filename; public readonly int compressed; diff --git a/Aaru.Archives/Arc/Unimplemented.cs b/Aaru.Archives/Arc/Unimplemented.cs index 72a57f934..12c1194f9 100644 --- a/Aaru.Archives/Arc/Unimplemented.cs +++ b/Aaru.Archives/Arc/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 Arc { #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();