// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : AppleMap.cs // Author(s) : Natalia Portillo // // Component : Partitioning scheme plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages Apple Partition Map. // // --[ 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-2023 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Console; using Aaru.Helpers; using Marshal = Aaru.Helpers.Marshal; namespace Aaru.Partitions; // Information about structures learnt from Inside Macintosh // Constants from image testing /// /// Implements decoding of the Apple Partition Map [SuppressMessage("ReSharper", "UnusedMember.Local")] public sealed class AppleMap : IPartition { /// "ER", driver descriptor magic const ushort DDM_MAGIC = 0x4552; /// "PM", new entry magic const ushort APM_MAGIC = 0x504D; /// "TS", old map magic const ushort APM_MAGIC_OLD = 0x5453; /// Old indicator for HFS partition, "TFS1" const uint HFS_MAGIC_OLD = 0x54465331; /// public string Name => Localization.AppleMap_Name; /// public Guid Id => new("36405F8D-4F1A-07F5-209C-223D735D6D22"); /// public string Author => Authors.NataliaPortillo; /// public bool GetInformation(IMediaImage imagePlugin, out List partitions, ulong sectorOffset) { uint sectorSize = imagePlugin.Info.SectorSize is 2352 or 2448 ? 2048 : imagePlugin.Info.SectorSize; partitions = new List(); if(sectorOffset + 2 >= imagePlugin.Info.Sectors) return false; ErrorNumber errno = imagePlugin.ReadSector(sectorOffset, out byte[] ddmSector); if(errno != ErrorNumber.NoError) return false; ushort maxDrivers = 61; switch(sectorSize) { case 256: { byte[] tmp = new byte[512]; Array.Copy(ddmSector, 0, tmp, 0, 256); ddmSector = tmp; maxDrivers = 29; break; } case < 256: return false; } AppleDriverDescriptorMap ddm = Marshal.ByteArrayToStructureBigEndian(ddmSector); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbSig = 0x{0:X4}", ddm.sbSig); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbBlockSize = {0}", ddm.sbBlockSize); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbBlocks = {0}", ddm.sbBlocks); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDevType = {0}", ddm.sbDevType); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDevId = {0}", ddm.sbDevId); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbData = 0x{0:X8}", ddm.sbData); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDrvrCount = {0}", ddm.sbDrvrCount); uint sequence = 0; if(ddm.sbSig == DDM_MAGIC) if(ddm.sbDrvrCount < maxDrivers) { ddm.sbMap = new AppleDriverEntry[ddm.sbDrvrCount]; for(int i = 0; i < ddm.sbDrvrCount; i++) { byte[] tmp = new byte[8]; Array.Copy(ddmSector, 18 + (i * 8), tmp, 0, 8); ddm.sbMap[i] = Marshal.ByteArrayToStructureBigEndian(tmp); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddBlock = {0}", ddm.sbMap[i].ddBlock, i); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddSize = {0}", ddm.sbMap[i].ddSize, i); AaruConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddType = {0}", ddm.sbMap[i].ddType, i); if(ddm.sbMap[i].ddSize == 0) continue; var part = new Partition { Size = (ulong)(ddm.sbMap[i].ddSize * 512), Length = (ulong)(ddm.sbMap[i].ddSize * 512 / sectorSize), Sequence = sequence, Offset = ddm.sbMap[i].ddBlock * sectorSize, Start = ddm.sbMap[i].ddBlock + sectorOffset, Type = "Apple_Driver" }; if(ddm.sbMap[i].ddSize * 512 % sectorSize > 0) part.Length++; partitions.Add(part); sequence++; } } errno = imagePlugin.ReadSector(1 + sectorOffset, out byte[] partSector); if(errno != ErrorNumber.NoError) return false; AppleOldDevicePartitionMap oldMap = Marshal.ByteArrayToStructureBigEndian(partSector); // This is the easy one, no sector size mixing if(oldMap.pdSig == APM_MAGIC_OLD) { for(int i = 2; i < partSector.Length; i += 12) { byte[] tmp = new byte[12]; Array.Copy(partSector, i, tmp, 0, 12); AppleMapOldPartitionEntry oldEntry = Marshal.ByteArrayToStructureBigEndian(tmp); AaruConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdStart = {0}", oldEntry.pdStart, (i - 2) / 12); AaruConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdSize = {0}", oldEntry.pdSize, (i - 2) / 12); AaruConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdFSID = 0x{0:X8}", oldEntry.pdFSID, (i - 2) / 12); if(oldEntry is { pdSize: 0, pdFSID: 0 }) { if(oldEntry.pdStart == 0) break; continue; } var part = new Partition { Size = oldEntry.pdStart * ddm.sbBlockSize, Length = oldEntry.pdStart * ddm.sbBlockSize / sectorSize, Sequence = sequence, Offset = oldEntry.pdSize * ddm.sbBlockSize, Start = oldEntry.pdSize * ddm.sbBlockSize / sectorSize, Scheme = Name, Type = oldEntry.pdFSID == HFS_MAGIC_OLD ? "Apple_HFS" : $"0x{oldEntry.pdFSID:X8}" }; partitions.Add(part); sequence++; } return partitions.Count > 0; } AppleMapPartitionEntry entry; uint entrySize; uint entryCount; uint sectorsToRead; uint skipDdm; // If sector is bigger than 512 if(ddmSector.Length > 512) { byte[] tmp = new byte[512]; Array.Copy(ddmSector, 512, tmp, 0, 512); entry = Marshal.ByteArrayToStructureBigEndian(tmp); // Check for a partition entry that's 512-byte aligned if(entry.signature == APM_MAGIC) { AaruConsole.DebugWriteLine("AppleMap Plugin", Localization.Found_misaligned_entry); entrySize = 512; entryCount = entry.entries; skipDdm = 512; sectorsToRead = ((entryCount + 1) * 512 / sectorSize) + 1; } else { entry = Marshal.ByteArrayToStructureBigEndian(partSector); if(entry.signature == APM_MAGIC) { AaruConsole.DebugWriteLine("AppleMap Plugin", Localization.Found_aligned_entry); entrySize = sectorSize; entryCount = entry.entries; skipDdm = sectorSize; sectorsToRead = entryCount + 2; } else return partitions.Count > 0; } } else { entry = Marshal.ByteArrayToStructureBigEndian(partSector); if(entry.signature == APM_MAGIC) { AaruConsole.DebugWriteLine("AppleMap Plugin", Localization.Found_aligned_entry); entrySize = sectorSize; entryCount = entry.entries; skipDdm = sectorSize; sectorsToRead = entryCount + 2; } else return partitions.Count > 0; } errno = imagePlugin.ReadSectors(sectorOffset, sectorsToRead, out byte[] entries); if(errno != ErrorNumber.NoError) return false; AaruConsole.DebugWriteLine("AppleMap Plugin", "entry_size = {0}", entrySize); AaruConsole.DebugWriteLine("AppleMap Plugin", "entry_count = {0}", entryCount); AaruConsole.DebugWriteLine("AppleMap Plugin", "skip_ddm = {0}", skipDdm); AaruConsole.DebugWriteLine("AppleMap Plugin", "sectors_to_read = {0}", sectorsToRead); byte[] copy = new byte[entries.Length - skipDdm]; Array.Copy(entries, skipDdm, copy, 0, copy.Length); entries = copy; for(int i = 0; i < entryCount; i++) { byte[] tmp = new byte[entrySize]; Array.Copy(entries, i * entrySize, tmp, 0, entrySize); entry = Marshal.ByteArrayToStructureBigEndian(tmp); if(entry.signature != APM_MAGIC) continue; AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].signature = 0x{1:X4}", i, entry.signature); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].reserved1 = 0x{1:X4}", i, entry.reserved1); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entries = {1}", i, entry.entries); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].start = {1}", i, entry.start); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].sectors = {1}", i, entry.sectors); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].name = \"{1}\"", i, StringHandlers.CToString(entry.name)); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].type = \"{1}\"", i, StringHandlers.CToString(entry.type)); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].first_data_block = {1}", i, entry.first_data_block); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].data_sectors = {1}", i, entry.data_sectors); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].flags = {1}", i, (AppleMapFlags)entry.flags); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].first_boot_block = {1}", i, entry.first_boot_block); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].boot_size = {1}", i, entry.boot_size); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].load_address = 0x{1:X8}", i, entry.load_address); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].load_address2 = 0x{1:X8}", i, entry.load_address2); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entry_point = 0x{1:X8}", i, entry.entry_point); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entry_point2 = 0x{1:X8}", i, entry.entry_point2); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].checksum = 0x{1:X8}", i, entry.checksum); AaruConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].processor = \"{1}\"", i, StringHandlers.CToString(entry.processor)); var flags = (AppleMapFlags)entry.flags; // BeOS doesn't mark its partitions as valid //if(flags.HasFlag(AppleMapFlags.Valid) && if(StringHandlers.CToString(entry.type) == "Apple_partition_map" || entry.sectors <= 0) continue; var sb = new StringBuilder(); var partition = new Partition { Sequence = sequence, Type = StringHandlers.CToString(entry.type), Name = StringHandlers.CToString(entry.name), Offset = entry.start * entrySize, Size = entry.sectors * entrySize, Start = (entry.start * entrySize / sectorSize) + sectorOffset, Length = entry.sectors * entrySize / sectorSize, Scheme = Name }; sb.AppendLine(Localization.Partition_flags); if(flags.HasFlag(AppleMapFlags.Valid)) sb.AppendLine(Localization.Partition_is_valid); if(flags.HasFlag(AppleMapFlags.Allocated)) sb.AppendLine(Localization.Partition_entry_is_allocated); if(flags.HasFlag(AppleMapFlags.InUse)) sb.AppendLine(Localization.Partition_is_in_use); if(flags.HasFlag(AppleMapFlags.Bootable)) sb.AppendLine(Localization.Partition_is_bootable); if(flags.HasFlag(AppleMapFlags.Readable)) sb.AppendLine(Localization.Partition_is_readable); if(flags.HasFlag(AppleMapFlags.Writable)) sb.AppendLine(Localization.Partition_is_writable); if(flags.HasFlag(AppleMapFlags.Bootable)) { sb.AppendFormat(Localization.First_boot_sector_0, entry.first_boot_block * entrySize / sectorSize). AppendLine(); sb.AppendFormat(Localization.Boot_is_0_bytes, entry.boot_size).AppendLine(); sb.AppendFormat(Localization.Boot_load_address_0_X8, entry.load_address).AppendLine(); sb.AppendFormat(Localization.Boot_entry_point_0_X8, entry.entry_point).AppendLine(); sb.AppendFormat(Localization.Boot_code_checksum_0_X8, entry.checksum).AppendLine(); sb.AppendFormat(Localization.Processor_0, StringHandlers.CToString(entry.processor)).AppendLine(); if(flags.HasFlag(AppleMapFlags.PicCode)) sb.AppendLine(Localization.Partition_boot_code_is_position_independent); } partition.Description = sb.ToString(); if(partition.Start < imagePlugin.Info.Sectors && partition.End < imagePlugin.Info.Sectors) { partitions.Add(partition); sequence++; } // Some CD and DVDs end with an Apple_Free that expands beyond the disc size... else if(partition.Start < imagePlugin.Info.Sectors) { AaruConsole.DebugWriteLine("AppleMap Plugin", Localization.Cutting_last_partition_end_0_to_media_size_1, partition.End, imagePlugin.Info.Sectors - 1); partition.Length = imagePlugin.Info.Sectors - partition.Start; partitions.Add(partition); sequence++; } else AaruConsole.DebugWriteLine("AppleMap Plugin", Localization.Not_adding_partition_because_start_0_is_outside_media_size_1, partition.Start, imagePlugin.Info.Sectors - 1); } return partitions.Count > 0; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleDriverDescriptorMap { /// Signature public readonly ushort sbSig; /// Bytes per sector public readonly ushort sbBlockSize; /// Sectors of the disk public readonly uint sbBlocks; /// Device type public readonly ushort sbDevType; /// Device ID public readonly ushort sbDevId; /// Reserved public readonly uint sbData; /// Number of entries of the driver descriptor public readonly ushort sbDrvrCount; /// Entries of the driver descriptor [MarshalAs(UnmanagedType.ByValArray, SizeConst = 61)] public AppleDriverEntry[] sbMap; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleDriverEntry { /// First sector of the driver public readonly uint ddBlock; /// Size in 512bytes sectors of the driver public readonly ushort ddSize; /// Operating system (MacOS = 1) public readonly ushort ddType; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleOldDevicePartitionMap { /// Signature public readonly ushort pdSig; /// Entries of the driver descriptor [MarshalAs(UnmanagedType.ByValArray, SizeConst = 42)] public readonly AppleMapOldPartitionEntry[] pdMap; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleMapOldPartitionEntry { /// First sector of the partition public readonly uint pdStart; /// Number of sectors of the partition public readonly uint pdSize; /// Partition type public readonly uint pdFSID; } [Flags] enum AppleMapFlags : uint { /// Partition is valid Valid = 0x01, /// Partition is allocated Allocated = 0x02, /// Partition is in use InUse = 0x04, /// Partition is bootable Bootable = 0x08, /// Partition is readable Readable = 0x10, /// Partition is writable Writable = 0x20, /// Partition boot code is position independent PicCode = 0x40, /// OS specific flag Specific1 = 0x80, /// OS specific flag Specific2 = 0x100, /// Unknown, seen in the wild Unknown = 0x200, /// Unknown, seen in the wild Unknown2 = 0x40000000, /// Reserved, not seen in the wild Reserved = 0xBFFFFC00 } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleMapPartitionEntry { /// Signature public readonly ushort signature; /// Reserved public readonly ushort reserved1; /// Number of entries on the partition map, each one sector public readonly uint entries; /// First sector of the partition public readonly uint start; /// Number of sectos of the partition public readonly uint sectors; /// Partition name, 32 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public readonly byte[] name; /// Partition type. 32 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public readonly byte[] type; /// First sector of the data area public readonly uint first_data_block; /// Number of sectors of the data area public readonly uint data_sectors; /// Partition flags public readonly uint flags; /// First sector of the boot code public readonly uint first_boot_block; /// Size in bytes of the boot code public readonly uint boot_size; /// Load address of the boot code public readonly uint load_address; /// Load address of the boot code public readonly uint load_address2; /// Entry point of the boot code public readonly uint entry_point; /// Entry point of the boot code public readonly uint entry_point2; /// Boot code checksum public readonly uint checksum; /// Processor type, 16 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public readonly byte[] processor; /// Boot arguments [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public readonly uint[] boot_arguments; } }