// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Read.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Reads MAME Compressed Hunks of Data 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-2020 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Exceptions; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.CommonTypes.Structs.Devices.ATA; using Aaru.Console; using Aaru.Decoders.CD; using Aaru.Helpers; using Session = Aaru.CommonTypes.Structs.Session; namespace Aaru.DiscImages { public sealed partial class Chd { [SuppressMessage("ReSharper", "UnusedVariable")] public bool Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); stream.Seek(0, SeekOrigin.Begin); byte[] magic = new byte[8]; stream.Read(magic, 0, 8); if(!_chdTag.SequenceEqual(magic)) return false; // Read length byte[] buffer = new byte[4]; stream.Read(buffer, 0, 4); uint length = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0); buffer = new byte[4]; stream.Read(buffer, 0, 4); uint version = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0); buffer = new byte[length]; stream.Seek(0, SeekOrigin.Begin); stream.Read(buffer, 0, (int)length); ulong nextMetaOff = 0; switch(version) { case 1: { HeaderV1 hdrV1 = Marshal.ByteArrayToStructureBigEndian(buffer); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV1.tag)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.length = {0} bytes", hdrV1.length); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.version = {0}", hdrV1.version); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.flags = {0}", (Flags)hdrV1.flags); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.compression = {0}", (Compression)hdrV1.compression); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.hunksize = {0}", hdrV1.hunksize); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.totalhunks = {0}", hdrV1.totalhunks); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.cylinders = {0}", hdrV1.cylinders); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.heads = {0}", hdrV1.heads); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.sectors = {0}", hdrV1.sectors); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV1.md5)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV1.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV1.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV1.parentmd5)); AaruConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); DateTime start = DateTime.UtcNow; _hunkTable = new ulong[hdrV1.totalhunks]; uint hunkSectorCount = (uint)Math.Ceiling(((double)hdrV1.totalhunks * 8) / 512); byte[] hunkSectorBytes = new byte[512]; for(int i = 0; i < hunkSectorCount; i++) { stream.Read(hunkSectorBytes, 0, 512); // This does the big-endian trick but reverses the order of elements also Array.Reverse(hunkSectorBytes); HunkSector hunkSector = Marshal.ByteArrayToStructureLittleEndian(hunkSectorBytes); // This restores the order of elements Array.Reverse(hunkSector.hunkEntry); if(_hunkTable.Length >= ((i * 512) / 8) + (512 / 8)) Array.Copy(hunkSector.hunkEntry, 0, _hunkTable, (i * 512) / 8, 512 / 8); else Array.Copy(hunkSector.hunkEntry, 0, _hunkTable, (i * 512) / 8, _hunkTable.Length - ((i * 512) / 8)); } DateTime end = DateTime.UtcNow; AaruConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); _imageInfo.MediaType = MediaType.GENERIC_HDD; _imageInfo.Sectors = hdrV1.hunksize * hdrV1.totalhunks; _imageInfo.XmlMediaType = XmlMediaType.BlockMedia; _imageInfo.SectorSize = 512; _imageInfo.Version = "1"; _imageInfo.ImageSize = _imageInfo.SectorSize * hdrV1.hunksize * hdrV1.totalhunks; _totalHunks = hdrV1.totalhunks; _sectorsPerHunk = hdrV1.hunksize; _hdrCompression = hdrV1.compression; _mapVersion = 1; _isHdd = true; _imageInfo.Cylinders = hdrV1.cylinders; _imageInfo.Heads = hdrV1.heads; _imageInfo.SectorsPerTrack = hdrV1.sectors; break; } case 2: { HeaderV2 hdrV2 = Marshal.ByteArrayToStructureBigEndian(buffer); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV2.tag)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.length = {0} bytes", hdrV2.length); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.version = {0}", hdrV2.version); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.flags = {0}", (Flags)hdrV2.flags); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.compression = {0}", (Compression)hdrV2.compression); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.hunksize = {0}", hdrV2.hunksize); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.totalhunks = {0}", hdrV2.totalhunks); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.cylinders = {0}", hdrV2.cylinders); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.heads = {0}", hdrV2.heads); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.sectors = {0}", hdrV2.sectors); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV2.md5)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV2.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV2.parentmd5)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV2.seclen = {0}", hdrV2.seclen); AaruConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); DateTime start = DateTime.UtcNow; _hunkTable = new ulong[hdrV2.totalhunks]; // How many sectors uses the BAT uint hunkSectorCount = (uint)Math.Ceiling(((double)hdrV2.totalhunks * 8) / 512); byte[] hunkSectorBytes = new byte[512]; for(int i = 0; i < hunkSectorCount; i++) { stream.Read(hunkSectorBytes, 0, 512); // This does the big-endian trick but reverses the order of elements also Array.Reverse(hunkSectorBytes); HunkSector hunkSector = Marshal.ByteArrayToStructureLittleEndian(hunkSectorBytes); // This restores the order of elements Array.Reverse(hunkSector.hunkEntry); if(_hunkTable.Length >= ((i * 512) / 8) + (512 / 8)) Array.Copy(hunkSector.hunkEntry, 0, _hunkTable, (i * 512) / 8, 512 / 8); else Array.Copy(hunkSector.hunkEntry, 0, _hunkTable, (i * 512) / 8, _hunkTable.Length - ((i * 512) / 8)); } DateTime end = DateTime.UtcNow; AaruConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); _imageInfo.MediaType = MediaType.GENERIC_HDD; _imageInfo.Sectors = hdrV2.hunksize * hdrV2.totalhunks; _imageInfo.XmlMediaType = XmlMediaType.BlockMedia; _imageInfo.SectorSize = hdrV2.seclen; _imageInfo.Version = "2"; _imageInfo.ImageSize = _imageInfo.SectorSize * hdrV2.hunksize * hdrV2.totalhunks; _totalHunks = hdrV2.totalhunks; _sectorsPerHunk = hdrV2.hunksize; _hdrCompression = hdrV2.compression; _mapVersion = 1; _isHdd = true; _imageInfo.Cylinders = hdrV2.cylinders; _imageInfo.Heads = hdrV2.heads; _imageInfo.SectorsPerTrack = hdrV2.sectors; break; } case 3: { HeaderV3 hdrV3 = Marshal.ByteArrayToStructureBigEndian(buffer); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV3.tag)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.length = {0} bytes", hdrV3.length); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.version = {0}", hdrV3.version); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.flags = {0}", (Flags)hdrV3.flags); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.compression = {0}", (Compression)hdrV3.compression); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.totalhunks = {0}", hdrV3.totalhunks); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.logicalbytes = {0}", hdrV3.logicalbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.metaoffset = {0}", hdrV3.metaoffset); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.md5)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.parentmd5 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentmd5) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV3.parentmd5)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.hunkbytes = {0}", hdrV3.hunkbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.sha1)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV3.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV3.parentsha1)); AaruConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); DateTime start = DateTime.UtcNow; _hunkMap = new byte[hdrV3.totalhunks * 16]; stream.Read(_hunkMap, 0, _hunkMap.Length); DateTime end = DateTime.UtcNow; AaruConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); nextMetaOff = hdrV3.metaoffset; _imageInfo.ImageSize = hdrV3.logicalbytes; _imageInfo.Version = "3"; _totalHunks = hdrV3.totalhunks; _bytesPerHunk = hdrV3.hunkbytes; _hdrCompression = hdrV3.compression; _mapVersion = 3; break; } case 4: { HeaderV4 hdrV4 = Marshal.ByteArrayToStructureBigEndian(buffer); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV4.tag)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.length = {0} bytes", hdrV4.length); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.version = {0}", hdrV4.version); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.flags = {0}", (Flags)hdrV4.flags); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.compression = {0}", (Compression)hdrV4.compression); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.totalhunks = {0}", hdrV4.totalhunks); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.logicalbytes = {0}", hdrV4.logicalbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.metaoffset = {0}", hdrV4.metaoffset); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.hunkbytes = {0}", hdrV4.hunkbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.sha1)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV4.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV4.parentsha1)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV4.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.rawsha1)); AaruConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); DateTime start = DateTime.UtcNow; _hunkMap = new byte[hdrV4.totalhunks * 16]; stream.Read(_hunkMap, 0, _hunkMap.Length); DateTime end = DateTime.UtcNow; AaruConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); nextMetaOff = hdrV4.metaoffset; _imageInfo.ImageSize = hdrV4.logicalbytes; _imageInfo.Version = "4"; _totalHunks = hdrV4.totalhunks; _bytesPerHunk = hdrV4.hunkbytes; _hdrCompression = hdrV4.compression; _mapVersion = 3; break; } case 5: { // TODO: Check why reading is misaligned throw new ImageNotSupportedException("CHD version 5 is not yet supported."); HeaderV5 hdrV5 = Marshal.ByteArrayToStructureBigEndian(buffer); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV5.tag)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.length = {0} bytes", hdrV5.length); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.version = {0}", hdrV5.version); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor0 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter. GetBytes(hdrV5.compressor0))); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor1 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter. GetBytes(hdrV5.compressor1))); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor2 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter. GetBytes(hdrV5.compressor2))); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.compressor3 = \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter. GetBytes(hdrV5.compressor3))); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.logicalbytes = {0}", hdrV5.logicalbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.mapoffset = {0}", hdrV5.mapoffset); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.metaoffset = {0}", hdrV5.metaoffset); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.hunkbytes = {0}", hdrV5.hunkbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.unitbytes = {0}", hdrV5.unitbytes); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.sha1)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.parentsha1 = {0}", ArrayHelpers.ArrayIsNullOrEmpty(hdrV5.parentsha1) ? "null" : ArrayHelpers.ByteArrayToHex(hdrV5.parentsha1)); AaruConsole.DebugWriteLine("CHD plugin", "hdrV5.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.rawsha1)); // TODO: Implement compressed CHD v5 if(hdrV5.compressor0 == 0) { AaruConsole.DebugWriteLine("CHD plugin", "Reading Hunk map."); DateTime start = DateTime.UtcNow; _hunkTableSmall = new uint[hdrV5.logicalbytes / hdrV5.hunkbytes]; uint hunkSectorCount = (uint)Math.Ceiling(((double)_hunkTableSmall.Length * 4) / 512); byte[] hunkSectorBytes = new byte[512]; stream.Seek((long)hdrV5.mapoffset, SeekOrigin.Begin); for(int i = 0; i < hunkSectorCount; i++) { stream.Read(hunkSectorBytes, 0, 512); // This does the big-endian trick but reverses the order of elements also Array.Reverse(hunkSectorBytes); HunkSectorSmall hunkSector = Marshal.ByteArrayToStructureLittleEndian(hunkSectorBytes); // This restores the order of elements Array.Reverse(hunkSector.hunkEntry); if(_hunkTableSmall.Length >= ((i * 512) / 4) + (512 / 4)) Array.Copy(hunkSector.hunkEntry, 0, _hunkTableSmall, (i * 512) / 4, 512 / 4); else Array.Copy(hunkSector.hunkEntry, 0, _hunkTableSmall, (i * 512) / 4, _hunkTableSmall.Length - ((i * 512) / 4)); } DateTime end = DateTime.UtcNow; AaruConsole.DebugWriteLine("CHD plugin", "Took {0} seconds", (end - start).TotalSeconds); } else throw new ImageNotSupportedException("Cannot read compressed CHD version 5"); nextMetaOff = hdrV5.metaoffset; _imageInfo.ImageSize = hdrV5.logicalbytes; _imageInfo.Version = "5"; _totalHunks = (uint)(hdrV5.logicalbytes / hdrV5.hunkbytes); _bytesPerHunk = hdrV5.hunkbytes; _hdrCompression = hdrV5.compressor0; _hdrCompression1 = hdrV5.compressor1; _hdrCompression2 = hdrV5.compressor2; _hdrCompression3 = hdrV5.compressor3; _mapVersion = 5; break; } default: throw new ImageNotSupportedException($"Unsupported CHD version {version}"); } if(_mapVersion >= 3) { _isCdrom = false; _isHdd = false; _isGdrom = false; _swapAudio = false; _tracks = new Dictionary(); AaruConsole.DebugWriteLine("CHD plugin", "Reading metadata."); ulong currentSector = 0; uint currentTrack = 1; while(nextMetaOff > 0) { byte[] hdrBytes = new byte[16]; stream.Seek((long)nextMetaOff, SeekOrigin.Begin); stream.Read(hdrBytes, 0, hdrBytes.Length); MetadataHeader header = Marshal.ByteArrayToStructureBigEndian(hdrBytes); byte[] meta = new byte[header.flagsAndLength & 0xFFFFFF]; stream.Read(meta, 0, meta.Length); AaruConsole.DebugWriteLine("CHD plugin", "Found metadata \"{0}\"", Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(header.tag))); switch(header.tag) { // "GDDD" case HARD_DISK_METADATA: if(_isCdrom || _isGdrom) throw new ImageNotSupportedException("Image cannot be a hard disk and a C/GD-ROM at the same time, aborting."); string gddd = StringHandlers.CToString(meta); var gdddRegEx = new Regex(REGEX_METADATA_HDD); Match gdddMatch = gdddRegEx.Match(gddd); if(gdddMatch.Success) { _isHdd = true; _imageInfo.SectorSize = uint.Parse(gdddMatch.Groups["bps"].Value); _imageInfo.Cylinders = uint.Parse(gdddMatch.Groups["cylinders"].Value); _imageInfo.Heads = uint.Parse(gdddMatch.Groups["heads"].Value); _imageInfo.SectorsPerTrack = uint.Parse(gdddMatch.Groups["sectors"].Value); } break; // "CHCD" case CDROM_OLD_METADATA: if(_isHdd) throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); if(_isGdrom) throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); uint chdTracksNumber = BigEndianBitConverter.ToUInt32(meta, 0); // Byteswapped if(chdTracksNumber > 99) chdTracksNumber = BigEndianBitConverter.ToUInt32(meta, 0); currentSector = 0; for(uint i = 0; i < chdTracksNumber; i++) { var chdTrack = new TrackOld { type = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 0)), subType = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 4)), dataSize = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 8)), subSize = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 12)), frames = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 16)), extraFrames = BigEndianBitConverter.ToUInt32(meta, (int)(4 + (i * 24) + 20)) }; var aaruTrack = new Track(); switch((TrackTypeOld)chdTrack.type) { case TrackTypeOld.Audio: aaruTrack.TrackBytesPerSector = 2352; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.Audio; break; case TrackTypeOld.Mode1: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode1; break; case TrackTypeOld.Mode1Raw: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode1; break; case TrackTypeOld.Mode2: case TrackTypeOld.Mode2FormMix: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2336; aaruTrack.TrackType = TrackType.CdMode2Formless; break; case TrackTypeOld.Mode2Form1: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode2Form1; break; case TrackTypeOld.Mode2Form2: aaruTrack.TrackBytesPerSector = 2324; aaruTrack.TrackRawBytesPerSector = 2324; aaruTrack.TrackType = TrackType.CdMode2Form2; break; case TrackTypeOld.Mode2Raw: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode2Formless; break; default: throw new ImageNotSupportedException($"Unsupported track type {chdTrack.type}"); } switch((SubTypeOld)chdTrack.subType) { case SubTypeOld.Cooked: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; case SubTypeOld.None: aaruTrack.TrackSubchannelType = TrackSubchannelType.None; break; case SubTypeOld.Raw: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; default: throw new ImageNotSupportedException($"Unsupported subchannel type {chdTrack.type}"); } aaruTrack.TrackDescription = $"Track {i + 1}"; aaruTrack.TrackEndSector = (currentSector + chdTrack.frames) - 1; aaruTrack.TrackFile = imageFilter.GetFilename(); aaruTrack.TrackFileType = "BINARY"; aaruTrack.TrackFilter = imageFilter; aaruTrack.TrackStartSector = currentSector; aaruTrack.TrackSequence = i + 1; aaruTrack.TrackSession = 1; if(aaruTrack.TrackSequence == 1) aaruTrack.Indexes.Add(0, -150); aaruTrack.Indexes.Add(1, (int)currentSector); currentSector += chdTrack.frames + chdTrack.extraFrames; _tracks.Add(aaruTrack.TrackSequence, aaruTrack); } _isCdrom = true; break; // "CHTR" case CDROM_TRACK_METADATA: if(_isHdd) throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); if(_isGdrom) throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); string chtr = StringHandlers.CToString(meta); var chtrRegEx = new Regex(REGEX_METADATA_CDROM); Match chtrMatch = chtrRegEx.Match(chtr); if(chtrMatch.Success) { _isCdrom = true; uint trackNo = uint.Parse(chtrMatch.Groups["track"].Value); uint frames = uint.Parse(chtrMatch.Groups["frames"].Value); string subtype = chtrMatch.Groups["sub_type"].Value; string tracktype = chtrMatch.Groups["track_type"].Value; if(trackNo != currentTrack) throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); var aaruTrack = new Track(); switch(tracktype) { case TRACK_TYPE_AUDIO: aaruTrack.TrackBytesPerSector = 2352; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.Audio; break; case TRACK_TYPE_MODE1: case TRACK_TYPE_MODE1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE1_RAW: case TRACK_TYPE_MODE1_RAW_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE2: case TRACK_TYPE_MODE2_2K: case TRACK_TYPE_MODE2_FM: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2336; aaruTrack.TrackType = TrackType.CdMode2Formless; break; case TRACK_TYPE_MODE2_F1: case TRACK_TYPE_MODE2_F1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode2Form1; break; case TRACK_TYPE_MODE2_F2: case TRACK_TYPE_MODE2_F2_2K: aaruTrack.TrackBytesPerSector = 2324; aaruTrack.TrackRawBytesPerSector = 2324; aaruTrack.TrackType = TrackType.CdMode2Form2; break; case TRACK_TYPE_MODE2_RAW: case TRACK_TYPE_MODE2_RAW_2K: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode2Formless; break; default: throw new ImageNotSupportedException($"Unsupported track type {tracktype}"); } switch(subtype) { case SUB_TYPE_COOKED: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; case SUB_TYPE_NONE: aaruTrack.TrackSubchannelType = TrackSubchannelType.None; break; case SUB_TYPE_RAW: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; default: throw new ImageNotSupportedException($"Unsupported subchannel type {subtype}"); } aaruTrack.TrackDescription = $"Track {trackNo}"; aaruTrack.TrackEndSector = (currentSector + frames) - 1; aaruTrack.TrackFile = imageFilter.GetFilename(); aaruTrack.TrackFileType = "BINARY"; aaruTrack.TrackFilter = imageFilter; aaruTrack.TrackStartSector = currentSector; aaruTrack.TrackSequence = trackNo; aaruTrack.TrackSession = 1; if(aaruTrack.TrackSequence == 1) aaruTrack.Indexes.Add(0, -150); aaruTrack.Indexes.Add(1, (int)currentSector); currentSector += frames; currentTrack++; _tracks.Add(aaruTrack.TrackSequence, aaruTrack); } break; // "CHT2" case CDROM_TRACK_METADATA2: if(_isHdd) throw new ImageNotSupportedException("Image cannot be a hard disk and a CD-ROM at the same time, aborting."); if(_isGdrom) throw new ImageNotSupportedException("Image cannot be a GD-ROM and a CD-ROM at the same time, aborting."); string cht2 = StringHandlers.CToString(meta); var cht2RegEx = new Regex(REGEX_METADATA_CDROM2); Match cht2Match = cht2RegEx.Match(cht2); if(cht2Match.Success) { _isCdrom = true; uint trackNo = uint.Parse(cht2Match.Groups["track"].Value); uint frames = uint.Parse(cht2Match.Groups["frames"].Value); string subtype = cht2Match.Groups["sub_type"].Value; string trackType = cht2Match.Groups["track_type"].Value; uint pregap = uint.Parse(cht2Match.Groups["pregap"].Value); // What is this, really? Same as track type? string pregapType = cht2Match.Groups["pgtype"].Value; // Read above, but for subchannel string pregapSubType = cht2Match.Groups["pgsub"].Value; // This is a recommendation (shall) of 150 sectors at the end of the last data track, // or of any data track followed by an audio track, according to Yellow Book. // It is indistinguishable from normal data. // TODO: Does CHD store it, or like CDRWin, ignores it? uint postgap = uint.Parse(cht2Match.Groups["postgap"].Value); if(trackNo != currentTrack) throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); var aaruTrack = new Track(); switch(trackType) { case TRACK_TYPE_AUDIO: aaruTrack.TrackBytesPerSector = 2352; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.Audio; break; case TRACK_TYPE_MODE1: case TRACK_TYPE_MODE1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE1_RAW: case TRACK_TYPE_MODE1_RAW_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE2: case TRACK_TYPE_MODE2_2K: case TRACK_TYPE_MODE2_FM: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2336; aaruTrack.TrackType = TrackType.CdMode2Formless; break; case TRACK_TYPE_MODE2_F1: case TRACK_TYPE_MODE2_F1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode2Form1; break; case TRACK_TYPE_MODE2_F2: case TRACK_TYPE_MODE2_F2_2K: aaruTrack.TrackBytesPerSector = 2324; aaruTrack.TrackRawBytesPerSector = 2324; aaruTrack.TrackType = TrackType.CdMode2Form2; break; case TRACK_TYPE_MODE2_RAW: case TRACK_TYPE_MODE2_RAW_2K: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode2Formless; break; default: throw new ImageNotSupportedException($"Unsupported track type {trackType}"); } switch(subtype) { case SUB_TYPE_COOKED: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; case SUB_TYPE_NONE: aaruTrack.TrackSubchannelType = TrackSubchannelType.None; break; case SUB_TYPE_RAW: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; default: throw new ImageNotSupportedException($"Unsupported subchannel type {subtype}"); } aaruTrack.TrackDescription = $"Track {trackNo}"; aaruTrack.TrackEndSector = (currentSector + frames) - 1; aaruTrack.TrackFile = imageFilter.GetFilename(); aaruTrack.TrackFileType = "BINARY"; aaruTrack.TrackFilter = imageFilter; aaruTrack.TrackStartSector = currentSector; aaruTrack.TrackSequence = trackNo; aaruTrack.TrackSession = 1; if(aaruTrack.TrackSequence == 1) { if(pregap <= 150) { aaruTrack.Indexes.Add(0, -150); aaruTrack.TrackPregap = 150; } else { aaruTrack.Indexes.Add(0, -1 * (int)pregap); aaruTrack.TrackPregap = pregap; } aaruTrack.Indexes.Add(1, (int)currentSector); } else if(pregap > 0) { aaruTrack.Indexes.Add(0, (int)currentSector); aaruTrack.TrackPregap = pregap; aaruTrack.Indexes.Add(1, (int)(currentSector + pregap)); } else aaruTrack.Indexes.Add(1, (int)currentSector); currentSector += frames; currentTrack++; _tracks.Add(aaruTrack.TrackSequence, aaruTrack); } break; // "CHGT" case GDROM_OLD_METADATA: _swapAudio = true; goto case GDROM_METADATA; // "CHGD" case GDROM_METADATA: if(_isHdd) throw new ImageNotSupportedException("Image cannot be a hard disk and a GD-ROM at the same time, aborting."); if(_isCdrom) throw new ImageNotSupportedException("Image cannot be a CD-ROM and a GD-ROM at the same time, aborting."); string chgd = StringHandlers.CToString(meta); var chgdRegEx = new Regex(REGEX_METADATA_GDROM); Match chgdMatch = chgdRegEx.Match(chgd); if(chgdMatch.Success) { _isGdrom = true; uint trackNo = uint.Parse(chgdMatch.Groups["track"].Value); uint frames = uint.Parse(chgdMatch.Groups["frames"].Value); string subtype = chgdMatch.Groups["sub_type"].Value; string trackType = chgdMatch.Groups["track_type"].Value; // TODO: Check pregap, postgap and pad behaviour uint pregap = uint.Parse(chgdMatch.Groups["pregap"].Value); string pregapType = chgdMatch.Groups["pgtype"].Value; string pregapSubType = chgdMatch.Groups["pgsub"].Value; uint postgap = uint.Parse(chgdMatch.Groups["postgap"].Value); uint pad = uint.Parse(chgdMatch.Groups["pad"].Value); if(trackNo != currentTrack) throw new ImageNotSupportedException("Unsorted tracks, cannot proceed."); var aaruTrack = new Track(); switch(trackType) { case TRACK_TYPE_AUDIO: aaruTrack.TrackBytesPerSector = 2352; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.Audio; break; case TRACK_TYPE_MODE1: case TRACK_TYPE_MODE1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE1_RAW: case TRACK_TYPE_MODE1_RAW_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode1; break; case TRACK_TYPE_MODE2: case TRACK_TYPE_MODE2_2K: case TRACK_TYPE_MODE2_FM: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2336; aaruTrack.TrackType = TrackType.CdMode2Formless; break; case TRACK_TYPE_MODE2_F1: case TRACK_TYPE_MODE2_F1_2K: aaruTrack.TrackBytesPerSector = 2048; aaruTrack.TrackRawBytesPerSector = 2048; aaruTrack.TrackType = TrackType.CdMode2Form1; break; case TRACK_TYPE_MODE2_F2: case TRACK_TYPE_MODE2_F2_2K: aaruTrack.TrackBytesPerSector = 2324; aaruTrack.TrackRawBytesPerSector = 2324; aaruTrack.TrackType = TrackType.CdMode2Form2; break; case TRACK_TYPE_MODE2_RAW: case TRACK_TYPE_MODE2_RAW_2K: aaruTrack.TrackBytesPerSector = 2336; aaruTrack.TrackRawBytesPerSector = 2352; aaruTrack.TrackType = TrackType.CdMode2Formless; break; default: throw new ImageNotSupportedException($"Unsupported track type {trackType}"); } switch(subtype) { case SUB_TYPE_COOKED: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.PackedInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; case SUB_TYPE_NONE: aaruTrack.TrackSubchannelType = TrackSubchannelType.None; break; case SUB_TYPE_RAW: aaruTrack.TrackSubchannelFile = imageFilter.GetFilename(); aaruTrack.TrackSubchannelType = TrackSubchannelType.RawInterleaved; aaruTrack.TrackSubchannelFilter = imageFilter; break; default: throw new ImageNotSupportedException($"Unsupported subchannel type {subtype}"); } aaruTrack.TrackDescription = $"Track {trackNo}"; aaruTrack.TrackEndSector = (currentSector + frames) - 1; aaruTrack.TrackFile = imageFilter.GetFilename(); aaruTrack.TrackFileType = "BINARY"; aaruTrack.TrackFilter = imageFilter; aaruTrack.TrackStartSector = currentSector; aaruTrack.TrackSequence = trackNo; aaruTrack.TrackSession = (ushort)(trackNo > 2 ? 2 : 1); if(aaruTrack.TrackSequence == 1) { if(pregap <= 150) { aaruTrack.Indexes.Add(0, -150); aaruTrack.TrackPregap = 150; } else { aaruTrack.Indexes.Add(0, -1 * (int)pregap); aaruTrack.TrackPregap = pregap; } aaruTrack.Indexes.Add(1, (int)currentSector); } else if(pregap > 0) { aaruTrack.Indexes.Add(0, (int)currentSector); aaruTrack.TrackPregap = pregap; aaruTrack.Indexes.Add(1, (int)(currentSector + pregap)); } else aaruTrack.Indexes.Add(1, (int)currentSector); currentSector += frames; currentTrack++; _tracks.Add(aaruTrack.TrackSequence, aaruTrack); } break; // "IDNT" case HARD_DISK_IDENT_METADATA: Identify.IdentifyDevice? idnt = CommonTypes.Structs.Devices.ATA.Identify.Decode(meta); if(idnt.HasValue) { _imageInfo.MediaManufacturer = idnt.Value.MediaManufacturer; _imageInfo.MediaSerialNumber = idnt.Value.MediaSerial; _imageInfo.DriveModel = idnt.Value.Model; _imageInfo.DriveSerialNumber = idnt.Value.SerialNumber; _imageInfo.DriveFirmwareRevision = idnt.Value.FirmwareRevision; if(idnt.Value.CurrentCylinders > 0 && idnt.Value.CurrentHeads > 0 && idnt.Value.CurrentSectorsPerTrack > 0) { _imageInfo.Cylinders = idnt.Value.CurrentCylinders; _imageInfo.Heads = idnt.Value.CurrentHeads; _imageInfo.SectorsPerTrack = idnt.Value.CurrentSectorsPerTrack; } else { _imageInfo.Cylinders = idnt.Value.Cylinders; _imageInfo.Heads = idnt.Value.Heads; _imageInfo.SectorsPerTrack = idnt.Value.SectorsPerTrack; } } _identify = meta; if(!_imageInfo.ReadableMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) _imageInfo.ReadableMediaTags.Add(MediaTagType.ATA_IDENTIFY); break; case PCMCIA_CIS_METADATA: _cis = meta; if(!_imageInfo.ReadableMediaTags.Contains(MediaTagType.PCMCIA_CIS)) _imageInfo.ReadableMediaTags.Add(MediaTagType.PCMCIA_CIS); break; } nextMetaOff = header.next; } if(_isHdd) { _sectorsPerHunk = _bytesPerHunk / _imageInfo.SectorSize; _imageInfo.Sectors = _imageInfo.ImageSize / _imageInfo.SectorSize; _imageInfo.MediaType = MediaType.GENERIC_HDD; _imageInfo.XmlMediaType = XmlMediaType.BlockMedia; } else if(_isCdrom) { // Hardcoded on MAME for CD-ROM _sectorsPerHunk = 8; _imageInfo.MediaType = MediaType.CDROM; _imageInfo.XmlMediaType = XmlMediaType.OpticalDisc; foreach(Track aaruTrack in _tracks.Values) _imageInfo.Sectors += (aaruTrack.TrackEndSector - aaruTrack.TrackStartSector) + 1; } else if(_isGdrom) { // Hardcoded on MAME for GD-ROM _sectorsPerHunk = 8; _imageInfo.MediaType = MediaType.GDROM; _imageInfo.XmlMediaType = XmlMediaType.OpticalDisc; foreach(Track aaruTrack in _tracks.Values) _imageInfo.Sectors += (aaruTrack.TrackEndSector - aaruTrack.TrackStartSector) + 1; } else throw new ImageNotSupportedException("Image does not represent a known media, aborting"); } if(_isCdrom || _isGdrom) { _offsetmap = new Dictionary(); _partitions = new List(); ulong partPos = 0; foreach(Track aaruTrack in _tracks.Values) { var partition = new Partition { Description = aaruTrack.TrackDescription, Size = ((aaruTrack.TrackEndSector - (ulong)aaruTrack.Indexes[1]) + 1) * (ulong)aaruTrack.TrackRawBytesPerSector, Length = (aaruTrack.TrackEndSector - (ulong)aaruTrack.Indexes[1]) + 1, Sequence = aaruTrack.TrackSequence, Offset = partPos, Start = (ulong)aaruTrack.Indexes[1], Type = aaruTrack.TrackType.ToString() }; partPos += partition.Length; _offsetmap.Add(aaruTrack.TrackStartSector, aaruTrack.TrackSequence); if(aaruTrack.TrackSubchannelType != TrackSubchannelType.None) if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel); switch(aaruTrack.TrackType) { case TrackType.CdMode1: case TrackType.CdMode2Form1: if(aaruTrack.TrackRawBytesPerSector == 2352) { if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubHeader); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEcc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEcc); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccP)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccP); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEccQ)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEccQ); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEdc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEdc); } break; case TrackType.CdMode2Form2: if(aaruTrack.TrackRawBytesPerSector == 2352) { if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubHeader); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorEdc)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorEdc); } break; case TrackType.CdMode2Formless: if(aaruTrack.TrackRawBytesPerSector == 2352) { if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync); if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader)) _imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader); } break; } if(aaruTrack.TrackBytesPerSector > _imageInfo.SectorSize) _imageInfo.SectorSize = (uint)aaruTrack.TrackBytesPerSector; _partitions.Add(partition); } _imageInfo.HasPartitions = true; _imageInfo.HasSessions = true; } _maxBlockCache = (int)(MAX_CACHE_SIZE / (_imageInfo.SectorSize * _sectorsPerHunk)); _maxSectorCache = (int)(MAX_CACHE_SIZE / _imageInfo.SectorSize); _imageStream = stream; _sectorCache = new Dictionary(); _hunkCache = new Dictionary(); // TODO: Detect CompactFlash // TODO: Get manufacturer and drive name from CIS if applicable if(_cis != null) _imageInfo.MediaType = MediaType.PCCardTypeI; _sectorBuilder = new SectorBuilder(); return true; } public byte[] ReadSector(ulong sectorAddress) { if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); var track = new Track(); uint sectorSize; if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector)) { if(_isHdd) sectorSize = _imageInfo.SectorSize; else { track = GetTrack(sectorAddress); sectorSize = (uint)track.TrackRawBytesPerSector; } ulong hunkNo = sectorAddress / _sectorsPerHunk; ulong secOff = (sectorAddress * sectorSize) % (_sectorsPerHunk * sectorSize); byte[] hunk = GetHunk(hunkNo); sector = new byte[_imageInfo.SectorSize]; Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear(); _sectorCache.Add(sectorAddress, sector); } if(_isHdd) return sector; uint sectorOffset; bool mode2 = false; switch(track.TrackType) { case TrackType.CdMode1: { if(track.TrackRawBytesPerSector == 2352) { sectorOffset = 16; sectorSize = 2048; } else { sectorOffset = 0; sectorSize = 2048; } break; } case TrackType.CdMode2Form1: { if(track.TrackRawBytesPerSector == 2352) { sectorOffset = 0; sectorSize = 2352; mode2 = true; } else { sectorOffset = 0; sectorSize = 2048; } break; } case TrackType.CdMode2Form2: { if(track.TrackRawBytesPerSector == 2352) { sectorOffset = 0; sectorSize = 2352; mode2 = true; } else { sectorOffset = 0; sectorSize = 2324; } break; } case TrackType.CdMode2Formless: { sectorOffset = 0; sectorSize = (uint)track.TrackRawBytesPerSector; mode2 = true; break; } case TrackType.Audio: { sectorOffset = 0; sectorSize = 2352; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } byte[] buffer = new byte[sectorSize]; if(mode2) buffer = Sector.GetUserDataFromMode2(sector); else if(track.TrackType == TrackType.Audio && _swapAudio) for(int i = 0; i < 2352; i += 2) { buffer[i + 1] = sector[i]; buffer[i] = sector[i + 1]; } else Array.Copy(sector, sectorOffset, buffer, 0, sectorSize); return buffer; } public byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) { if(_isHdd) throw new FeatureNotPresentImageException("Hard disk images do not have sector tags"); if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); var track = new Track(); uint sectorSize; if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector)) { track = GetTrack(sectorAddress); sectorSize = (uint)track.TrackRawBytesPerSector; ulong hunkNo = sectorAddress / _sectorsPerHunk; ulong secOff = (sectorAddress * sectorSize) % (_sectorsPerHunk * sectorSize); byte[] hunk = GetHunk(hunkNo); sector = new byte[_imageInfo.SectorSize]; Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear(); _sectorCache.Add(sectorAddress, sector); } if(_isHdd) return sector; uint sectorOffset; if(tag == SectorTagType.CdSectorSubchannel) switch(track.TrackSubchannelType) { case TrackSubchannelType.None: throw new FeatureNotPresentImageException("Requested sector does not contain subchannel"); case TrackSubchannelType.RawInterleaved: sectorOffset = (uint)track.TrackRawBytesPerSector; sectorSize = 96; break; default: throw new FeatureSupportedButNotImplementedImageException($"Unsupported subchannel type {track.TrackSubchannelType}"); } else switch(track.TrackType) { case TrackType.CdMode1: case TrackType.CdMode2Form1: { if(track.TrackRawBytesPerSector == 2352) switch(tag) { case SectorTagType.CdSectorSync: { sectorOffset = 0; sectorSize = 12; break; } case SectorTagType.CdSectorHeader: { sectorOffset = 12; sectorSize = 4; break; } case SectorTagType.CdSectorSubHeader: throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); case SectorTagType.CdSectorEcc: { sectorOffset = 2076; sectorSize = 276; break; } case SectorTagType.CdSectorEccP: { sectorOffset = 2076; sectorSize = 172; break; } case SectorTagType.CdSectorEccQ: { sectorOffset = 2248; sectorSize = 104; break; } case SectorTagType.CdSectorEdc: { sectorOffset = 2064; sectorSize = 4; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } else throw new FeatureNotPresentImageException("Requested sector does not contain tags"); break; } case TrackType.CdMode2Form2: { if(track.TrackRawBytesPerSector == 2352) switch(tag) { case SectorTagType.CdSectorSync: { sectorOffset = 0; sectorSize = 12; break; } case SectorTagType.CdSectorHeader: { sectorOffset = 12; sectorSize = 4; break; } case SectorTagType.CdSectorSubHeader: { sectorOffset = 16; sectorSize = 8; break; } case SectorTagType.CdSectorEdc: { sectorOffset = 2348; sectorSize = 4; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } else switch(tag) { case SectorTagType.CdSectorSync: case SectorTagType.CdSectorHeader: case SectorTagType.CdSectorSubchannel: case SectorTagType.CdSectorEcc: case SectorTagType.CdSectorEccP: case SectorTagType.CdSectorEccQ: throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); case SectorTagType.CdSectorSubHeader: { sectorOffset = 0; sectorSize = 8; break; } case SectorTagType.CdSectorEdc: { sectorOffset = 2332; sectorSize = 4; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } break; } case TrackType.CdMode2Formless: { if(track.TrackRawBytesPerSector == 2352) switch(tag) { case SectorTagType.CdSectorSync: case SectorTagType.CdSectorHeader: case SectorTagType.CdSectorEcc: case SectorTagType.CdSectorEccP: case SectorTagType.CdSectorEccQ: throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); case SectorTagType.CdSectorSubHeader: { sectorOffset = 0; sectorSize = 8; break; } case SectorTagType.CdSectorEdc: { sectorOffset = 2332; sectorSize = 4; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } else throw new FeatureNotPresentImageException("Requested sector does not contain tags"); break; } case TrackType.Audio: throw new FeatureNotPresentImageException("Requested sector does not contain tags"); default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } byte[] buffer = new byte[sectorSize]; if(track.TrackType == TrackType.Audio && _swapAudio) for(int i = 0; i < 2352; i += 2) { buffer[i + 1] = sector[i]; buffer[i] = sector[i + 1]; } else Array.Copy(sector, sectorOffset, buffer, 0, sectorSize); if(track.TrackType == TrackType.Audio && _swapAudio) for(int i = 0; i < 2352; i += 2) { buffer[i + 1] = sector[i]; buffer[i] = sector[i + 1]; } else Array.Copy(sector, sectorOffset, buffer, 0, sectorSize); return buffer; } public byte[] ReadSectors(ulong sectorAddress, uint length) { if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); if(sectorAddress + length > _imageInfo.Sectors) throw new ArgumentOutOfRangeException(nameof(length), $"Requested more sectors ({sectorAddress + length}) than available ({_imageInfo.Sectors})"); var ms = new MemoryStream(); for(uint i = 0; i < length; i++) { byte[] sector = ReadSector(sectorAddress + i); ms.Write(sector, 0, sector.Length); } return ms.ToArray(); } public byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) { if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); if(sectorAddress + length > _imageInfo.Sectors) throw new ArgumentOutOfRangeException(nameof(length), $"Requested more sectors ({sectorAddress + length}) than available ({_imageInfo.Sectors})"); var ms = new MemoryStream(); for(uint i = 0; i < length; i++) { byte[] sector = ReadSectorTag(sectorAddress + i, tag); ms.Write(sector, 0, sector.Length); } return ms.ToArray(); } public byte[] ReadSectorLong(ulong sectorAddress) { if(_isHdd) return ReadSector(sectorAddress); if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); var track = new Track(); if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector)) { track = GetTrack(sectorAddress); uint sectorSize = (uint)track.TrackRawBytesPerSector; ulong hunkNo = sectorAddress / _sectorsPerHunk; ulong secOff = (sectorAddress * sectorSize) % (_sectorsPerHunk * sectorSize); byte[] hunk = GetHunk(hunkNo); sector = new byte[_imageInfo.SectorSize]; Array.Copy(hunk, (int)secOff, sector, 0, sector.Length); if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear(); _sectorCache.Add(sectorAddress, sector); } byte[] buffer = new byte[track.TrackRawBytesPerSector]; if(track.TrackType == TrackType.Audio && _swapAudio) for(int i = 0; i < 2352; i += 2) { buffer[i + 1] = sector[i]; buffer[i] = sector[i + 1]; } else Array.Copy(sector, 0, buffer, 0, track.TrackRawBytesPerSector); switch(track.TrackType) { case TrackType.CdMode1 when track.TrackRawBytesPerSector == 2048: { byte[] fullSector = new byte[2352]; Array.Copy(buffer, 0, fullSector, 16, 2048); _sectorBuilder.ReconstructPrefix(ref fullSector, TrackType.CdMode1, (long)sectorAddress); _sectorBuilder.ReconstructEcc(ref fullSector, TrackType.CdMode1); buffer = fullSector; break; } case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2048: { byte[] fullSector = new byte[2352]; Array.Copy(buffer, 0, fullSector, 24, 2048); _sectorBuilder.ReconstructPrefix(ref fullSector, TrackType.CdMode2Form1, (long)sectorAddress); _sectorBuilder.ReconstructEcc(ref fullSector, TrackType.CdMode2Form1); buffer = fullSector; break; } case TrackType.CdMode2Form1 when track.TrackRawBytesPerSector == 2324: { byte[] fullSector = new byte[2352]; Array.Copy(buffer, 0, fullSector, 24, 2324); _sectorBuilder.ReconstructPrefix(ref fullSector, TrackType.CdMode2Form2, (long)sectorAddress); _sectorBuilder.ReconstructEcc(ref fullSector, TrackType.CdMode2Form2); buffer = fullSector; break; } case TrackType.CdMode2Formless when track.TrackRawBytesPerSector == 2336: { byte[] fullSector = new byte[2352]; _sectorBuilder.ReconstructPrefix(ref fullSector, TrackType.CdMode2Formless, (long)sectorAddress); Array.Copy(buffer, 0, fullSector, 16, 2336); buffer = fullSector; break; } } return buffer; } public byte[] ReadSectorsLong(ulong sectorAddress, uint length) { if(sectorAddress > _imageInfo.Sectors - 1) throw new ArgumentOutOfRangeException(nameof(sectorAddress), $"Sector address {sectorAddress} not found"); if(sectorAddress + length > _imageInfo.Sectors) throw new ArgumentOutOfRangeException(nameof(length), $"Requested more sectors ({sectorAddress + length}) than available ({_imageInfo.Sectors})"); var ms = new MemoryStream(); for(uint i = 0; i < length; i++) { byte[] sector = ReadSectorLong(sectorAddress + i); ms.Write(sector, 0, sector.Length); } return ms.ToArray(); } public byte[] ReadDiskTag(MediaTagType tag) { if(_imageInfo.ReadableMediaTags.Contains(MediaTagType.ATA_IDENTIFY)) return _identify; if(_imageInfo.ReadableMediaTags.Contains(MediaTagType.PCMCIA_CIS)) return _cis; throw new FeatureUnsupportedImageException("Feature not supported by image format"); } public List GetSessionTracks(Session session) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return GetSessionTracks(session.SessionSequence); } public List GetSessionTracks(ushort session) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return _tracks.Values.Where(track => track.TrackSession == session).ToList(); } public byte[] ReadSector(ulong sectorAddress, uint track) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSector(GetAbsoluteSector(sectorAddress, track)); } public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSectorTag(GetAbsoluteSector(sectorAddress, track), tag); } public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSectors(GetAbsoluteSector(sectorAddress, track), length); } public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSectorsTag(GetAbsoluteSector(sectorAddress, track), length, tag); } public byte[] ReadSectorLong(ulong sectorAddress, uint track) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSectorLong(GetAbsoluteSector(sectorAddress, track)); } public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) { if(_isHdd) throw new FeaturedNotSupportedByDiscImageException("Cannot access optical tracks on a hard disk image"); return ReadSectorLong(GetAbsoluteSector(sectorAddress, track), length); } } }