// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Read.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Reads CPCEMU disk images. // // --[ 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.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Aaru.Checksums; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Decoders.Floppy; using Aaru.Helpers; using Aaru.Logging; namespace Aaru.Images; public sealed partial class Cpcdsk { #region IMediaImage Members /// public ErrorNumber Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); stream.Seek(0, SeekOrigin.Begin); if(stream.Length < 512) return ErrorNumber.InvalidArgument; var headerB = new byte[256]; stream.EnsureRead(headerB, 0, 256); int pos; for(pos = 0; pos < 254; pos++) if(headerB[pos] == 0x0D && headerB[pos + 1] == 0x0A) break; if(pos >= 254) return ErrorNumber.InvalidArgument; string magic = Encoding.ASCII.GetString(headerB, 0, pos); stream.Position = pos + 2; stream.EnsureRead(headerB, 0, 256); DiskInfo header = Marshal.ByteArrayToStructureLittleEndian(headerB); if(!string.Equals(CPCDSK_ID, magic, StringComparison.InvariantCultureIgnoreCase) && !string.Equals(EDSK_ID, magic, StringComparison.InvariantCultureIgnoreCase) && !string.Equals(DU54_ID, magic, StringComparison.InvariantCultureIgnoreCase)) return ErrorNumber.InvalidArgument; _extended = string.Equals(EDSK_ID, magic, StringComparison.InvariantCultureIgnoreCase); AaruLogging.Debug(MODULE_NAME, Localization.Extended_equals_0, _extended); AaruLogging.Debug(MODULE_NAME, Localization.magic_equals_0_quoted, magic); AaruLogging.Debug(MODULE_NAME, "header.magic = \"{0}\"", StringHandlers.CToString(header.magic)); AaruLogging.Debug(MODULE_NAME, "header.creator = \"{0}\"", StringHandlers.CToString(header.creator)); AaruLogging.Debug(MODULE_NAME, "header.tracks = {0}", header.tracks); AaruLogging.Debug(MODULE_NAME, "header.sides = {0}", header.sides); if(!_extended) AaruLogging.Debug(MODULE_NAME, "header.tracksize = {0}", header.tracksize); else { for(var i = 0; i < header.tracks; i++) { for(var j = 0; j < header.sides; j++) { AaruLogging.Debug(MODULE_NAME, Localization.Track_0_Side_1_size_equals_2, i, j, header.tracksizeTable[i * header.sides + j] * 256); } } } ulong currentSector = 0; _sectors = new Dictionary(); _addressMarks = new Dictionary(); ulong readtracks = 0; var allTracksSameSize = true; ulong sectorsPerTrack = 0; // Seek to first track descriptor stream.Seek(256, SeekOrigin.Begin); for(var i = 0; i < header.tracks; i++) { for(var j = 0; j < header.sides; j++) { // Track not stored in image if(_extended && header.tracksizeTable[i * header.sides + j] == 0) continue; long trackPos = stream.Position; var trackB = new byte[256]; stream.EnsureRead(trackB, 0, 256); TrackInfo trackInfo = Marshal.ByteArrayToStructureLittleEndian(trackB); if(!string.Equals(TRACK_ID, Encoding.ASCII.GetString(trackInfo.magic), StringComparison.InvariantCultureIgnoreCase)) { AaruLogging.Error(Localization.Not_the_expected_track_info); return ErrorNumber.InvalidArgument; } AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].magic = \"{0}\"", StringHandlers.CToString(trackInfo.magic), i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].bps = {0}", SizeCodeToBytes(trackInfo.bps), i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].dataRate = {0}", trackInfo.dataRate, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].filler = 0x{0:X2}", trackInfo.filler, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].gap3 = 0x{0:X2}", trackInfo.gap3, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].padding = {0}", trackInfo.padding, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].recordingMode = {0}", trackInfo.recordingMode, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sectors = {0}", trackInfo.sectors, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].side = {0}", trackInfo.side, i, j); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].track = {0}", trackInfo.track, i, j); if(trackInfo.sectors != sectorsPerTrack) { if(sectorsPerTrack == 0) sectorsPerTrack = trackInfo.sectors; else allTracksSameSize = false; } Dictionary thisTrackSectors = new(); Dictionary thisTrackAddressMarks = new(); for(var k = 1; k <= trackInfo.sectors; k++) { AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].id = 0x{0:X2}", trackInfo.sectorsInfo[k - 1].id, i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].len = {0}", trackInfo.sectorsInfo[k - 1].len, i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].side = {0}", trackInfo.sectorsInfo[k - 1].side, i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].size = {0}", SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size), i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].st1 = 0x{0:X2}", trackInfo.sectorsInfo[k - 1].st1, i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].st2 = 0x{0:X2}", trackInfo.sectorsInfo[k - 1].st2, i, j, k); AaruLogging.Debug(MODULE_NAME, "trackInfo[{1}:{2}].sector[{3}].track = {0}", trackInfo.sectorsInfo[k - 1].track, i, j, k); int sectLen = _extended ? trackInfo.sectorsInfo[k - 1].len : SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size); var sector = new byte[sectLen]; stream.EnsureRead(sector, 0, sectLen); if(sectLen < SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size)) { var temp = new byte[SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size)]; Array.Copy(sector, 0, temp, 0, sector.Length); sector = temp; } else if(sectLen > SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size)) { var temp = new byte[SizeCodeToBytes(trackInfo.sectorsInfo[k - 1].size)]; Array.Copy(sector, 0, temp, 0, temp.Length); sector = temp; } thisTrackSectors[(trackInfo.sectorsInfo[k - 1].id & 0x3F) - 1] = sector; var amForCrc = new byte[8]; amForCrc[0] = 0xA1; amForCrc[1] = 0xA1; amForCrc[2] = 0xA1; amForCrc[3] = (byte)IBMIdType.AddressMark; amForCrc[4] = trackInfo.sectorsInfo[k - 1].track; amForCrc[5] = trackInfo.sectorsInfo[k - 1].side; amForCrc[6] = trackInfo.sectorsInfo[k - 1].id; amForCrc[7] = (byte)trackInfo.sectorsInfo[k - 1].size; CRC16IbmContext.Data(amForCrc, 8, out byte[] amCrc); var addressMark = new byte[22]; Array.Copy(amForCrc, 0, addressMark, 12, 8); Array.Copy(amCrc, 0, addressMark, 20, 2); thisTrackAddressMarks[(trackInfo.sectorsInfo[k - 1].id & 0x3F) - 1] = addressMark; } foreach(KeyValuePair s in thisTrackSectors.OrderBy(static k => k.Key)) { _sectors.Add(currentSector, s.Value); _addressMarks.Add(currentSector, s.Value); currentSector++; if(s.Value.Length > _imageInfo.SectorSize) _imageInfo.SectorSize = (uint)s.Value.Length; } stream.Seek(trackPos, SeekOrigin.Begin); if(_extended) { stream.Seek(header.tracksizeTable[i * header.sides + j] * 256, SeekOrigin.Current); _imageInfo.ImageSize += (ulong)(header.tracksizeTable[i * header.sides + j] * 256) - 256; } else { stream.Seek(header.tracksize, SeekOrigin.Current); _imageInfo.ImageSize += (ulong)header.tracksize - 256; } readtracks++; } } AaruLogging.Debug(MODULE_NAME, Localization.Read_0_sectors, _sectors.Count); AaruLogging.Debug(MODULE_NAME, Localization.Read_0_tracks, readtracks); AaruLogging.Debug(MODULE_NAME, Localization.All_tracks_are_same_size_0, allTracksSameSize); _imageInfo.Application = StringHandlers.CToString(header.creator); _imageInfo.CreationTime = imageFilter.CreationTime; _imageInfo.LastModificationTime = imageFilter.LastWriteTime; _imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename); _imageInfo.Sectors = (ulong)_sectors.Count; _imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia; _imageInfo.MediaType = MediaType.CompactFloppy; _imageInfo.ReadableSectorTags.Add(SectorTagType.FloppyAddressMark); _imageInfo.Cylinders = header.tracks; _imageInfo.Heads = header.sides; _imageInfo.SectorsPerTrack = (uint)(_imageInfo.Sectors / (_imageInfo.Cylinders * _imageInfo.Heads)); return ErrorNumber.NoError; } /// public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) { buffer = null; sectorStatus = SectorStatus.NotDumped; if(negative) return ErrorNumber.NotSupported; if(_sectors.TryGetValue(sectorAddress, out buffer)) { sectorStatus = SectorStatus.Dumped; return ErrorNumber.NoError; } sectorStatus = SectorStatus.NotDumped; return ErrorNumber.SectorNotFound; } /// public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer, out SectorStatus[] sectorStatus) { buffer = null; sectorStatus = null; if(negative) return ErrorNumber.NotSupported; if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange; if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange; var ms = new MemoryStream(); sectorStatus = new SectorStatus[length]; for(uint i = 0; i < length; i++) { ErrorNumber errno = ReadSector(sectorAddress + i, false, out byte[] sector, out SectorStatus status); sectorStatus[i] = status; if(errno != ErrorNumber.NoError) return errno; ms.Write(sector, 0, sector.Length); } buffer = ms.ToArray(); return ErrorNumber.NoError; } /// public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) { buffer = null; if(negative) return ErrorNumber.NotSupported; if(tag != SectorTagType.FloppyAddressMark) return ErrorNumber.NotSupported; return _addressMarks.TryGetValue(sectorAddress, out buffer) ? ErrorNumber.NoError : ErrorNumber.SectorNotFound; } /// public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag, out byte[] buffer) { buffer = null; if(negative) return ErrorNumber.NotSupported; if(tag != SectorTagType.FloppyAddressMark) return ErrorNumber.NotSupported; if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange; if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange; var ms = new MemoryStream(); for(uint i = 0; i < length; i++) { ErrorNumber errno = ReadSector(sectorAddress + i, false, out byte[] addressMark, out _); if(errno != ErrorNumber.NoError) return errno; ms.Write(addressMark, 0, addressMark.Length); } buffer = ms.ToArray(); return ErrorNumber.NoError; } #endregion }