// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : MacBinary.cs // Author(s) : Natalia Portillo // // Component : Filters. // // --[ Description ] ---------------------------------------------------------- // // Provides a filter to open MacBinary files. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ using System; using System.IO; using System.Runtime.InteropServices; using System.Text; using Aaru.CommonTypes.Attributes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru.Helpers.IO; using Marshal = Aaru.Helpers.Marshal; namespace Aaru.Filters; // TODO: Interpret fdScript /// /// Decodes MacBinary files public sealed partial class MacBinary : IFilter { const uint MAGIC = 0x6D42494E; byte[] _bytes; long _dataForkOff; Header _header; bool _isBytes, _isStream, _isPath; long _rsrcForkOff; Stream _stream; #region Nested type: Header [StructLayout(LayoutKind.Sequential, Pack = 1)] [SwapEndian] partial struct Header { /// 0x00, MacBinary version, 0 public byte version; /// 0x01, Str63 Pascal filename [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public byte[] filename; /// 0x41, File type public uint type; /// 0x45, File creator public uint creator; /// 0x49, High byte of Finder flags public byte finderFlags; /// 0x4A, Must be 0 public byte zero1; /// 0x4B, File's icon vertical position within its window public ushort verticalPosition; /// 0x4D, File's icon horizontal position within its window public ushort horizontalPosition; /// 0x4F, File's window or folder ID public short windowID; /// 0x51, Protected flag public byte protect; /// 0x52, Must be 0 public byte zero2; /// 0x53, Size of data fork public uint dataLength; /// 0x57, Size of resource fork public uint resourceLength; /// 0x5B, File's creation time public uint creationTime; /// 0x5F, File's last modified time public uint modificationTime; /// 0x63, Length of Get Info comment public ushort commentLength; /// 0x65, Low byte of Finder flags public byte finderFlags2; #region MacBinary III /// 0x66, magic identifier, "mBIN" public uint magic; /// 0x6A, fdScript from fxInfo, identifies codepage of filename public byte fdScript; /// 0x6B, fdXFlags from fxInfo, extended Mac OS 8 finder flags public byte fdXFlags; #endregion MacBinary III /// 0x6C, unused public ulong reserved; /// 0x74, Total unpacked files public uint totalPackedFiles; #region MacBinary II /// 0x78, Length of secondary header public ushort secondaryHeaderLength; /// 0x7A, version number of MacBinary that wrote this file, starts at 129 public byte version2; /// 0x7B, version number of MacBinary required to open this file, starts at 129 public byte minVersion; /// 0x7C, CRC of previous bytes public short crc; #endregion MacBinary II /// 0x7E, Reserved for computer type and OS ID public short computerID; } #endregion #region IFilter Members /// public string Name => Localization.MacBinary_Name; /// public Guid Id => new("D7C321D3-E51F-45DF-A150-F6BFDF0D7704"); /// public string Author => Authors.NataliaPortillo; /// public void Close() { _bytes = null; _stream?.Close(); _isBytes = false; _isStream = false; _isPath = false; } /// public string BasePath { get; private set; } /// public DateTime CreationTime { get; private set; } /// public long DataForkLength => _header.dataLength; /// public Stream GetDataForkStream() { if(_header.dataLength == 0) return null; if(_isBytes) return new OffsetStream(_bytes, _dataForkOff, _dataForkOff + _header.dataLength - 1); if(_isStream) return new OffsetStream(_stream, _dataForkOff, _dataForkOff + _header.dataLength - 1); if(_isPath) { return new OffsetStream(BasePath, FileMode.Open, FileAccess.Read, _dataForkOff, _dataForkOff + _header.dataLength - 1); } return null; } /// public string Filename { get; private set; } /// public DateTime LastWriteTime { get; private set; } /// public long Length => _header.dataLength + _header.resourceLength; /// public string ParentFolder => System.IO.Path.GetDirectoryName(BasePath); /// public string Path => BasePath; /// public long ResourceForkLength => _header.resourceLength; /// public Stream GetResourceForkStream() { if(_header.resourceLength == 0) return null; if(_isBytes) return new OffsetStream(_bytes, _rsrcForkOff, _rsrcForkOff + _header.resourceLength - 1); if(_isStream) return new OffsetStream(_stream, _rsrcForkOff, _rsrcForkOff + _header.resourceLength - 1); if(_isPath) { return new OffsetStream(BasePath, FileMode.Open, FileAccess.Read, _rsrcForkOff, _rsrcForkOff + _header.resourceLength - 1); } return null; } /// public bool HasResourceFork => _header.resourceLength > 0; /// public bool Identify(byte[] buffer) { if(buffer == null || buffer.Length < 128) return false; var hdrB = new byte[128]; Array.Copy(buffer, 0, hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); return _header.magic == MAGIC || _header.version == 0 && _header.filename[0] > 0 && _header.filename[0] < 64 && _header.zero1 == 0 && _header is { zero2: 0, reserved: 0 } && (_header.dataLength > 0 || _header.resourceLength > 0); } /// public bool Identify(Stream stream) { if(stream == null || stream.Length < 128) return false; var hdrB = new byte[128]; stream.Seek(0, SeekOrigin.Begin); stream.EnsureRead(hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); return _header.magic == MAGIC || _header.version == 0 && _header.filename[0] > 0 && _header.filename[0] < 64 && _header.zero1 == 0 && _header is { zero2: 0, reserved: 0 } && (_header.dataLength > 0 || _header.resourceLength > 0); } /// public bool Identify(string path) { if(!File.Exists(path)) return false; var fstream = new FileStream(path, FileMode.Open, FileAccess.Read); if(fstream.Length < 128) return false; var hdrB = new byte[128]; fstream.EnsureRead(hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); fstream.Close(); return _header.magic == MAGIC || _header.version == 0 && _header.filename[0] > 0 && _header.filename[0] < 64 && _header.zero1 == 0 && _header is { zero2: 0, reserved: 0 } && (_header.dataLength > 0 || _header.resourceLength > 0); } /// public ErrorNumber Open(byte[] buffer) { var ms = new MemoryStream(buffer); ms.Seek(0, SeekOrigin.Begin); var hdrB = new byte[128]; ms.EnsureRead(hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); uint blocks = 1; blocks += (uint)(_header.secondaryHeaderLength / 128); if(_header.secondaryHeaderLength % 128 > 0) blocks++; _dataForkOff = blocks * 128; blocks += _header.dataLength / 128; if(_header.dataLength % 128 > 0) blocks++; _rsrcForkOff = blocks * 128; Filename = StringHandlers.PascalToString(_header.filename, Encoding.GetEncoding("macintosh")); CreationTime = DateHandlers.MacToDateTime(_header.creationTime); LastWriteTime = DateHandlers.MacToDateTime(_header.modificationTime); ms.Close(); _isBytes = true; _bytes = buffer; return ErrorNumber.NoError; } /// public ErrorNumber Open(Stream stream) { stream.Seek(0, SeekOrigin.Begin); var hdrB = new byte[128]; stream.EnsureRead(hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); uint blocks = 1; blocks += (uint)(_header.secondaryHeaderLength / 128); if(_header.secondaryHeaderLength % 128 > 0) blocks++; _dataForkOff = blocks * 128; blocks += _header.dataLength / 128; if(_header.dataLength % 128 > 0) blocks++; _rsrcForkOff = blocks * 128; Filename = StringHandlers.PascalToString(_header.filename, Encoding.GetEncoding("macintosh")); CreationTime = DateHandlers.MacToDateTime(_header.creationTime); LastWriteTime = DateHandlers.MacToDateTime(_header.modificationTime); stream.Seek(0, SeekOrigin.Begin); _isStream = true; _stream = stream; return ErrorNumber.NoError; } /// public ErrorNumber Open(string path) { var fs = new FileStream(path, FileMode.Open, FileAccess.Read); fs.Seek(0, SeekOrigin.Begin); var hdrB = new byte[128]; fs.EnsureRead(hdrB, 0, 128); _header = Marshal.ByteArrayToStructureBigEndian
(hdrB); uint blocks = 1; blocks += (uint)(_header.secondaryHeaderLength / 128); if(_header.secondaryHeaderLength % 128 > 0) blocks++; _dataForkOff = blocks * 128; blocks += _header.dataLength / 128; if(_header.dataLength % 128 > 0) blocks++; _rsrcForkOff = blocks * 128; Filename = StringHandlers.PascalToString(_header.filename, Encoding.GetEncoding("macintosh")); CreationTime = DateHandlers.MacToDateTime(_header.creationTime); LastWriteTime = DateHandlers.MacToDateTime(_header.modificationTime); fs.Close(); _isPath = true; BasePath = path; return ErrorNumber.NoError; } #endregion }