mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 17:56:18 +00:00
Add Compact Pro archive format support with basic functionality
This commit is contained in:
42
Aaru.Archives/CompactPro/CompactPro.cs
Normal file
42
Aaru.Archives/CompactPro/CompactPro.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro : IArchive
|
||||
{
|
||||
const string MODULE_NAME = "Compact Pro Archive Plugin";
|
||||
string _comment;
|
||||
|
||||
Encoding _encoding;
|
||||
List<Entry> _entries;
|
||||
ArchiveSupportedFeature _features;
|
||||
Stream _stream;
|
||||
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "compactpro";
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("B7E2C3A1-4F8D-4E6A-9C1B-3D5F7A2E8B4C");
|
||||
/// <inheritdoc />
|
||||
public string Author => Authors.NataliaPortillo;
|
||||
/// <inheritdoc />
|
||||
public bool Opened { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public ArchiveSupportedFeature ArchiveFeatures => !Opened
|
||||
? ArchiveSupportedFeature.SupportsCompression |
|
||||
ArchiveSupportedFeature.SupportsFilenames |
|
||||
ArchiveSupportedFeature.HasEntryTimestamp |
|
||||
ArchiveSupportedFeature.SupportsSubdirectories |
|
||||
ArchiveSupportedFeature.HasExplicitDirectories |
|
||||
ArchiveSupportedFeature.SupportsXAttrs
|
||||
: _features;
|
||||
/// <inheritdoc />
|
||||
public int NumberOfEntries => Opened ? _entries.Count : -1;
|
||||
|
||||
#endregion
|
||||
}
|
||||
37
Aaru.Archives/CompactPro/Consts.cs
Normal file
37
Aaru.Archives/CompactPro/Consts.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
const int MIN_HEADER_SIZE = 8;
|
||||
|
||||
/// <summary>First byte of a Compact Pro archive is always 0x01.</summary>
|
||||
const byte MARKER = 0x01;
|
||||
|
||||
/// <summary>Encryption flag (bit 0 of entry flags).</summary>
|
||||
const ushort FLAG_ENCRYPTED = 0x01;
|
||||
|
||||
/// <summary>LZH compression for resource fork (bit 1 of entry flags).</summary>
|
||||
const ushort FLAG_RESOURCE_LZH = 0x02;
|
||||
|
||||
/// <summary>LZH compression for data fork (bit 2 of entry flags).</summary>
|
||||
const ushort FLAG_DATA_LZH = 0x04;
|
||||
|
||||
/// <summary>Bit 7 in name length byte indicates a directory entry.</summary>
|
||||
const byte NAME_DIRECTORY_FLAG = 0x80;
|
||||
|
||||
/// <summary>Mask for the actual name length (lower 7 bits).</summary>
|
||||
const byte NAME_LENGTH_MASK = 0x7F;
|
||||
|
||||
/// <summary>Size of file entry metadata (excluding name) in bytes.</summary>
|
||||
const int FILE_METADATA_SIZE = 45;
|
||||
|
||||
/// <summary>Size of directory entry metadata (excluding name) in bytes.</summary>
|
||||
const int DIR_METADATA_SIZE = 2;
|
||||
|
||||
// XAttr name constants
|
||||
const string XATTR_COMMENT = "comment";
|
||||
const string XATTR_APPLE_RESOURCE_FORK = "com.apple.ResourceFork";
|
||||
const string XATTR_APPLE_FINDER_INFO = "com.apple.FinderInfo";
|
||||
const string XATTR_APPLE_HFS_TYPE = "hfs.type";
|
||||
const string XATTR_APPLE_HFS_CREATOR = "hfs.creator";
|
||||
}
|
||||
158
Aaru.Archives/CompactPro/Files.cs
Normal file
158
Aaru.Archives/CompactPro/Files.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Compression.CompactPro;
|
||||
using Aaru.Filters;
|
||||
using Aaru.Helpers.IO;
|
||||
using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetFilename(int entryNumber, out string fileName)
|
||||
{
|
||||
fileName = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
Entry entry = _entries[entryNumber];
|
||||
|
||||
fileName = entry.DirectoryPath is not null
|
||||
? entry.DirectoryPath + "/" + (entry.Filename ?? "")
|
||||
: entry.Filename ?? "";
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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].DataCompressedSize;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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].DataUncompressedSize;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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.DataUncompressedSize / 512,
|
||||
BlockSize = 512,
|
||||
Length = entry.DataUncompressedSize,
|
||||
LastWriteTimeUtc = entry.ModificationTime,
|
||||
CreationTimeUtc = entry.CreationTime
|
||||
};
|
||||
|
||||
if(entry.DataUncompressedSize % 512 != 0) stat.Blocks++;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
if(entry.Encrypted) return ErrorNumber.NotSupported;
|
||||
|
||||
Stream stream;
|
||||
|
||||
if(entry.DataUncompressedSize == 0)
|
||||
stream = new MemoryStream([]);
|
||||
else
|
||||
{
|
||||
stream = new OffsetStream(new NonClosableStream(_stream),
|
||||
entry.DataOffset,
|
||||
entry.DataOffset + entry.DataCompressedSize - 1);
|
||||
|
||||
if(entry.DataLzh)
|
||||
stream = new LzhStream(stream, entry.DataUncompressedSize);
|
||||
else
|
||||
stream = new RleStream(stream, entry.DataUncompressedSize);
|
||||
}
|
||||
|
||||
filter = new ZZZNoFilter();
|
||||
ErrorNumber errno = filter.Open(stream);
|
||||
|
||||
if(errno == ErrorNumber.NoError) return ErrorNumber.NoError;
|
||||
|
||||
stream.Close();
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
57
Aaru.Archives/CompactPro/Info.cs
Normal file
57
Aaru.Archives/CompactPro/Info.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Helpers;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Identify(IFilter filter)
|
||||
{
|
||||
if(filter.DataForkLength < MIN_HEADER_SIZE) return false;
|
||||
|
||||
Stream stream = filter.GetDataForkStream();
|
||||
stream.Position = 0;
|
||||
|
||||
var hdr = new byte[MIN_HEADER_SIZE];
|
||||
stream.ReadExactly(hdr, 0, hdr.Length);
|
||||
|
||||
// First byte must be the marker
|
||||
if(hdr[0] != MARKER) return false;
|
||||
|
||||
// Bytes 4-7 are the big-endian directory offset
|
||||
var dirOffset = BigEndianBitConverter.ToUInt32(hdr, 4);
|
||||
|
||||
// Directory offset must be within the file, with room for at least the header CRC + numEntries + commentLen
|
||||
if(dirOffset + 7 > (ulong)filter.DataForkLength) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetInformation(IFilter filter, Encoding encoding, out string information)
|
||||
{
|
||||
information = "";
|
||||
|
||||
if(filter.DataForkLength < MIN_HEADER_SIZE) return;
|
||||
|
||||
Stream stream = filter.GetDataForkStream();
|
||||
stream.Position = 0;
|
||||
|
||||
var hdr = new byte[MIN_HEADER_SIZE];
|
||||
stream.ReadExactly(hdr, 0, hdr.Length);
|
||||
|
||||
if(hdr[0] != MARKER) return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(Localization.CompactPro_archive);
|
||||
|
||||
information = sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
190
Aaru.Archives/CompactPro/Open.cs
Normal file
190
Aaru.Archives/CompactPro/Open.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Helpers;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
void ParseDirectory(string parentPath, int numEntries)
|
||||
{
|
||||
while(numEntries > 0)
|
||||
{
|
||||
int namelenByte = _stream.ReadByte();
|
||||
|
||||
if(namelenByte < 0) break;
|
||||
|
||||
int nameLen = namelenByte & NAME_LENGTH_MASK;
|
||||
bool isDir = (namelenByte & NAME_DIRECTORY_FLAG) != 0;
|
||||
|
||||
var name = "";
|
||||
|
||||
if(nameLen > 0)
|
||||
{
|
||||
var nameBytes = new byte[nameLen];
|
||||
_stream.ReadExactly(nameBytes, 0, nameLen);
|
||||
name = _encoding.GetString(nameBytes);
|
||||
}
|
||||
|
||||
if(isDir)
|
||||
{
|
||||
// Directory entry: 2 bytes for number of child entries
|
||||
var dirMeta = new byte[DIR_METADATA_SIZE];
|
||||
_stream.ReadExactly(dirMeta, 0, DIR_METADATA_SIZE);
|
||||
|
||||
var numDirEntries = BigEndianBitConverter.ToUInt16(dirMeta, 0);
|
||||
|
||||
string dirPath = parentPath.Length > 0 ? parentPath + "/" + name : name;
|
||||
|
||||
var dirEntry = new Entry
|
||||
{
|
||||
Filename = name,
|
||||
DirectoryPath = parentPath.Length > 0 ? parentPath : null,
|
||||
IsDirectory = true
|
||||
};
|
||||
|
||||
_entries.Add(dirEntry);
|
||||
|
||||
ParseDirectory(dirPath, numDirEntries);
|
||||
|
||||
// The directory entry plus its children count as numDirEntries + 1
|
||||
numEntries -= numDirEntries + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// File entry: 45 bytes of metadata
|
||||
var fileMeta = new byte[FILE_METADATA_SIZE];
|
||||
_stream.ReadExactly(fileMeta, 0, FILE_METADATA_SIZE);
|
||||
|
||||
// Parse fields (all big-endian)
|
||||
// byte volume = fileMeta[0];
|
||||
var fileOffset = BigEndianBitConverter.ToUInt32(fileMeta, 1);
|
||||
var fileType = BigEndianBitConverter.ToUInt32(fileMeta, 5);
|
||||
var creator = BigEndianBitConverter.ToUInt32(fileMeta, 9);
|
||||
var creationDate = BigEndianBitConverter.ToUInt32(fileMeta, 13);
|
||||
var modificationDate = BigEndianBitConverter.ToUInt32(fileMeta, 17);
|
||||
var finderFlags = BigEndianBitConverter.ToUInt16(fileMeta, 21);
|
||||
var crc = BigEndianBitConverter.ToUInt32(fileMeta, 23);
|
||||
var flags = BigEndianBitConverter.ToUInt16(fileMeta, 27);
|
||||
var resourceLen = BigEndianBitConverter.ToUInt32(fileMeta, 29);
|
||||
var dataLen = BigEndianBitConverter.ToUInt32(fileMeta, 33);
|
||||
var resourceCompLen = BigEndianBitConverter.ToUInt32(fileMeta, 37);
|
||||
var dataCompLen = BigEndianBitConverter.ToUInt32(fileMeta, 41);
|
||||
|
||||
var entry = new Entry
|
||||
{
|
||||
Filename = name,
|
||||
DirectoryPath = parentPath.Length > 0 ? parentPath : null,
|
||||
IsDirectory = false,
|
||||
DataOffset = fileOffset + resourceCompLen,
|
||||
DataCompressedSize = dataCompLen,
|
||||
DataUncompressedSize = dataLen,
|
||||
DataLzh = (flags & FLAG_DATA_LZH) != 0,
|
||||
ResourceOffset = fileOffset,
|
||||
ResourceCompressedSize = resourceCompLen,
|
||||
ResourceUncompressedSize = resourceLen,
|
||||
ResourceLzh = (flags & FLAG_RESOURCE_LZH) != 0,
|
||||
FileType = fileType,
|
||||
Creator = creator,
|
||||
FinderFlags = finderFlags,
|
||||
Crc32 = crc,
|
||||
Encrypted = (flags & FLAG_ENCRYPTED) != 0,
|
||||
CreationTime = DateHandlers.MacToDateTime(creationDate),
|
||||
ModificationTime = DateHandlers.MacToDateTime(modificationDate)
|
||||
};
|
||||
|
||||
_entries.Add(entry);
|
||||
|
||||
numEntries--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Open(IFilter filter, Encoding encoding)
|
||||
{
|
||||
if(filter.DataForkLength < MIN_HEADER_SIZE) return ErrorNumber.InvalidArgument;
|
||||
|
||||
_encoding = encoding ?? Encoding.GetEncoding("macintosh");
|
||||
_stream = filter.GetDataForkStream();
|
||||
_stream.Position = 0;
|
||||
|
||||
var hdr = new byte[MIN_HEADER_SIZE];
|
||||
_stream.ReadExactly(hdr, 0, hdr.Length);
|
||||
|
||||
if(hdr[0] != MARKER) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// byte 1 = volume, bytes 2-3 = xmagic (skip)
|
||||
var dirOffset = BigEndianBitConverter.ToUInt32(hdr, 4);
|
||||
|
||||
if(dirOffset + 7 > (ulong)_stream.Length) return ErrorNumber.InvalidArgument;
|
||||
|
||||
_stream.Position = dirOffset;
|
||||
|
||||
// Read directory header: CRC(4) + numEntries(2) + commentLen(1)
|
||||
var dirHdr = new byte[7];
|
||||
_stream.ReadExactly(dirHdr, 0, 7);
|
||||
|
||||
// Skip CRC (bytes 0-3)
|
||||
var numEntries = BigEndianBitConverter.ToUInt16(dirHdr, 4);
|
||||
byte commentLen = dirHdr[6];
|
||||
|
||||
if(commentLen > 0)
|
||||
{
|
||||
var commentBytes = new byte[commentLen];
|
||||
_stream.ReadExactly(commentBytes, 0, commentLen);
|
||||
_comment = _encoding.GetString(commentBytes);
|
||||
}
|
||||
|
||||
_entries = [];
|
||||
|
||||
ParseDirectory("", numEntries);
|
||||
|
||||
_features = ArchiveSupportedFeature.SupportsFilenames | ArchiveSupportedFeature.HasEntryTimestamp;
|
||||
|
||||
var hasCompression = false;
|
||||
var hasSubdirectories = false;
|
||||
var hasDirectories = false;
|
||||
var hasXAttrs = false;
|
||||
|
||||
for(int i = 0, count = _entries.Count; i < count; i++)
|
||||
{
|
||||
Entry entry = _entries[i];
|
||||
|
||||
if(entry.IsDirectory) hasDirectories = true;
|
||||
|
||||
if(entry.DataLzh || entry.ResourceLzh) hasCompression = true;
|
||||
|
||||
if(entry.DirectoryPath is not null) hasSubdirectories = true;
|
||||
|
||||
if(!entry.IsDirectory && (entry.ResourceUncompressedSize > 0 || entry.FileType != 0 || entry.Creator != 0))
|
||||
hasXAttrs = true;
|
||||
}
|
||||
|
||||
if(_comment is not null) hasXAttrs = true;
|
||||
|
||||
if(hasCompression) _features |= ArchiveSupportedFeature.SupportsCompression;
|
||||
if(hasSubdirectories) _features |= ArchiveSupportedFeature.SupportsSubdirectories;
|
||||
if(hasDirectories) _features |= ArchiveSupportedFeature.HasExplicitDirectories;
|
||||
if(hasXAttrs) _features |= ArchiveSupportedFeature.SupportsXAttrs;
|
||||
|
||||
Opened = true;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Close()
|
||||
{
|
||||
if(!Opened) return;
|
||||
|
||||
_entries = null;
|
||||
_comment = null;
|
||||
Opened = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
38
Aaru.Archives/CompactPro/Structs.cs
Normal file
38
Aaru.Archives/CompactPro/Structs.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
#region Nested type: Entry
|
||||
|
||||
struct Entry
|
||||
{
|
||||
public string Filename;
|
||||
public string DirectoryPath;
|
||||
public bool IsDirectory;
|
||||
|
||||
// Data fork
|
||||
public long DataOffset;
|
||||
public long DataCompressedSize;
|
||||
public long DataUncompressedSize;
|
||||
public bool DataLzh;
|
||||
|
||||
// Resource fork
|
||||
public long ResourceOffset;
|
||||
public long ResourceCompressedSize;
|
||||
public long ResourceUncompressedSize;
|
||||
public bool ResourceLzh;
|
||||
|
||||
// Mac metadata
|
||||
public uint FileType;
|
||||
public uint Creator;
|
||||
public ushort FinderFlags;
|
||||
public DateTime CreationTime;
|
||||
public DateTime ModificationTime;
|
||||
public uint Crc32;
|
||||
public bool Encrypted;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
153
Aaru.Archives/CompactPro/Xattrs.cs
Normal file
153
Aaru.Archives/CompactPro/Xattrs.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Compression.CompactPro;
|
||||
using Aaru.Helpers.IO;
|
||||
|
||||
namespace Aaru.Archives;
|
||||
|
||||
public sealed partial class CompactPro
|
||||
{
|
||||
ErrorNumber ReadResourceFork(Entry entry, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
Stream stream = new OffsetStream(new NonClosableStream(_stream),
|
||||
entry.ResourceOffset,
|
||||
entry.ResourceOffset + entry.ResourceCompressedSize - 1);
|
||||
|
||||
try
|
||||
{
|
||||
if(entry.ResourceLzh)
|
||||
stream = new LzhStream(stream, entry.ResourceUncompressedSize);
|
||||
else
|
||||
stream = new RleStream(stream, entry.ResourceUncompressedSize);
|
||||
|
||||
buffer = new byte[stream.Length];
|
||||
stream.Position = 0;
|
||||
stream.ReadExactly(buffer, 0, buffer.Length);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] BuildFinderInfo(Entry entry)
|
||||
{
|
||||
// Classic Mac FinderInfo is 16 bytes: type(4) + creator(4) + flags(2) + location(4) + folder(2)
|
||||
var info = new byte[16];
|
||||
|
||||
info[0] = (byte)(entry.FileType >> 24);
|
||||
info[1] = (byte)(entry.FileType >> 16);
|
||||
info[2] = (byte)(entry.FileType >> 8);
|
||||
info[3] = (byte)entry.FileType;
|
||||
info[4] = (byte)(entry.Creator >> 24);
|
||||
info[5] = (byte)(entry.Creator >> 16);
|
||||
info[6] = (byte)(entry.Creator >> 8);
|
||||
info[7] = (byte)entry.Creator;
|
||||
info[8] = (byte)(entry.FinderFlags >> 8);
|
||||
info[9] = (byte)entry.FinderFlags;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static byte[] TypeCodeToBytes(uint code)
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
bytes[0] = (byte)(code >> 24);
|
||||
bytes[1] = (byte)(code >> 16);
|
||||
bytes[2] = (byte)(code >> 8);
|
||||
bytes[3] = (byte)code;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
#region IArchive Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ListXAttr(int entryNumber, out List<string> xattrs)
|
||||
{
|
||||
xattrs = null;
|
||||
|
||||
if(!Opened) return ErrorNumber.NotOpened;
|
||||
|
||||
if(entryNumber < 0 || entryNumber >= _entries.Count) return ErrorNumber.OutOfRange;
|
||||
|
||||
xattrs = [];
|
||||
|
||||
Entry entry = _entries[entryNumber];
|
||||
|
||||
if(_comment is not null) xattrs.Add(XATTR_COMMENT);
|
||||
|
||||
if(entry.IsDirectory) return ErrorNumber.NoError;
|
||||
|
||||
if(entry.ResourceUncompressedSize > 0) xattrs.Add(XATTR_APPLE_RESOURCE_FORK);
|
||||
if(entry.FileType != 0 || entry.Creator != 0 || entry.FinderFlags != 0) xattrs.Add(XATTR_APPLE_FINDER_INFO);
|
||||
if(entry.FileType != 0) xattrs.Add(XATTR_APPLE_HFS_TYPE);
|
||||
if(entry.Creator != 0) xattrs.Add(XATTR_APPLE_HFS_CREATOR);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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];
|
||||
|
||||
switch(xattr)
|
||||
{
|
||||
case XATTR_COMMENT:
|
||||
if(_comment is null) return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
buffer = Encoding.UTF8.GetBytes(_comment);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
case XATTR_APPLE_RESOURCE_FORK:
|
||||
if(entry.IsDirectory || entry.ResourceUncompressedSize == 0) return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
if(entry.Encrypted) return ErrorNumber.NotSupported;
|
||||
|
||||
return ReadResourceFork(entry, out buffer);
|
||||
|
||||
case XATTR_APPLE_FINDER_INFO:
|
||||
if(entry.IsDirectory) return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
if(entry.FileType == 0 && entry.Creator == 0 && entry.FinderFlags == 0)
|
||||
return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
buffer = BuildFinderInfo(entry);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
case XATTR_APPLE_HFS_TYPE:
|
||||
if(entry.IsDirectory || entry.FileType == 0) return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
buffer = TypeCodeToBytes(entry.FileType);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
case XATTR_APPLE_HFS_CREATOR:
|
||||
if(entry.IsDirectory || entry.Creator == 0) return ErrorNumber.NoSuchExtendedAttribute;
|
||||
|
||||
buffer = TypeCodeToBytes(entry.Creator);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
|
||||
default:
|
||||
return ErrorNumber.NoSuchExtendedAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -878,5 +878,11 @@ namespace Aaru.Archives {
|
||||
return ResourceManager.GetString("ZIP_comment_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string CompactPro_archive {
|
||||
get {
|
||||
return ResourceManager.GetString("CompactPro_archive", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,4 +432,7 @@
|
||||
<data name="ZIP_comment_0" xml:space="preserve">
|
||||
<value>[slateblue1]Comentario del archivo: [green]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="CompactPro_archive" xml:space="preserve">
|
||||
<value>[bold][blue]Archivo Compact Pro[/][/]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -440,4 +440,7 @@
|
||||
<data name="ZIP_comment_0" xml:space="preserve">
|
||||
<value>[slateblue1]Archive comment: [green]{0}[/][/]</value>
|
||||
</data>
|
||||
<data name="CompactPro_archive" xml:space="preserve">
|
||||
<value>[bold][blue]Compact Pro archive[/][/]</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user