// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // 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-2018 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using DiscImageChef.Console; namespace DiscImageChef.Partitions { // Information about structures learnt from Inside Macintosh // Constants from image testing public class AppleMap : PartitionPlugin { /// "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 AppleMap() { Name = "Apple Partition Map"; PluginUuid = new Guid("36405F8D-4F1A-07F5-209C-223D735D6D22"); } public override bool GetInformation(DiscImages.ImagePlugin imagePlugin, out List partitions, ulong sectorOffset) { uint sector_size; if(imagePlugin.GetSectorSize() == 2352 || imagePlugin.GetSectorSize() == 2448) sector_size = 2048; else sector_size = imagePlugin.GetSectorSize(); partitions = new List(); if(sectorOffset + 2 >= imagePlugin.GetSectors()) return false; byte[] ddm_sector = imagePlugin.ReadSector(sectorOffset); AppleDriverDescriptorMap ddm; ushort max_drivers = 61; if(sector_size == 256) { byte[] tmp = new byte[512]; Array.Copy(ddm_sector, 0, tmp, 0, 256); ddm_sector = tmp; max_drivers = 29; } else if(sector_size < 256) return false; ddm = BigEndianMarshal.ByteArrayToStructureBigEndian(ddm_sector); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbSig = 0x{0:X4}", ddm.sbSig); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbBlockSize = {0}", ddm.sbBlockSize); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbBlocks = {0}", ddm.sbBlocks); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDevType = {0}", ddm.sbDevType); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDevId = {0}", ddm.sbDevId); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbData = 0x{0:X8}", ddm.sbData); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbDrvrCount = {0}", ddm.sbDrvrCount); uint sequence = 0; if(ddm.sbSig == DDM_MAGIC) { if(ddm.sbDrvrCount < max_drivers) { ddm.sbMap = new AppleDriverEntry[ddm.sbDrvrCount]; for(int i = 0; i < ddm.sbDrvrCount; i++) { byte[] tmp = new byte[8]; Array.Copy(ddm_sector, 18 + i * 8, tmp, 0, 8); ddm.sbMap[i] = BigEndianMarshal.ByteArrayToStructureBigEndian(tmp); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddBlock = {0}", ddm.sbMap[i].ddBlock, i); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddSize = {0}", ddm.sbMap[i].ddSize, i); DicConsole.DebugWriteLine("AppleMap Plugin", "ddm.sbMap[{1}].ddType = {0}", ddm.sbMap[i].ddType, i); if(ddm.sbMap[i].ddSize == 0) continue; CommonTypes.Partition part = new CommonTypes.Partition() { Size = (ulong)(ddm.sbMap[i].ddSize * 512), Length = (ulong)((ddm.sbMap[i].ddSize * 512) / sector_size), Sequence = sequence, Offset = ddm.sbMap[i].ddBlock * sector_size, Start = ddm.sbMap[i].ddBlock + sectorOffset, Type = "Apple_Driver" }; if((ddm.sbMap[i].ddSize * 512) % sector_size > 0) part.Length++; partitions.Add(part); sequence++; } } } byte[] part_sector = imagePlugin.ReadSector(1 + sectorOffset); AppleOldDevicePartitionMap old_map = BigEndianMarshal.ByteArrayToStructureBigEndian(part_sector); // This is the easy one, no sector size mixing if(old_map.pdSig == APM_MAGIC_OLD) { for(int i = 2; i < part_sector.Length; i += 12) { byte[] tmp = new byte[12]; Array.Copy(part_sector, i, tmp, 0, 12); AppleMapOldPartitionEntry old_entry = BigEndianMarshal.ByteArrayToStructureBigEndian(tmp); DicConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdStart = {0}", old_entry.pdStart, (i - 2) / 12); DicConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdSize = {0}", old_entry.pdSize, (i - 2) / 12); DicConsole.DebugWriteLine("AppleMap Plugin", "old_map.sbMap[{1}].pdFSID = 0x{0:X8}", old_entry.pdFSID, (i - 2) / 12); if(old_entry.pdSize == 0 && old_entry.pdFSID == 0) { if(old_entry.pdStart == 0) break; continue; } CommonTypes.Partition part = new CommonTypes.Partition { Size = old_entry.pdStart * ddm.sbBlockSize, Length = (old_entry.pdStart * ddm.sbBlockSize) / sector_size, Sequence = sequence, Offset = old_entry.pdSize * ddm.sbBlockSize, Start = (old_entry.pdSize * ddm.sbBlockSize) / sector_size, Scheme = Name }; if(old_entry.pdFSID == HFS_MAGIC_OLD) part.Type = "Apple_HFS"; else part.Type = string.Format("0x{0:X8}", old_entry.pdFSID); partitions.Add(part); sequence++; } return partitions.Count > 0; } AppleMapPartitionEntry entry; uint entry_size; uint entry_count; uint sectors_to_read; uint skip_ddm; // If sector is bigger than 512 if(sector_size > 512) { byte[] tmp = new byte[512]; Array.Copy(ddm_sector, 512, tmp, 0, 512); entry = BigEndianMarshal.ByteArrayToStructureBigEndian(tmp); // Check for a partition entry that's 512-byte aligned if(entry.signature == APM_MAGIC) { DicConsole.DebugWriteLine("AppleMap Plugin", "Found misaligned entry."); entry_size = 512; entry_count = entry.entries; skip_ddm = 512; sectors_to_read = (((entry_count + 1) * 512) / sector_size) + 1; } else { entry = BigEndianMarshal.ByteArrayToStructureBigEndian(part_sector); if(entry.signature == APM_MAGIC) { DicConsole.DebugWriteLine("AppleMap Plugin", "Found aligned entry."); entry_size = sector_size; entry_count = entry.entries; skip_ddm = sector_size; sectors_to_read = entry_count + 2; } else return partitions.Count > 0; } } else { entry = BigEndianMarshal.ByteArrayToStructureBigEndian(part_sector); if(entry.signature == APM_MAGIC) { DicConsole.DebugWriteLine("AppleMap Plugin", "Found aligned entry."); entry_size = sector_size; entry_count = entry.entries; skip_ddm = sector_size; sectors_to_read = entry_count + 2; } else return partitions.Count > 0; } byte[] entries = imagePlugin.ReadSectors(sectorOffset, sectors_to_read); DicConsole.DebugWriteLine("AppleMap Plugin", "entry_size = {0}", entry_size); DicConsole.DebugWriteLine("AppleMap Plugin", "entry_count = {0}", entry_count); DicConsole.DebugWriteLine("AppleMap Plugin", "skip_ddm = {0}", skip_ddm); DicConsole.DebugWriteLine("AppleMap Plugin", "sectors_to_read = {0}", sectors_to_read); byte[] copy = new byte[entries.Length - skip_ddm]; Array.Copy(entries, skip_ddm, copy, 0, copy.Length); entries = copy; for(int i = 0; i < entry_count; i++) { byte[] tmp = new byte[entry_size]; Array.Copy(entries, i * entry_size, tmp, 0, entry_size); entry = BigEndianMarshal.ByteArrayToStructureBigEndian(tmp); if(entry.signature == APM_MAGIC) { DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].signature = 0x{1:X4}", i, entry.signature); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].reserved1 = 0x{1:X4}", i, entry.reserved1); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entries = {1}", i, entry.entries); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].start = {1}", i, entry.start); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].sectors = {1}", i, entry.sectors); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].name = \"{1}\"", i, StringHandlers.CToString(entry.name)); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].type = \"{1}\"", i, StringHandlers.CToString(entry.type)); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].first_data_block = {1}", i, entry.first_data_block); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].data_sectors = {1}", i, entry.data_sectors); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].flags = {1}", i, (AppleMapFlags)entry.flags); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].first_boot_block = {1}", i, entry.first_boot_block); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].boot_size = {1}", i, entry.boot_size); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].load_address = 0x{1:X8}", i, entry.load_address); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].load_address2 = 0x{1:X8}", i, entry.load_address2); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entry_point = 0x{1:X8}", i, entry.entry_point); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].entry_point2 = 0x{1:X8}", i, entry.entry_point2); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].checksum = 0x{1:X8}", i, entry.checksum); DicConsole.DebugWriteLine("AppleMap Plugin", "dpme[{0}].processor = \"{1}\"", i, StringHandlers.CToString(entry.processor)); AppleMapFlags 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) { StringBuilder sb = new StringBuilder(); CommonTypes.Partition _partition = new CommonTypes.Partition { Sequence = sequence, Type = StringHandlers.CToString(entry.type), Name = StringHandlers.CToString(entry.name), Offset = entry.start * entry_size, Size = entry.sectors * entry_size, Start = ((entry.start * entry_size) / sector_size) + sectorOffset, Length = (entry.sectors * entry_size) / sector_size, Scheme = Name }; sb.AppendLine("Partition flags:"); if(flags.HasFlag(AppleMapFlags.Valid)) sb.AppendLine("Partition is valid."); if(flags.HasFlag(AppleMapFlags.Allocated)) sb.AppendLine("Partition entry is allocated."); if(flags.HasFlag(AppleMapFlags.InUse)) sb.AppendLine("Partition is in use."); if(flags.HasFlag(AppleMapFlags.Bootable)) sb.AppendLine("Partition is bootable."); if(flags.HasFlag(AppleMapFlags.Readable)) sb.AppendLine("Partition is readable."); if(flags.HasFlag(AppleMapFlags.Writable)) sb.AppendLine("Partition is writable."); if(flags.HasFlag(AppleMapFlags.Bootable)) { sb.AppendFormat("First boot sector: {0}", (entry.first_boot_block * entry_size) / sector_size).AppendLine(); sb.AppendFormat("Boot is {0} bytes.", entry.boot_size).AppendLine(); sb.AppendFormat("Boot load address: 0x{0:X8}", entry.load_address).AppendLine(); sb.AppendFormat("Boot entry point: 0x{0:X8}", entry.entry_point).AppendLine(); sb.AppendFormat("Boot code checksum: 0x{0:X8}", entry.checksum).AppendLine(); sb.AppendFormat("Processor: {0}", StringHandlers.CToString(entry.processor)).AppendLine(); if(flags.HasFlag(AppleMapFlags.PicCode)) sb.AppendLine("Partition's boot code is position independent."); } _partition.Description = sb.ToString(); if(_partition.Start < imagePlugin.ImageInfo.Sectors && _partition.End < imagePlugin.ImageInfo.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.ImageInfo.Sectors) { DicConsole.DebugWriteLine("AppleMap Plugin", "Cutting last partition end ({0}) to media size ({1})", _partition.End, imagePlugin.ImageInfo.Sectors - 1); _partition.Length = imagePlugin.ImageInfo.Sectors - _partition.Start; partitions.Add(_partition); sequence++; } else { DicConsole.DebugWriteLine("AppleMap Plugin", "Not adding partition becaus start ({0}) is outside media size ({1})", _partition.Start, imagePlugin.ImageInfo.Sectors - 1); } } } } return partitions.Count > 0; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleDriverDescriptorMap { /// Signature public ushort sbSig; /// Byter per sector public ushort sbBlockSize; /// Sectors of the disk public uint sbBlocks; /// Device type public ushort sbDevType; /// Device ID public ushort sbDevId; /// Reserved public uint sbData; /// Number of entries of the driver descriptor public 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 uint ddBlock; /// Size in 512bytes sectors of the driver public ushort ddSize; /// Operating system (MacOS = 1) public ushort ddType; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleOldDevicePartitionMap { /// Signature public ushort pdSig; /// Entries of the driver descriptor [MarshalAs(UnmanagedType.ByValArray, SizeConst = 42)] public AppleMapOldPartitionEntry[] pdMap; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct AppleMapOldPartitionEntry { /// First sector of the partition public uint pdStart; /// Number of sectors of the partition public uint pdSize; /// Partition type public 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 ushort signature; /// Reserved public ushort reserved1; /// Number of entries on the partition map, each one sector public uint entries; /// First sector of the partition public uint start; /// Number of sectos of the partition public uint sectors; /// Partition name, 32 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] name; /// Partition type. 32 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] type; /// First sector of the data area public uint first_data_block; /// Number of sectors of the data area public uint data_sectors; /// Partition flags public uint flags; /// First sector of the boot code public uint first_boot_block; /// Size in bytes of the boot code public uint boot_size; /// Load address of the boot code public uint load_address; /// Load address of the boot code public uint load_address2; /// Entry point of the boot code public uint entry_point; /// Entry point of the boot code public uint entry_point2; /// Boot code checksum public uint checksum; /// Processor type, 16 bytes, null-padded [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] processor; /// Boot arguments [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public uint[] boot_arguments; } } }