diff --git a/Aaru.Archives/Arj/Arj.cs b/Aaru.Archives/Arj/Arj.cs new file mode 100644 index 000000000..596b6c8a1 --- /dev/null +++ b/Aaru.Archives/Arj/Arj.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Text; +using Aaru.CommonTypes.Interfaces; + +namespace Aaru.Archives; + +public sealed partial class Arj : IArchive +{ + const string MODULE_NAME = "ARJ Archive Plugin"; + Encoding _encoding; + ArchiveSupportedFeature _features; + Stream _stream; + +#region IArchive Members + + /// + public string Name => Localization.ARJ_Name; + /// + public Guid Id => new("7B2F4E8C-3A1D-4F6E-9C5B-8D0E2A7F1B3C"); + /// + public string Author => Authors.NataliaPortillo; + /// + public bool Opened { get; private set; } + /// + public ArchiveSupportedFeature ArchiveFeatures => !Opened + ? ArchiveSupportedFeature.SupportsCompression | + ArchiveSupportedFeature.SupportsFilenames | + ArchiveSupportedFeature.HasEntryTimestamp + : _features; + /// + public int NumberOfEntries => Opened ? _entries.Count : -1; + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Consts.cs b/Aaru.Archives/Arj/Consts.cs new file mode 100644 index 000000000..afe618c51 --- /dev/null +++ b/Aaru.Archives/Arj/Consts.cs @@ -0,0 +1,39 @@ +namespace Aaru.Archives; + +public sealed partial class Arj +{ + /// ARJ archive header signature (little-endian: 0x60, 0xEA) + const ushort HEADER_ID = 0xEA60; + /// Maximum basic header size in bytes + const int HEADER_SIZE_MAX = 2600; + /// Standard first header size (30 bytes of fixed fields) + const int FIRST_HDR_SIZE = 30; + /// Extended first header size with protection fields (34 bytes) + const int FIRST_HDR_SIZE_V = 34; + /// Minimum size for headers with access/creation timestamps + const int R9_HDR_SIZE = 46; + /// CRC32 mask for finalization (XOR with this for standard CRC32) + const uint CRC_MASK = 0xFFFFFFFF; + /// CRC32 polynomial (reversed, same as standard CRC-32/ISO) + const uint CRC_POLY = 0xEDB88320; + /// Extended header tag for OS/2 extended attributes + const byte EA_TAG = (byte)'E'; + /// Extended header tag for UNIX special files + const byte UXSPECIAL_TAG = (byte)'U'; + /// Extended header tag for owner info (string) + const byte OWNER_TAG = (byte)'O'; + /// Extended header tag for owner info (numeric) + const byte OWNER_NUM_TAG = (byte)'o'; + /// ARJZ minimum version that uses extended DEFLATE (deflatez) + const byte ARJZ_DEFLATEZ_VERSION = 55; + /// ARJZ minimum version that uses ArjzStream method 1 + const byte ARJZ_METHOD1_VERSION = 51; + /// ARJZ minimum version that uses ArjzStream method 2 + const byte ARJZ_METHOD2_VERSION = 52; + /// ARJZ minimum version that uses ArjzStream method 3 + const byte ARJZ_METHOD3_VERSION = 53; + /// Minimum number of bytes needed to attempt identification + const int MIN_HEADER_SIZE = 10; + /// OS/2 FEA flag: critical EA + const byte FEA_NEEDEA = 0x80; +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Enums.cs b/Aaru.Archives/Arj/Enums.cs new file mode 100644 index 000000000..847a92107 --- /dev/null +++ b/Aaru.Archives/Arj/Enums.cs @@ -0,0 +1,74 @@ +using System; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ +#region Method enum + + enum Method : byte + { + /// Uncompressed (stored) + Stored = 0, + /// Compressed with method 1 (most compression, LZH) + Method1 = 1, + /// Compressed with method 2 (medium compression, LZH) + Method2 = 2, + /// Compressed with method 3 (fast compression, LZH) + Method3 = 3, + /// Compressed with method 4 (fastest, Golomb-Rice) + Fastest = 4 + } + +#endregion + +#region HostOs enum + + enum HostOs : byte + { + MsDos = 0, + Primos = 1, + Unix = 2, + Amiga = 3, + MacOs = 4, + Os2 = 5, + AppleGs = 6, + AtariSt = 7, + Next = 8, + Vax = 9, + Win95 = 10, + WinNt = 11 + } + +#endregion + +#region FileType enum + + enum FileType : byte + { + Binary = 0, + Text = 1, + Comment = 2, + Directory = 3, + VolumeLabel = 4, + Chapter = 5, + UnixSpecial = 6 + } + +#endregion + +#region ArjFlags enum + + [Flags] + enum ArjFlags : byte + { + None = 0x00, + Garbled = 0x01, + Volume = 0x04, + ExtFile = 0x08, + PathSym = 0x10, + Backup = 0x20 + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Files.cs b/Aaru.Archives/Arj/Files.cs new file mode 100644 index 000000000..c104c48c5 --- /dev/null +++ b/Aaru.Archives/Arj/Files.cs @@ -0,0 +1,214 @@ +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 = Aaru.CommonTypes.Structs.FileAttributes; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ +#region IArchive Members + + /// + public ErrorNumber GetFilename(int entryNumber, out string fileName) + { + fileName = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + fileName = _entries[entryNumber].Filename ?? ""; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber GetEntryNumber(string fileName, bool caseInsensitiveMatch, out int entryNumber) + { + entryNumber = -1; + + if(!Opened) return ErrorNumber.NotOpened; + + StringComparison comparison = caseInsensitiveMatch + ? StringComparison.CurrentCultureIgnoreCase + : StringComparison.CurrentCulture; + + for(int i = 0, count = _entries.Count; i < count; i++) + { + GetFilename(i, out string name); + + if(name is null) continue; + + if(!name.Equals(fileName, comparison)) continue; + + entryNumber = i; + + return ErrorNumber.NoError; + } + + return ErrorNumber.NoSuchFile; + } + + /// + public ErrorNumber GetCompressedSize(int entryNumber, out long length) + { + length = -1; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + length = _entries[entryNumber].CompressedSize; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber GetUncompressedSize(int entryNumber, out long length) + { + length = -1; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + length = _entries[entryNumber].UncompressedSize; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber Stat(int entryNumber, out FileEntryInfo stat) + { + stat = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + Entry entry = _entries[entryNumber]; + + stat = new FileEntryInfo + { + Attributes = entry.IsDirectory ? FileAttributes.Directory : FileAttributes.File, + Blocks = entry.UncompressedSize / 512, + BlockSize = 512, + Length = entry.UncompressedSize, + LastWriteTime = entry.LastWriteTime, + LastWriteTimeUtc = entry.LastWriteTime.ToUniversalTime() + }; + + if(entry.UncompressedSize % 512 != 0) stat.Blocks++; + + if(entry.CreationTime != default(DateTime)) + { + stat.CreationTime = entry.CreationTime; + stat.CreationTimeUtc = entry.CreationTime.ToUniversalTime(); + } + + if(entry.LastAccessTime != default(DateTime)) + { + stat.AccessTime = entry.LastAccessTime; + stat.AccessTimeUtc = entry.LastAccessTime.ToUniversalTime(); + } + + // Unix file permissions for Unix hosts + if(entry.HostOs is HostOs.Unix or HostOs.Next) stat.Mode = entry.FileMode; + + 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; + + Entry entry = _entries[entryNumber]; + + if(entry.IsDirectory) return ErrorNumber.InvalidArgument; + + // Encrypted files are not supported + if((entry.ArjFlags & (byte)ArjFlags.Garbled) != 0) return ErrorNumber.NotSupported; + + Stream stream; + + if(entry.UncompressedSize == 0) + stream = new MemoryStream([]); + else + { + stream = new OffsetStream(new NonClosableStream(_stream), + entry.DataOffset, + entry.DataOffset + entry.CompressedSize); + + switch(entry.ArjxNbr) + { + // ARJZ dispatch based on arj_x_nbr + case ARJZ_METHOD1_VERSION: + stream = new ArjzStream(stream, entry.UncompressedSize, 1); + + break; + case ARJZ_METHOD2_VERSION: + stream = new ArjzStream(stream, entry.UncompressedSize, 2); + + break; + case ARJZ_METHOD3_VERSION: + stream = new ArjzStream(stream, entry.UncompressedSize, 3); + + break; + case ARJZ_DEFLATEZ_VERSION: + stream = new ArjzStream(stream, entry.UncompressedSize, 5); + + break; + default: + // Standard ARJ dispatch based on method + switch(entry.Method) + { + case Method.Stored: + // No decompression needed + break; + case Method.Method1: + stream = new ArjStream(stream, entry.UncompressedSize, 1); + + break; + case Method.Method2: + stream = new ArjStream(stream, entry.UncompressedSize, 2); + + break; + case Method.Method3: + stream = new ArjStream(stream, entry.UncompressedSize, 3); + + break; + case Method.Fastest: + stream = new ArjStream(stream, entry.UncompressedSize, 4); + + break; + default: + return ErrorNumber.NotSupported; + } + + break; + } + } + + 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/Arj/Info.cs b/Aaru.Archives/Arj/Info.cs new file mode 100644 index 000000000..e861c228a --- /dev/null +++ b/Aaru.Archives/Arj/Info.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Text; +using Aaru.CommonTypes.Interfaces; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ + /// Precomputed CRC32 lookup table (polynomial 0xEDB88320). + static readonly uint[] _crcTable = BuildCrcTable(); + + static uint[] BuildCrcTable() + { + var table = new uint[256]; + + for(uint i = 0; i < 256; i++) + { + uint r = i; + + for(var j = 0; j < 8; j++) r = (r & 1) != 0 ? r >> 1 ^ CRC_POLY : r >> 1; + + table[i] = r; + } + + return table; + } + + /// Compute CRC32 over a byte span. + static uint ComputeCrc(uint crc, ReadOnlySpan data) + { + foreach(byte b in data) crc = _crcTable[(byte)(crc ^ b)] ^ crc >> 8; + + return crc; + } + + /// Convert MS-DOS FAT timestamp to DateTime. + static DateTime DosToDateTime(uint dosDateTime) + { + int second = (int)(dosDateTime & 0x1F) * 2; + var minute = (int)(dosDateTime >> 5 & 0x3F); + var hour = (int)(dosDateTime >> 11 & 0x1F); + var day = (int)(dosDateTime >> 16 & 0x1F); + var month = (int)(dosDateTime >> 21 & 0x0F); + int year = (int)(dosDateTime >> 25 & 0x7F) + 1980; + + if(day < 1) day = 1; + if(month < 1) month = 1; + if(month > 12) month = 12; + + int maxDay = DateTime.DaysInMonth(year, month); + + if(day > maxDay) day = maxDay; + if(hour > 23) hour = 23; + if(minute > 59) minute = 59; + if(second > 59) second = 59; + + return new DateTime(year, month, day, hour, minute, second); + } + + /// Convert a Unix timestamp to DateTime. + static DateTime UnixToDateTime(uint unixTime) => + unixTime == 0 ? default(DateTime) : DateTimeOffset.FromUnixTimeSeconds(unixTime).DateTime; + + /// Convert a timestamp value to DateTime based on host OS. + static DateTime TimestampToDateTime(uint raw, HostOs hostOs) => + hostOs is HostOs.Unix or HostOs.Next ? UnixToDateTime(raw) : DosToDateTime(raw); + +#region IArchive Members + + /// + public bool Identify(IFilter filter) + { + if(filter.DataForkLength < MIN_HEADER_SIZE) return false; + + Stream stream = filter.GetDataForkStream(); + stream.Position = 0; + + var header = new byte[MIN_HEADER_SIZE]; + stream.ReadExactly(header, 0, header.Length); + + // Check header signature (little-endian: 0x60, 0xEA) + var headerSignature = BitConverter.ToUInt16(header, 0); + + if(headerSignature != HEADER_ID) return false; + + // Read basic header size + var basicHdrSize = BitConverter.ToUInt16(header, 2); + + if(basicHdrSize is 0 or > HEADER_SIZE_MAX) return false; + + // Read the full header for CRC validation + if(filter.DataForkLength < 4 + basicHdrSize + 4) return false; + + stream.Position = 4; + var hdrData = new byte[basicHdrSize]; + stream.ReadExactly(hdrData, 0, basicHdrSize); + + var crcBytes = new byte[4]; + stream.ReadExactly(crcBytes, 0, 4); + var storedCrc = BitConverter.ToUInt32(crcBytes, 0); + + // Compute and verify CRC32 + uint computedCrc = ComputeCrc(CRC_MASK, hdrData) ^ CRC_MASK; + + return computedCrc == storedCrc; + } + + /// + public void GetInformation(IFilter filter, Encoding encoding, out string information) + { + information = ""; + + if(!Identify(filter)) return; + + var sb = new StringBuilder(); + sb.AppendLine(Localization.ARJ_archive); + + information = sb.ToString(); + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Open.cs b/Aaru.Archives/Arj/Open.cs new file mode 100644 index 000000000..0261f8947 --- /dev/null +++ b/Aaru.Archives/Arj/Open.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Compression; +using Aaru.Logging; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ + List _entries; + + /// + /// Reads an ARJ header block (signature + size + data + CRC + extended headers). Returns false if end of archive + /// or invalid header. + /// + bool ReadHeader(out byte[] headerData, out List extHeaders) + { + headerData = null; + extHeaders = null; + + if(_stream.Position + 4 > _stream.Length) return false; + + // Read 2-byte signature + var sigBytes = new byte[2]; + _stream.ReadExactly(sigBytes, 0, 2); + var sig = BitConverter.ToUInt16(sigBytes, 0); + + if(sig != HEADER_ID) return false; + + // Read 2-byte basic header size + var sizeBytes = new byte[2]; + _stream.ReadExactly(sizeBytes, 0, 2); + var basicHdrSize = BitConverter.ToUInt16(sizeBytes, 0); + + // Size of 0 means end of archive + if(basicHdrSize == 0) return false; + + if(basicHdrSize > HEADER_SIZE_MAX) return false; + + // Read header data + if(_stream.Position + basicHdrSize + 4 > _stream.Length) return false; + + headerData = new byte[basicHdrSize]; + _stream.ReadExactly(headerData, 0, basicHdrSize); + + // Read and verify CRC32 + var crcBytes = new byte[4]; + _stream.ReadExactly(crcBytes, 0, 4); + var storedCrc = BitConverter.ToUInt32(crcBytes, 0); + uint computedCrc = ComputeCrc(CRC_MASK, headerData) ^ CRC_MASK; + + if(storedCrc != computedCrc) + { + AaruLogging.Debug(MODULE_NAME, + "[red]Header CRC mismatch: stored=0x{0:X8}, computed=0x{1:X8}[/]", + storedCrc, + computedCrc); + + return false; + } + + // Read extended headers + extHeaders = []; + + while(_stream.Position + 2 <= _stream.Length) + { + var extSizeBytes = new byte[2]; + _stream.ReadExactly(extSizeBytes, 0, 2); + var extSize = BitConverter.ToUInt16(extSizeBytes, 0); + + if(extSize == 0) break; + + if(_stream.Position + extSize + 4 > _stream.Length) break; + + var extData = new byte[extSize]; + _stream.ReadExactly(extData, 0, extSize); + + // Read extended header CRC + var extCrcBytes = new byte[4]; + _stream.ReadExactly(extCrcBytes, 0, 4); + var extStoredCrc = BitConverter.ToUInt32(extCrcBytes, 0); + uint extComputedCrc = ComputeCrc(CRC_MASK, extData) ^ CRC_MASK; + + if(extStoredCrc != extComputedCrc) + { + AaruLogging.Debug(MODULE_NAME, "[red]Extended header CRC mismatch[/]"); + + continue; + } + + // First byte is the tag, second byte is the continuation flag + if(extData.Length >= 2) + { + byte tag = extData[0]; + byte continuation = extData[1]; + var data = new byte[extData.Length - 2]; + + if(data.Length > 0) Array.Copy(extData, 2, data, 0, data.Length); + + // Find existing block with same tag and append, or create new + var appended = false; + + for(var i = 0; i < extHeaders.Count; i++) + { + if(extHeaders[i].Tag != tag) continue; + + // Append data to existing block + var combined = new byte[extHeaders[i].Data.Length + data.Length]; + Array.Copy(extHeaders[i].Data, 0, combined, 0, extHeaders[i].Data.Length); + Array.Copy(data, 0, combined, extHeaders[i].Data.Length, data.Length); + + extHeaders[i] = new ExtHeaderBlock + { + Tag = tag, + Data = combined + }; + + appended = true; + + break; + } + + if(!appended) + extHeaders.Add(new ExtHeaderBlock + { + Tag = tag, + Data = data + }); + } + } + + return true; + } + + /// Decompress an EA extended header block. + static byte[] DecompressEaBlock(byte[] rawEaData) + { + if(rawEaData is null || rawEaData.Length < 4) return null; + + byte eaMethod = rawEaData[0]; + int eaOrigLen = rawEaData[1] | rawEaData[2] << 8; + + // Bytes 3-6 would be CRC, but we skip validation here + int compDataOffset = eaMethod == 0 ? 3 : 7; + + if(compDataOffset > rawEaData.Length) return null; + + int compDataLen = rawEaData.Length - compDataOffset; + + if(eaMethod == 0) + { + // Stored EA data + var result = new byte[compDataLen]; + Array.Copy(rawEaData, compDataOffset, result, 0, compDataLen); + + return result; + } + + if(eaMethod > 4 || eaOrigLen <= 0) return null; + + try + { + var compStream = new MemoryStream(rawEaData, compDataOffset, compDataLen); + var arjStream = new ArjStream(compStream, eaOrigLen, eaMethod); + var decompressed = new byte[eaOrigLen]; + var totalRead = 0; + + while(totalRead < eaOrigLen) + { + int bytesRead = arjStream.Read(decompressed, totalRead, eaOrigLen - totalRead); + + if(bytesRead <= 0) break; + + totalRead += bytesRead; + } + + return decompressed; + } + catch + { + return null; + } + } + + /// Extract a null-terminated string from a byte array at the given offset. + static string ExtractNullTerminatedString(byte[] data, int offset, Encoding encoding) + { + if(offset >= data.Length) return ""; + + int end = Array.IndexOf(data, (byte)0, offset); + + if(end < 0) end = data.Length; + + int length = end - offset; + + return length <= 0 ? "" : encoding.GetString(data, offset, length); + } + + /// Get the length including the null terminator of a null-terminated string. + static int GetNullTerminatedLength(byte[] data, int offset) + { + if(offset >= data.Length) return 0; + + int end = Array.IndexOf(data, (byte)0, offset); + + if(end < 0) return data.Length - offset; + + return end - offset; + } + + /// Normalize path separators to forward slash. + static string NormalizePath(string path) => path?.Replace('\\', '/'); + + struct ExtHeaderBlock + { + public byte Tag; + public byte[] Data; + } + +#region IArchive Members + + /// + public ErrorNumber Open(IFilter filter, Encoding encoding) + { + if(filter.DataForkLength < MIN_HEADER_SIZE) return ErrorNumber.InvalidArgument; + + _stream = filter.GetDataForkStream(); + _stream.Position = 0; + _encoding = encoding ?? Encoding.GetEncoding(437); + _entries = []; + + _features = ArchiveSupportedFeature.SupportsFilenames | ArchiveSupportedFeature.HasEntryTimestamp; + + // Read and skip the main archive header + if(!ReadHeader(out _, out _)) + { + AaruLogging.Debug(MODULE_NAME, "[red]Invalid main archive header[/]"); + + return ErrorNumber.InvalidArgument; + } + + // Read file entry headers + while(_stream.Position < _stream.Length) + { + long entryStart = _stream.Position; + + if(!ReadHeader(out byte[] headerData, out List extHeaders)) break; + + if(headerData is null || headerData.Length < FIRST_HDR_SIZE) break; + + // Parse fixed header fields + var pos = 0; + byte firstHdrSize = headerData[pos++]; + byte arjNbr = headerData[pos++]; + byte arjxNbr = headerData[pos++]; + byte hostOs = headerData[pos++]; + byte arjFlags = headerData[pos++]; + byte method = headerData[pos++]; + byte fileType = headerData[pos++]; + byte pwdModifier = headerData[pos++]; + var timestamp = BitConverter.ToUInt32(headerData, pos); + pos += 4; + var compSize = BitConverter.ToUInt32(headerData, pos); + pos += 4; + var origSize = BitConverter.ToUInt32(headerData, pos); + pos += 4; + var fileCrc = BitConverter.ToUInt32(headerData, pos); + pos += 4; + var entryPos = BitConverter.ToUInt16(headerData, pos); + pos += 2; + var fileMode = BitConverter.ToUInt16(headerData, pos); + pos += 2; + + byte extFlags = 0; + byte chapterNumber = 0; + uint accessTime = 0; + uint creationTime = 0; + + if(firstHdrSize >= FIRST_HDR_SIZE) + { + extFlags = headerData[pos++]; + chapterNumber = headerData[pos++]; + } + + if(firstHdrSize >= FIRST_HDR_SIZE_V) + { + pos++; // prot_blocks + pos++; // arjprot_id + pos += 2; // reserved + } + + if(firstHdrSize >= R9_HDR_SIZE) + { + pos += 4; // resume_position + accessTime = BitConverter.ToUInt32(headerData, pos); + pos += 4; + creationTime = BitConverter.ToUInt32(headerData, pos); + pos += 4; + pos += 4; // reserved + } + + // Extract filename (null-terminated at offset firstHdrSize) + string filename = ExtractNullTerminatedString(headerData, firstHdrSize, _encoding); + + // Extract comment (null-terminated after filename) + int commentOffset = firstHdrSize + GetNullTerminatedLength(headerData, firstHdrSize) + 1; + string comment = null; + + if(commentOffset < headerData.Length) + { + string commentStr = ExtractNullTerminatedString(headerData, commentOffset, _encoding); + + if(!string.IsNullOrEmpty(commentStr)) comment = commentStr; + } + + AaruLogging.Debug(MODULE_NAME, "[navy]filename[/] = [green]\"{0}\"[/]", filename); + AaruLogging.Debug(MODULE_NAME, "[navy]method[/] = [teal]{0}[/]", method); + AaruLogging.Debug(MODULE_NAME, "[navy]compSize[/] = [teal]{0}[/]", compSize); + AaruLogging.Debug(MODULE_NAME, "[navy]origSize[/] = [teal]{0}[/]", origSize); + AaruLogging.Debug(MODULE_NAME, "[navy]hostOs[/] = [teal]{0}[/]", (HostOs)hostOs); + AaruLogging.Debug(MODULE_NAME, "[navy]fileType[/] = [teal]{0}[/]", (FileType)fileType); + AaruLogging.Debug(MODULE_NAME, "[navy]arjFlags[/] = [teal]0x{0:X2}[/]", arjFlags); + AaruLogging.Debug(MODULE_NAME, "[navy]arjxNbr[/] = [teal]{0}[/]", arjxNbr); + + // Process extended attributes from extended headers + byte[] eaData = null; + + if(extHeaders is { Count: > 0 }) + { + foreach(ExtHeaderBlock eh in extHeaders) + { + if(eh.Tag != EA_TAG || eh.Data is null || eh.Data.Length < 4) continue; + + eaData = DecompressEaBlock(eh.Data); + } + } + + HostOs parsedHostOs = hostOs <= 11 ? (HostOs)hostOs : HostOs.MsDos; + + var entry = new Entry + { + Method = method <= 4 ? (Method)method : Method.Stored, + Filename = NormalizePath(filename), + CompressedSize = compSize, + UncompressedSize = origSize, + DataOffset = _stream.Position, + LastWriteTime = TimestampToDateTime(timestamp, parsedHostOs), + HostOs = parsedHostOs, + FileCrc = fileCrc, + FileMode = fileMode, + ArjFlags = arjFlags, + ArjxNbr = arjxNbr, + Comment = comment, + ExtendedAttributes = eaData, + IsDirectory = (FileType)fileType == FileType.Directory + }; + + if(accessTime != 0) entry.LastAccessTime = TimestampToDateTime(accessTime, parsedHostOs); + + if(creationTime != 0) entry.CreationTime = TimestampToDateTime(creationTime, parsedHostOs); + + // Update features + if(entry.Method != Method.Stored && entry.CompressedSize > 0) + _features |= ArchiveSupportedFeature.SupportsCompression; + + if(entry.IsDirectory) + { + _features |= ArchiveSupportedFeature.HasExplicitDirectories | + ArchiveSupportedFeature.SupportsSubdirectories; + } + + if(entry.Comment is not null || entry.ExtendedAttributes is not null) + _features |= ArchiveSupportedFeature.SupportsXAttrs; + + if(entry.Filename.Contains('/')) _features |= ArchiveSupportedFeature.SupportsSubdirectories; + + _entries.Add(entry); + + // Skip past compressed data + _stream.Position = entry.DataOffset + entry.CompressedSize; + } + + Opened = true; + + return ErrorNumber.NoError; + } + + /// + public void Close() + { + if(!Opened) return; + + _stream?.Close(); + _entries?.Clear(); + + _stream = null; + Opened = false; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Structs.cs b/Aaru.Archives/Arj/Structs.cs new file mode 100644 index 000000000..0058bc20e --- /dev/null +++ b/Aaru.Archives/Arj/Structs.cs @@ -0,0 +1,30 @@ +using System; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ +#region Nested type: Entry + + struct Entry + { + public Method Method; + public string Filename; + public long CompressedSize; + public long UncompressedSize; + public long DataOffset; + public DateTime LastWriteTime; + public DateTime CreationTime; + public DateTime LastAccessTime; + public HostOs HostOs; + public uint FileCrc; + public ushort FileMode; + public byte ArjFlags; + public byte ArjxNbr; + public string Comment; + public byte[] ExtendedAttributes; + public bool IsDirectory; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Arj/Xattrs.cs b/Aaru.Archives/Arj/Xattrs.cs new file mode 100644 index 000000000..1b1ea3c94 --- /dev/null +++ b/Aaru.Archives/Arj/Xattrs.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Aaru.CommonTypes.Enums; + +namespace Aaru.Archives; + +public sealed partial class Arj +{ + /// + /// Parse OS/2 FEA records from a decompressed EA block and return the EA names. Block format: count(2 LE) + + /// records of fEA(1) + nameLen(1) + valueLen(2 LE) + name(nameLen) + value(valueLen). + /// + static List ParseEaNames(byte[] eaBlock) + { + var names = new List(); + + if(eaBlock is null || eaBlock.Length < 2) return names; + + var offset = 0; + var count = BitConverter.ToUInt16(eaBlock, offset); + offset += 2; + + for(var i = 0; i < count && offset + 4 <= eaBlock.Length; i++) + { + byte fEa = eaBlock[offset++]; + byte nameLen = eaBlock[offset++]; + var valLen = BitConverter.ToUInt16(eaBlock, offset); + offset += 2; + + if(offset + nameLen + valLen > eaBlock.Length) break; + + string name = Encoding.ASCII.GetString(eaBlock, offset, nameLen); + offset += nameLen; + offset += valLen; + + if(!string.IsNullOrEmpty(name)) names.Add(name); + } + + return names; + } + + /// Find an OS/2 FEA record by name and return its raw value bytes. + static byte[] FindEaValue(byte[] eaBlock, string eaName) + { + if(eaBlock is null || eaBlock.Length < 2) return null; + + var offset = 0; + var count = BitConverter.ToUInt16(eaBlock, offset); + offset += 2; + + for(var i = 0; i < count && offset + 4 <= eaBlock.Length; i++) + { + byte fEa = eaBlock[offset++]; + byte nameLen = eaBlock[offset++]; + var valLen = BitConverter.ToUInt16(eaBlock, offset); + offset += 2; + + if(offset + nameLen + valLen > eaBlock.Length) return null; + + string name = Encoding.ASCII.GetString(eaBlock, offset, nameLen); + offset += nameLen; + + if(name == eaName) + { + var value = new byte[valLen]; + Array.Copy(eaBlock, offset, value, 0, valLen); + + return value; + } + + offset += valLen; + } + + return null; + } + +#region IArchive Members + + /// + public ErrorNumber ListXAttr(int entryNumber, out List xattrs) + { + xattrs = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + xattrs = []; + + Entry entry = _entries[entryNumber]; + + if(entry.Comment is not null) xattrs.Add("comment"); + + // Parse OS/2 extended attribute names from the decompressed EA block + if(entry.ExtendedAttributes is { Length: >= 2 }) + { + List eaNames = ParseEaNames(entry.ExtendedAttributes); + xattrs.AddRange(eaNames); + } + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber GetXattr(int entryNumber, string xattr, ref byte[] buffer) + { + buffer = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange; + + Entry entry = _entries[entryNumber]; + + if(xattr == "comment") + { + if(entry.Comment is null) return ErrorNumber.NoSuchExtendedAttribute; + + buffer = Encoding.UTF8.GetBytes(entry.Comment); + + return ErrorNumber.NoError; + } + + // Look up OS/2 extended attribute by name + if(entry.ExtendedAttributes is { Length: >= 2 }) + { + byte[] value = FindEaValue(entry.ExtendedAttributes, xattr); + + if(value is not null) + { + buffer = value; + + return ErrorNumber.NoError; + } + } + + return ErrorNumber.NoSuchExtendedAttribute; + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Localization/Localization.Designer.cs b/Aaru.Archives/Localization/Localization.Designer.cs index 104358a10..4af2da723 100644 --- a/Aaru.Archives/Localization/Localization.Designer.cs +++ b/Aaru.Archives/Localization/Localization.Designer.cs @@ -9,21 +9,21 @@ namespace Aaru.Archives { using System; - - + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Localization { - + private static System.Resources.ResourceManager resourceMan; - + private static System.Globalization.CultureInfo resourceCulture; - + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Localization() { } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Resources.ResourceManager ResourceManager { get { @@ -34,7 +34,7 @@ namespace Aaru.Archives { return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { @@ -44,737 +44,749 @@ namespace Aaru.Archives { resourceCulture = value; } } - + internal static string Symbian_Name { get { return ResourceManager.GetString("Symbian_Name", resourceCulture); } } - + internal static string Symbian_Installation_File { get { return ResourceManager.GetString("Symbian_Installation_File", resourceCulture); } } - + internal static string Symbian_9_1_or_later { get { return ResourceManager.GetString("Symbian_9_1_or_later", resourceCulture); } } - + internal static string Application_ID_0 { get { return ResourceManager.GetString("Application_ID_0", resourceCulture); } } - + internal static string UIDs_checksum_0 { get { return ResourceManager.GetString("UIDs_checksum_0", resourceCulture); } } - + internal static string Symbian_3_or_later { get { return ResourceManager.GetString("Symbian_3_or_later", resourceCulture); } } - + internal static string Symbian_6_or_later { get { return ResourceManager.GetString("Symbian_6_or_later", resourceCulture); } } - + internal static string Unknown_EPOC_magic_0 { get { return ResourceManager.GetString("Unknown_EPOC_magic_0", resourceCulture); } } - + internal static string CRC16_of_header_0 { get { return ResourceManager.GetString("CRC16_of_header_0", resourceCulture); } } - + internal static string SIS_contains_an_application { get { return ResourceManager.GetString("SIS_contains_an_application", resourceCulture); } } - + internal static string File_contains_0_languages { get { return ResourceManager.GetString("File_contains_0_languages", resourceCulture); } } - + internal static string File_contains_0_files_pointer_1 { get { return ResourceManager.GetString("File_contains_0_files_pointer_1", resourceCulture); } } - + internal static string File_contains_0_requisites { get { return ResourceManager.GetString("File_contains_0_requisites", resourceCulture); } } - + internal static string Capabilities { get { return ResourceManager.GetString("Capabilities", resourceCulture); } } - + internal static string Component_version_0_1 { get { return ResourceManager.GetString("Component_version_0_1", resourceCulture); } } - + internal static string Component_name_for_language_with_code_0_1 { get { return ResourceManager.GetString("Component_name_for_language_with_code_0_1", resourceCulture); } } - + internal static string Files_for_all_languages { get { return ResourceManager.GetString("Files_for_all_languages", resourceCulture); } } - + internal static string Files_for_0_language { get { return ResourceManager.GetString("Files_for_0_language", resourceCulture); } } - + internal static string Requisite_0 { get { return ResourceManager.GetString("Requisite_0", resourceCulture); } } - + internal static string Required_UID_0_version_1_2 { get { return ResourceManager.GetString("Required_UID_0_version_1_2", resourceCulture); } } - + internal static string Required_variant_0 { get { return ResourceManager.GetString("Required_variant_0", resourceCulture); } } - + internal static string Requisite_for_language_0_1 { get { return ResourceManager.GetString("Requisite_for_language_0_1", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_1st_Edition { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_1st_Edition", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_1st_Edition_Feature_Pack_1 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_1st_Edition_Feature_Pack_1", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_2nd_Edition { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_2nd_Edition", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_1 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_1", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_2 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_2", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_3 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_2nd_Edition_Feature_Pack_3", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_3rd_Edition { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_3rd_Edition", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_3rd_Edition_Feature_Pack_1 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_3rd_Edition_Feature_Pack_1", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_3rd_Edition_Feature_Pack_2 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_3rd_Edition_Feature_Pack_2", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_5th_Edition { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_5th_Edition", resourceCulture); } } - + internal static string SIS_Platform_UID_Symbian_3 { get { return ResourceManager.GetString("SIS_Platform_UID_Symbian_3", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_Belle { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_Belle", resourceCulture); } } - + internal static string SIS_Platform_UID_0 { get { return ResourceManager.GetString("SIS_Platform_UID_0", resourceCulture); } } - + internal static string Archive_options_0 { get { return ResourceManager.GetString("Archive_options_0", resourceCulture); } } - + internal static string SIS_Platform_UID_UIQ_21 { get { return ResourceManager.GetString("SIS_Platform_UID_UIQ_21", resourceCulture); } } - + internal static string SIS_Platform_UID_UIQ_30 { get { return ResourceManager.GetString("SIS_Platform_UID_UIQ_30", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_7650 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_7650", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_v1_0 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_v1_0", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_3650 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_3650", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6600 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6600", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6630 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6630", resourceCulture); } } - + internal static string SIS_Platform_UID_SonyEricsson_P80x { get { return ResourceManager.GetString("SIS_Platform_UID_SonyEricsson_P80x", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_60_v1_1 { get { return ResourceManager.GetString("SIS_Platform_UID_Series_60_v1_1", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N_Gage { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N_Gage", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_9500 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_9500", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_9300 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_9300", resourceCulture); } } - + internal static string SIS_Platform_UID_Series_80_2nd_edition { get { return ResourceManager.GetString("SIS_Platform_UID_Series_80_2nd_edition", resourceCulture); } } - + internal static string SIS_Platform_UID_Siemens_SX1 { get { return ResourceManager.GetString("SIS_Platform_UID_Siemens_SX1", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6260 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6260", resourceCulture); } } - + internal static string SIS_Platform_UID_SonyEricsson_P90x { get { return ResourceManager.GetString("SIS_Platform_UID_SonyEricsson_P90x", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_7710_Series_90 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_7710_Series_90", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_7610 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_7610", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6670 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6670", resourceCulture); } } - + internal static string SIS_Platform_UID_UIQ_20 { get { return ResourceManager.GetString("SIS_Platform_UID_UIQ_20", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_3230 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_3230", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N90 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N90", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N70 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N70", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6680 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6680", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6620 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6620", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6682 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6682", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_6681 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_6681", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_3250 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_3250", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N80 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N80", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N92 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N92", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N73 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N73", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N91 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N91", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_N71 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_N71", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_E60 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_E60", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_E70 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_E70", resourceCulture); } } - + internal static string SIS_Platform_UID_Nokia_E61 { get { return ResourceManager.GetString("SIS_Platform_UID_Nokia_E61", resourceCulture); } } - + internal static string Option_0 { get { return ResourceManager.GetString("Option_0", resourceCulture); } } - + internal static string Name_for_language_0_1 { get { return ResourceManager.GetString("Name_for_language_0_1", resourceCulture); } } - + internal static string This_file_format_is_not_yet_implemented { get { return ResourceManager.GetString("This_file_format_is_not_yet_implemented", resourceCulture); } } - + internal static string Found_component_name_for_language_0_at_1_with_a_length_of_2_bytes { get { return ResourceManager.GetString("Found_component_name_for_language_0_at_1_with_a_length_of_2_bytes", resourceCulture); } } - + internal static string Conditions { get { return ResourceManager.GetString("Conditions", resourceCulture); } } - + internal static string Console_package { get { return ResourceManager.GetString("Console_package", resourceCulture); } } - + internal static string Live_package { get { return ResourceManager.GetString("Live_package", resourceCulture); } } - + internal static string Microsoft_package { get { return ResourceManager.GetString("Microsoft_package", resourceCulture); } } - + internal static string Certificate_owner_console_ID_0_1_2_3_4 { get { return ResourceManager.GetString("Certificate_owner_console_ID_0_1_2_3_4", resourceCulture); } } - + internal static string Certificate_owner_console_part_number_0 { get { return ResourceManager.GetString("Certificate_owner_console_part_number_0", resourceCulture); } } - + internal static string Certificate_owner_console_type_0 { get { return ResourceManager.GetString("Certificate_owner_console_type_0", resourceCulture); } } - + internal static string Certificate_date_of_generation_0 { get { return ResourceManager.GetString("Certificate_date_of_generation_0", resourceCulture); } } - + internal static string Signatures_SHA1_0 { get { return ResourceManager.GetString("Signatures_SHA1_0", resourceCulture); } } - + internal static string Header_size_0 { get { return ResourceManager.GetString("Header_size_0", resourceCulture); } } - + internal static string Content_type_0 { get { return ResourceManager.GetString("Content_type_0", resourceCulture); } } - + internal static string Content_size_0 { get { return ResourceManager.GetString("Content_size_0", resourceCulture); } } - + internal static string Title_ID_0 { get { return ResourceManager.GetString("Title_ID_0", resourceCulture); } } - + internal static string Media_ID_0 { get { return ResourceManager.GetString("Media_ID_0", resourceCulture); } } - + internal static string Publisher_name_0 { get { return ResourceManager.GetString("Publisher_name_0", resourceCulture); } } - + internal static string Title_name_0 { get { return ResourceManager.GetString("Title_name_0", resourceCulture); } } - + internal static string Metadata_version_0 { get { return ResourceManager.GetString("Metadata_version_0", resourceCulture); } } - + internal static string Version_0 { get { return ResourceManager.GetString("Version_0", resourceCulture); } } - + internal static string Base_version_0 { get { return ResourceManager.GetString("Base_version_0", resourceCulture); } } - + internal static string Descriptor_type_0 { get { return ResourceManager.GetString("Descriptor_type_0", resourceCulture); } } - + internal static string Display_name_0 { get { return ResourceManager.GetString("Display_name_0", resourceCulture); } } - + internal static string Display_description_0 { get { return ResourceManager.GetString("Display_description_0", resourceCulture); } } - + internal static string Device_ID_0 { get { return ResourceManager.GetString("Device_ID_0", resourceCulture); } } - + internal static string Console_ID_0 { get { return ResourceManager.GetString("Console_ID_0", resourceCulture); } } - + internal static string Profile_ID_0 { get { return ResourceManager.GetString("Profile_ID_0", resourceCulture); } } - + internal static string ARC_archive { get { return ResourceManager.GetString("ARC_archive", resourceCulture); } } - + internal static string LHA_archive { get { return ResourceManager.GetString("LHA_archive", resourceCulture); } } - + internal static string AMG_archive { get { return ResourceManager.GetString("AMG_archive", resourceCulture); } } - + internal static string AMG_version_0_1 { get { return ResourceManager.GetString("AMG_version_0_1", resourceCulture); } } - + internal static string Archive_contains_0_files_for_1_bytes { get { return ResourceManager.GetString("Archive_contains_0_files_for_1_bytes", resourceCulture); } } - + internal static string Archive_comment { get { return ResourceManager.GetString("Archive_comment", resourceCulture); } } - + internal static string HA_archive { get { return ResourceManager.GetString("HA_archive", resourceCulture); } } - + internal static string Created_with_HA_version_0 { get { return ResourceManager.GetString("Created_with_HA_version_0", resourceCulture); } } - + internal static string Archive_contains_0_files { get { return ResourceManager.GetString("Archive_contains_0_files", resourceCulture); } } - + internal static string EwfArchive_Name { get { return ResourceManager.GetString("EwfArchive_Name", resourceCulture); } } - + internal static string Ace_Name { get { return ResourceManager.GetString("Ace_Name", resourceCulture); } } - + internal static string ACE_archive { get { return ResourceManager.GetString("ACE_archive", resourceCulture); } } - + internal static string ACE_version_created_0_1 { get { return ResourceManager.GetString("ACE_version_created_0_1", resourceCulture); } } - + internal static string ACE_version_needed_0_1 { get { return ResourceManager.GetString("ACE_version_needed_0_1", resourceCulture); } } - + internal static string ACE_host_os_0 { get { return ResourceManager.GetString("ACE_host_os_0", resourceCulture); } } - + internal static string ACE_solid_archive { get { return ResourceManager.GetString("ACE_solid_archive", resourceCulture); } } - + internal static string ACE_multi_volume_archive { get { return ResourceManager.GetString("ACE_multi_volume_archive", resourceCulture); } } - + internal static string ACE_locked_archive { get { return ResourceManager.GetString("ACE_locked_archive", resourceCulture); } } - + internal static string ACE_sfx_archive { get { return ResourceManager.GetString("ACE_sfx_archive", resourceCulture); } } - + internal static string ACE_has_recovery_record { get { return ResourceManager.GetString("ACE_has_recovery_record", resourceCulture); } } - + internal static string ACE_volume_number_0 { get { return ResourceManager.GetString("ACE_volume_number_0", resourceCulture); } } + + internal static string ARJ_archive { + get { + return ResourceManager.GetString("ARJ_archive", resourceCulture); + } + } + + internal static string ARJ_Name { + get { + return ResourceManager.GetString("ARJ_Name", resourceCulture); + } + } } } diff --git a/Aaru.Archives/Localization/Localization.es.resx b/Aaru.Archives/Localization/Localization.es.resx index 02f17861d..ad526fee8 100644 --- a/Aaru.Archives/Localization/Localization.es.resx +++ b/Aaru.Archives/Localization/Localization.es.resx @@ -381,4 +381,10 @@ [slateblue1]NĂºmero de volumen: [green]{0}[/][/] + + [bold][blue]Archivo ARJ[/][/] + + + ARJ + \ No newline at end of file diff --git a/Aaru.Archives/Localization/Localization.resx b/Aaru.Archives/Localization/Localization.resx index 9d05b474a..2a89662d7 100644 --- a/Aaru.Archives/Localization/Localization.resx +++ b/Aaru.Archives/Localization/Localization.resx @@ -389,4 +389,10 @@ [slateblue1]Volume number: [green]{0}[/][/] + + [bold][blue]ARJ archive[/][/] + + + ARJ + \ No newline at end of file diff --git a/README.md b/README.md index 73d7fa68d..162e3aa08 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,8 @@ Supported archives * ACE (.ACE) * ARC (.ARC) +* ARJ (.ARJ) +* ARJZ (.ARZ) * Expert Witness Format (EWF) * HA (.HA) * LARC (.LZS)