// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Info.cs // Author(s) : Natalia Portillo // // Component : Acorn filesystem plugin. // // --[ 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.Text; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru.Logging; using Partition = Aaru.CommonTypes.Partition; namespace Aaru.Filesystems; /// /// Implements detection of Acorn's Advanced Data Filing System (ADFS) public sealed partial class AcornADFS { #region IFilesystem Members // TODO: BBC Master hard disks are untested... /// public bool Identify(IMediaImage imagePlugin, Partition partition) { if(partition.Start >= partition.End) return false; ulong sbSector; uint sectorsToRead; if(imagePlugin.Info.SectorSize < 256) return false; byte[] sector; ErrorNumber errno; // ADFS-S, ADFS-M, ADFS-L, ADFS-D without partitions if(partition.Start == 0) { errno = imagePlugin.ReadSector(0, out sector); if(errno != ErrorNumber.NoError) return false; byte oldChk0 = AcornMapChecksum(sector, 255); OldMapSector0 oldMap0 = Marshal.ByteArrayToStructureLittleEndian(sector); errno = imagePlugin.ReadSector(1, out sector); if(errno != ErrorNumber.NoError) return false; byte oldChk1 = AcornMapChecksum(sector, 255); OldMapSector1 oldMap1 = Marshal.ByteArrayToStructureLittleEndian(sector); AaruLogging.Debug(MODULE_NAME, "oldMap0.checksum = {0}", oldMap0.checksum); AaruLogging.Debug(MODULE_NAME, "oldChk0 = {0}", oldChk0); // According to documentation map1 MUST start on sector 1. On ADFS-D it starts at 0x100, not on sector 1 (0x400) if(oldMap0.checksum == oldChk0 && oldMap1.checksum != oldChk1 && sector.Length >= 512) { errno = imagePlugin.ReadSector(0, out sector); if(errno != ErrorNumber.NoError) return false; byte[] tmp = new byte[256]; Array.Copy(sector, 256, tmp, 0, 256); oldChk1 = AcornMapChecksum(tmp, 255); oldMap1 = Marshal.ByteArrayToStructureLittleEndian(tmp); } AaruLogging.Debug(MODULE_NAME, "oldMap1.checksum = {0}", oldMap1.checksum); AaruLogging.Debug(MODULE_NAME, "oldChk1 = {0}", oldChk1); if(oldMap0.checksum == oldChk0 && oldMap1.checksum == oldChk1 && oldMap0.checksum != 0 && oldMap1.checksum != 0) { sbSector = OLD_DIRECTORY_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = OLD_DIRECTORY_SIZE / imagePlugin.Info.SectorSize; if(OLD_DIRECTORY_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; errno = imagePlugin.ReadSectors(sbSector, sectorsToRead, out sector); if(errno != ErrorNumber.NoError) return false; if(sector.Length > OLD_DIRECTORY_SIZE) { byte[] tmp = new byte[OLD_DIRECTORY_SIZE]; Array.Copy(sector, 0, tmp, 0, OLD_DIRECTORY_SIZE - 53); Array.Copy(sector, sector.Length - 54, tmp, OLD_DIRECTORY_SIZE - 54, 53); sector = tmp; } OldDirectory oldRoot = Marshal.ByteArrayToStructureLittleEndian(sector); byte dirChk = AcornDirectoryChecksum(sector, (int)OLD_DIRECTORY_SIZE - 1); AaruLogging.Debug(MODULE_NAME, "oldRoot.header.magic at 0x200 = {0}", oldRoot.header.magic); AaruLogging.Debug(MODULE_NAME, "oldRoot.tail.magic at 0x200 = {0}", oldRoot.tail.magic); AaruLogging.Debug(MODULE_NAME, "oldRoot.tail.checkByte at 0x200 = {0}", oldRoot.tail.checkByte); AaruLogging.Debug(MODULE_NAME, "dirChk at 0x200 = {0}", dirChk); if(oldRoot.header.magic == OLD_DIR_MAGIC && oldRoot.tail.magic == OLD_DIR_MAGIC || oldRoot.header.magic == NEW_DIR_MAGIC && oldRoot.tail.magic == NEW_DIR_MAGIC) return true; // RISC OS says the old directory can't be in the new location, hard disks created by RISC OS 3.10 do that... sbSector = NEW_DIRECTORY_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = NEW_DIRECTORY_SIZE / imagePlugin.Info.SectorSize; if(NEW_DIRECTORY_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; errno = imagePlugin.ReadSectors(sbSector, sectorsToRead, out sector); if(errno != ErrorNumber.NoError) return false; if(sector.Length > OLD_DIRECTORY_SIZE) { byte[] tmp = new byte[OLD_DIRECTORY_SIZE]; Array.Copy(sector, 0, tmp, 0, OLD_DIRECTORY_SIZE - 53); Array.Copy(sector, sector.Length - 54, tmp, OLD_DIRECTORY_SIZE - 54, 53); sector = tmp; } oldRoot = Marshal.ByteArrayToStructureLittleEndian(sector); dirChk = AcornDirectoryChecksum(sector, (int)OLD_DIRECTORY_SIZE - 1); AaruLogging.Debug(MODULE_NAME, "oldRoot.header.magic at 0x400 = {0}", oldRoot.header.magic); AaruLogging.Debug(MODULE_NAME, "oldRoot.tail.magic at 0x400 = {0}", oldRoot.tail.magic); AaruLogging.Debug(MODULE_NAME, "oldRoot.tail.checkByte at 0x400 = {0}", oldRoot.tail.checkByte); AaruLogging.Debug(MODULE_NAME, "dirChk at 0x400 = {0}", dirChk); if(oldRoot.header.magic == OLD_DIR_MAGIC && oldRoot.tail.magic == OLD_DIR_MAGIC || oldRoot.header.magic == NEW_DIR_MAGIC && oldRoot.tail.magic == NEW_DIR_MAGIC) return true; } } // Partitioning or not, new formats follow: DiscRecord drSb; errno = imagePlugin.ReadSector(partition.Start, out sector); if(errno != ErrorNumber.NoError) return false; byte newChk = NewMapChecksum(sector); AaruLogging.Debug(MODULE_NAME, "newChk = {0}", newChk); AaruLogging.Debug(MODULE_NAME, "map.zoneChecksum = {0}", sector[0]); sbSector = BOOT_BLOCK_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = BOOT_BLOCK_SIZE / imagePlugin.Info.SectorSize; if(BOOT_BLOCK_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; if(sbSector + partition.Start + sectorsToRead >= partition.End) return false; errno = imagePlugin.ReadSectors(sbSector + partition.Start, sectorsToRead, out byte[] bootSector); if(errno != ErrorNumber.NoError) return false; int bootChk = 0; if(bootSector.Length < 512) return false; for(int i = 0; i < 0x1FF; i++) bootChk = (bootChk & 0xFF) + (bootChk >> 8) + bootSector[i]; AaruLogging.Debug(MODULE_NAME, "bootChk = {0}", bootChk); AaruLogging.Debug(MODULE_NAME, "bBlock.checksum = {0}", bootSector[0x1FF]); if(newChk == sector[0] && newChk != 0) { NewMap nmap = Marshal.ByteArrayToStructureLittleEndian(sector); drSb = nmap.discRecord; } else if(bootChk == bootSector[0x1FF]) { BootBlock bBlock = Marshal.ByteArrayToStructureLittleEndian(bootSector); drSb = bBlock.discRecord; } else return false; AaruLogging.Debug(MODULE_NAME, "drSb.log2secsize = {0}", drSb.log2secsize); AaruLogging.Debug(MODULE_NAME, "drSb.idlen = {0}", drSb.idlen); AaruLogging.Debug(MODULE_NAME, "drSb.disc_size_high = {0}", drSb.disc_size_high); AaruLogging.Debug(MODULE_NAME, "drSb.disc_size = {0}", drSb.disc_size); AaruLogging.Debug(MODULE_NAME, "IsNullOrEmpty(drSb.reserved) = {0}", ArrayHelpers.ArrayIsNullOrEmpty(drSb.reserved)); if(drSb.log2secsize is < 8 or > 10) return false; if(drSb.idlen < drSb.log2secsize + 3 || drSb.idlen > 19) return false; if(drSb.disc_size_high >> drSb.log2secsize != 0) return false; if(!ArrayHelpers.ArrayIsNullOrEmpty(drSb.reserved)) return false; ulong bytes = drSb.disc_size_high; bytes *= 0x100000000; bytes += drSb.disc_size; return bytes <= imagePlugin.Info.Sectors * imagePlugin.Info.SectorSize; } // TODO: Find root directory on volumes with DiscRecord // TODO: Support big directories (ADFS-G?) // TODO: Find the real freemap on volumes with DiscRecord, as DiscRecord's discid may be empty but this one isn't /// public void GetInformation(IMediaImage imagePlugin, Partition partition, Encoding encoding, out string information, out FileSystem metadata) { encoding ??= Encoding.GetEncoding("iso-8859-1"); var sbInformation = new StringBuilder(); metadata = new FileSystem(); information = ""; ErrorNumber errno; ulong sbSector; byte[] sector; uint sectorsToRead; ulong bytes; // ADFS-S, ADFS-M, ADFS-L, ADFS-D without partitions if(partition.Start == 0) { errno = imagePlugin.ReadSector(0, out sector); if(errno != ErrorNumber.NoError) return; byte oldChk0 = AcornMapChecksum(sector, 255); OldMapSector0 oldMap0 = Marshal.ByteArrayToStructureLittleEndian(sector); errno = imagePlugin.ReadSector(1, out sector); if(errno != ErrorNumber.NoError) return; byte oldChk1 = AcornMapChecksum(sector, 255); OldMapSector1 oldMap1 = Marshal.ByteArrayToStructureLittleEndian(sector); // According to documentation map1 MUST start on sector 1. On ADFS-D it starts at 0x100, not on sector 1 (0x400) if(oldMap0.checksum == oldChk0 && oldMap1.checksum != oldChk1 && sector.Length >= 512) { errno = imagePlugin.ReadSector(0, out sector); if(errno != ErrorNumber.NoError) return; byte[] tmp = new byte[256]; Array.Copy(sector, 256, tmp, 0, 256); oldChk1 = AcornMapChecksum(tmp, 255); oldMap1 = Marshal.ByteArrayToStructureLittleEndian(tmp); } if(oldMap0.checksum == oldChk0 && oldMap1.checksum == oldChk1 && oldMap0.checksum != 0 && oldMap1.checksum != 0) { bytes = (ulong)((oldMap0.size[2] << 16) + (oldMap0.size[1] << 8) + oldMap0.size[0]) * 256; byte[] namebytes = new byte[10]; for(int i = 0; i < 5; i++) { namebytes[i * 2] = oldMap0.name[i]; namebytes[i * 2 + 1] = oldMap1.name[i]; } metadata = new FileSystem { Bootable = oldMap1.boot != 0, // Or not? Clusters = bytes / imagePlugin.Info.SectorSize, ClusterSize = imagePlugin.Info.SectorSize, Type = FS_TYPE }; if(ArrayHelpers.ArrayIsNullOrEmpty(namebytes)) { sbSector = OLD_DIRECTORY_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = OLD_DIRECTORY_SIZE / imagePlugin.Info.SectorSize; if(OLD_DIRECTORY_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; errno = imagePlugin.ReadSectors(sbSector, sectorsToRead, out sector); if(errno != ErrorNumber.NoError) return; if(sector.Length > OLD_DIRECTORY_SIZE) { byte[] tmp = new byte[OLD_DIRECTORY_SIZE]; Array.Copy(sector, 0, tmp, 0, OLD_DIRECTORY_SIZE - 53); Array.Copy(sector, sector.Length - 54, tmp, OLD_DIRECTORY_SIZE - 54, 53); sector = tmp; } OldDirectory oldRoot = Marshal.ByteArrayToStructureLittleEndian(sector); if(oldRoot.header.magic == OLD_DIR_MAGIC && oldRoot.tail.magic == OLD_DIR_MAGIC) namebytes = oldRoot.tail.name; else { // RISC OS says the old directory can't be in the new location, hard disks created by RISC OS 3.10 do that... sbSector = NEW_DIRECTORY_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = NEW_DIRECTORY_SIZE / imagePlugin.Info.SectorSize; if(NEW_DIRECTORY_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; errno = imagePlugin.ReadSectors(sbSector, sectorsToRead, out sector); if(errno != ErrorNumber.NoError) return; if(sector.Length > OLD_DIRECTORY_SIZE) { byte[] tmp = new byte[OLD_DIRECTORY_SIZE]; Array.Copy(sector, 0, tmp, 0, OLD_DIRECTORY_SIZE - 53); Array.Copy(sector, sector.Length - 54, tmp, OLD_DIRECTORY_SIZE - 54, 53); sector = tmp; } oldRoot = Marshal.ByteArrayToStructureLittleEndian(sector); if(oldRoot.header.magic == OLD_DIR_MAGIC && oldRoot.tail.magic == OLD_DIR_MAGIC) namebytes = oldRoot.tail.name; else { errno = imagePlugin.ReadSectors(sbSector, sectorsToRead, out sector); if(errno != ErrorNumber.NoError) return; if(sector.Length > NEW_DIRECTORY_SIZE) { byte[] tmp = new byte[NEW_DIRECTORY_SIZE]; Array.Copy(sector, 0, tmp, 0, NEW_DIRECTORY_SIZE - 41); Array.Copy(sector, sector.Length - 42, tmp, NEW_DIRECTORY_SIZE - 42, 41); sector = tmp; } NewDirectory newRoot = Marshal.ByteArrayToStructureLittleEndian(sector); if(newRoot.header.magic == NEW_DIR_MAGIC && newRoot.tail.magic == NEW_DIR_MAGIC) namebytes = newRoot.tail.title; } } } sbInformation.AppendLine(Localization.Acorn_Advanced_Disc_Filing_System); sbInformation.AppendLine(); sbInformation.AppendFormat(Localization._0_bytes_per_sector, imagePlugin.Info.SectorSize).AppendLine(); sbInformation.AppendFormat(Localization.Volume_has_0_bytes, bytes).AppendLine(); sbInformation.AppendFormat(Localization.Volume_name_0, StringHandlers.CToString(namebytes, encoding)) .AppendLine(); if(oldMap1.discId > 0) { metadata.VolumeSerial = $"{oldMap1.discId:X4}"; sbInformation.AppendFormat(Localization.Volume_ID_0_X4, oldMap1.discId).AppendLine(); } if(!ArrayHelpers.ArrayIsNullOrEmpty(namebytes)) metadata.VolumeName = StringHandlers.CToString(namebytes, encoding); information = sbInformation.ToString(); return; } } // Partitioning or not, new formats follow: DiscRecord drSb; errno = imagePlugin.ReadSector(partition.Start, out sector); if(errno != ErrorNumber.NoError) return; byte newChk = NewMapChecksum(sector); AaruLogging.Debug(MODULE_NAME, "newChk = {0}", newChk); AaruLogging.Debug(MODULE_NAME, "map.zoneChecksum = {0}", sector[0]); sbSector = BOOT_BLOCK_LOCATION / imagePlugin.Info.SectorSize; sectorsToRead = BOOT_BLOCK_SIZE / imagePlugin.Info.SectorSize; if(BOOT_BLOCK_SIZE % imagePlugin.Info.SectorSize > 0) sectorsToRead++; errno = imagePlugin.ReadSectors(sbSector + partition.Start, sectorsToRead, out byte[] bootSector); if(errno != ErrorNumber.NoError) return; int bootChk = 0; for(int i = 0; i < 0x1FF; i++) bootChk = (bootChk & 0xFF) + (bootChk >> 8) + bootSector[i]; AaruLogging.Debug(MODULE_NAME, "bootChk = {0}", bootChk); AaruLogging.Debug(MODULE_NAME, "bBlock.checksum = {0}", bootSector[0x1FF]); if(newChk == sector[0] && newChk != 0) { NewMap nmap = Marshal.ByteArrayToStructureLittleEndian(sector); drSb = nmap.discRecord; } else if(bootChk == bootSector[0x1FF]) { BootBlock bBlock = Marshal.ByteArrayToStructureLittleEndian(bootSector); drSb = bBlock.discRecord; } else return; AaruLogging.Debug(MODULE_NAME, "drSb.log2secsize = {0}", drSb.log2secsize); AaruLogging.Debug(MODULE_NAME, "drSb.spt = {0}", drSb.spt); AaruLogging.Debug(MODULE_NAME, "drSb.heads = {0}", drSb.heads); AaruLogging.Debug(MODULE_NAME, "drSb.density = {0}", drSb.density); AaruLogging.Debug(MODULE_NAME, "drSb.idlen = {0}", drSb.idlen); AaruLogging.Debug(MODULE_NAME, "drSb.log2bpmb = {0}", drSb.log2bpmb); AaruLogging.Debug(MODULE_NAME, "drSb.skew = {0}", drSb.skew); AaruLogging.Debug(MODULE_NAME, "drSb.bootoption = {0}", drSb.bootoption); AaruLogging.Debug(MODULE_NAME, "drSb.lowsector = {0}", drSb.lowsector); AaruLogging.Debug(MODULE_NAME, "drSb.nzones = {0}", drSb.nzones); AaruLogging.Debug(MODULE_NAME, "drSb.zone_spare = {0}", drSb.zone_spare); AaruLogging.Debug(MODULE_NAME, "drSb.root = {0}", drSb.root); AaruLogging.Debug(MODULE_NAME, "drSb.disc_size = {0}", drSb.disc_size); AaruLogging.Debug(MODULE_NAME, "drSb.disc_id = {0}", drSb.disc_id); AaruLogging.Debug(MODULE_NAME, "drSb.disc_name = {0}", StringHandlers.CToString(drSb.disc_name, encoding)); AaruLogging.Debug(MODULE_NAME, "drSb.disc_type = {0}", drSb.disc_type); AaruLogging.Debug(MODULE_NAME, "drSb.disc_size_high = {0}", drSb.disc_size_high); AaruLogging.Debug(MODULE_NAME, "drSb.flags = {0}", drSb.flags); AaruLogging.Debug(MODULE_NAME, "drSb.nzones_high = {0}", drSb.nzones_high); AaruLogging.Debug(MODULE_NAME, "drSb.format_version = {0}", drSb.format_version); AaruLogging.Debug(MODULE_NAME, "drSb.root_size = {0}", drSb.root_size); if(drSb.log2secsize is < 8 or > 10) return; if(drSb.idlen < drSb.log2secsize + 3 || drSb.idlen > 19) return; if(drSb.disc_size_high >> drSb.log2secsize != 0) return; if(!ArrayHelpers.ArrayIsNullOrEmpty(drSb.reserved)) return; bytes = drSb.disc_size_high; bytes *= 0x100000000; bytes += drSb.disc_size; ulong zones = drSb.nzones_high; zones *= 0x100000000; zones += drSb.nzones; if(bytes > imagePlugin.Info.Sectors * imagePlugin.Info.SectorSize) return; metadata = new FileSystem(); sbInformation.AppendLine(Localization.Acorn_Advanced_Disc_Filing_System); sbInformation.AppendLine(); sbInformation.AppendFormat(Localization.Version_0, drSb.format_version).AppendLine(); sbInformation.AppendFormat(Localization._0_bytes_per_sector, 1 << drSb.log2secsize).AppendLine(); sbInformation.AppendFormat(Localization._0_sectors_per_track, drSb.spt).AppendLine(); sbInformation.AppendFormat(Localization._0_heads, drSb.heads).AppendLine(); sbInformation.AppendFormat(Localization.Density_code_0, drSb.density).AppendLine(); sbInformation.AppendFormat(Localization.Skew_0, drSb.skew).AppendLine(); sbInformation.AppendFormat(Localization.Boot_option_0, drSb.bootoption).AppendLine(); // TODO: What the hell is this field refering to? sbInformation.AppendFormat(Localization.Root_starts_at_frag_0, drSb.root).AppendLine(); //sbInformation.AppendFormat("Root is {0} bytes long", drSb.root_size).AppendLine(); sbInformation.AppendFormat(Localization.Volume_has_0_bytes_in_1_zones, bytes, zones).AppendLine(); sbInformation.AppendFormat(Localization.Volume_flags_0_X4, drSb.flags).AppendLine(); if(drSb.disc_id > 0) { metadata.VolumeSerial = $"{drSb.disc_id:X4}"; sbInformation.AppendFormat(Localization.Volume_ID_0_X4, drSb.disc_id).AppendLine(); } if(!ArrayHelpers.ArrayIsNullOrEmpty(drSb.disc_name)) { string discname = StringHandlers.CToString(drSb.disc_name, encoding); metadata.VolumeName = discname; sbInformation.AppendFormat(Localization.Volume_name_0, discname).AppendLine(); } information = sbInformation.ToString(); metadata.Bootable |= drSb.bootoption != 0; // Or not? metadata.Clusters = bytes / (ulong)(1 << drSb.log2secsize); metadata.ClusterSize = (uint)(1 << drSb.log2secsize); metadata.Type = FS_TYPE; } #endregion }