Files
Aaru/Aaru.Images/CHD/Read.cs

2064 lines
86 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
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.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.CommonTypes.Structs.Devices.ATA;
using Aaru.Decoders.CD;
using Aaru.Helpers;
using Aaru.Logging;
using Session = Aaru.CommonTypes.Structs.Session;
namespace Aaru.Images;
public sealed partial class Chd
{
#region IOpticalMediaImage Members
/// <inheritdoc />
[SuppressMessage("ReSharper", "UnusedVariable")]
public ErrorNumber Open(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
stream.Seek(0, SeekOrigin.Begin);
var magic = new byte[8];
stream.EnsureRead(magic, 0, 8);
if(!_chdTag.SequenceEqual(magic)) return ErrorNumber.InvalidArgument;
// Read length
var buffer = new byte[4];
stream.EnsureRead(buffer, 0, 4);
var length = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0);
buffer = new byte[4];
stream.EnsureRead(buffer, 0, 4);
var version = BitConverter.ToUInt32(buffer.Reverse().ToArray(), 0);
buffer = new byte[length];
stream.Seek(0, SeekOrigin.Begin);
stream.EnsureRead(buffer, 0, (int)length);
ulong nextMetaOff = 0;
var hunkMapStopwatch = new Stopwatch();
switch(version)
{
case 1:
{
HeaderV1 hdrV1 = Marshal.ByteArrayToStructureBigEndian<HeaderV1>(buffer);
AaruLogging.Debug(MODULE_NAME, "hdrV1.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV1.tag));
AaruLogging.Debug(MODULE_NAME, "hdrV1.length = {0} bytes", hdrV1.length);
AaruLogging.Debug(MODULE_NAME, "hdrV1.version = {0}", hdrV1.version);
AaruLogging.Debug(MODULE_NAME, "hdrV1.flags = {0}", (Flags)hdrV1.flags);
AaruLogging.Debug(MODULE_NAME, "hdrV1.compression = {0}", (Compression)hdrV1.compression);
AaruLogging.Debug(MODULE_NAME, "hdrV1.hunksize = {0}", hdrV1.hunksize);
AaruLogging.Debug(MODULE_NAME, "hdrV1.totalhunks = {0}", hdrV1.totalhunks);
AaruLogging.Debug(MODULE_NAME, "hdrV1.cylinders = {0}", hdrV1.cylinders);
AaruLogging.Debug(MODULE_NAME, "hdrV1.heads = {0}", hdrV1.heads);
AaruLogging.Debug(MODULE_NAME, "hdrV1.sectors = {0}", hdrV1.sectors);
AaruLogging.Debug(MODULE_NAME, "hdrV1.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV1.md5));
AaruLogging.Debug(MODULE_NAME,
"hdrV1.parentmd5 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV1.parentmd5)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV1.parentmd5));
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
hunkMapStopwatch.Restart();
_hunkTable = new ulong[hdrV1.totalhunks];
var hunkSectorCount = (uint)Math.Ceiling((double)hdrV1.totalhunks * 8 / 512);
var hunkSectorBytes = new byte[512];
for(var i = 0; i < hunkSectorCount; i++)
{
stream.EnsureRead(hunkSectorBytes, 0, 512);
// This does the big-endian trick but reverses the order of elements also
Array.Reverse(hunkSectorBytes);
HunkSector hunkSector = Marshal.ByteArrayToStructureLittleEndian<HunkSector>(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);
}
hunkMapStopwatch.Stop();
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.TotalSeconds);
_imageInfo.MediaType = MediaType.GENERIC_HDD;
_imageInfo.Sectors = hdrV1.hunksize * hdrV1.totalhunks;
_imageInfo.MetadataMediaType = MetadataMediaType.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<HeaderV2>(buffer);
AaruLogging.Debug(MODULE_NAME, "hdrV2.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV2.tag));
AaruLogging.Debug(MODULE_NAME, "hdrV2.length = {0} bytes", hdrV2.length);
AaruLogging.Debug(MODULE_NAME, "hdrV2.version = {0}", hdrV2.version);
AaruLogging.Debug(MODULE_NAME, "hdrV2.flags = {0}", (Flags)hdrV2.flags);
AaruLogging.Debug(MODULE_NAME, "hdrV2.compression = {0}", (Compression)hdrV2.compression);
AaruLogging.Debug(MODULE_NAME, "hdrV2.hunksize = {0}", hdrV2.hunksize);
AaruLogging.Debug(MODULE_NAME, "hdrV2.totalhunks = {0}", hdrV2.totalhunks);
AaruLogging.Debug(MODULE_NAME, "hdrV2.cylinders = {0}", hdrV2.cylinders);
AaruLogging.Debug(MODULE_NAME, "hdrV2.heads = {0}", hdrV2.heads);
AaruLogging.Debug(MODULE_NAME, "hdrV2.sectors = {0}", hdrV2.sectors);
AaruLogging.Debug(MODULE_NAME, "hdrV2.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV2.md5));
AaruLogging.Debug(MODULE_NAME,
"hdrV2.parentmd5 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV2.parentmd5)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV2.parentmd5));
AaruLogging.Debug(MODULE_NAME, "hdrV2.seclen = {0}", hdrV2.seclen);
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
hunkMapStopwatch.Restart();
_hunkTable = new ulong[hdrV2.totalhunks];
// How many sectors uses the BAT
var hunkSectorCount = (uint)Math.Ceiling((double)hdrV2.totalhunks * 8 / 512);
var hunkSectorBytes = new byte[512];
for(var i = 0; i < hunkSectorCount; i++)
{
stream.EnsureRead(hunkSectorBytes, 0, 512);
// This does the big-endian trick but reverses the order of elements also
Array.Reverse(hunkSectorBytes);
HunkSector hunkSector = Marshal.ByteArrayToStructureLittleEndian<HunkSector>(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);
}
hunkMapStopwatch.Stop();
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.TotalSeconds);
_imageInfo.MediaType = MediaType.GENERIC_HDD;
_imageInfo.Sectors = hdrV2.hunksize * hdrV2.totalhunks;
_imageInfo.MetadataMediaType = MetadataMediaType.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<HeaderV3>(buffer);
AaruLogging.Debug(MODULE_NAME, "hdrV3.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV3.tag));
AaruLogging.Debug(MODULE_NAME, "hdrV3.length = {0} bytes", hdrV3.length);
AaruLogging.Debug(MODULE_NAME, "hdrV3.version = {0}", hdrV3.version);
AaruLogging.Debug(MODULE_NAME, "hdrV3.flags = {0}", (Flags)hdrV3.flags);
AaruLogging.Debug(MODULE_NAME, "hdrV3.compression = {0}", (Compression)hdrV3.compression);
AaruLogging.Debug(MODULE_NAME, "hdrV3.totalhunks = {0}", hdrV3.totalhunks);
AaruLogging.Debug(MODULE_NAME, "hdrV3.logicalbytes = {0}", hdrV3.logicalbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV3.metaoffset = {0}", hdrV3.metaoffset);
AaruLogging.Debug(MODULE_NAME, "hdrV3.md5 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.md5));
AaruLogging.Debug(MODULE_NAME,
"hdrV3.parentmd5 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentmd5)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV3.parentmd5));
AaruLogging.Debug(MODULE_NAME, "hdrV3.hunkbytes = {0}", hdrV3.hunkbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV3.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV3.sha1));
AaruLogging.Debug(MODULE_NAME,
"hdrV3.parentsha1 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV3.parentsha1)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV3.parentsha1));
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
hunkMapStopwatch.Restart();
_hunkMap = new byte[hdrV3.totalhunks * 16];
stream.EnsureRead(_hunkMap, 0, _hunkMap.Length);
hunkMapStopwatch.Stop();
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.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<HeaderV4>(buffer);
AaruLogging.Debug(MODULE_NAME, "hdrV4.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV4.tag));
AaruLogging.Debug(MODULE_NAME, "hdrV4.length = {0} bytes", hdrV4.length);
AaruLogging.Debug(MODULE_NAME, "hdrV4.version = {0}", hdrV4.version);
AaruLogging.Debug(MODULE_NAME, "hdrV4.flags = {0}", (Flags)hdrV4.flags);
AaruLogging.Debug(MODULE_NAME, "hdrV4.compression = {0}", (Compression)hdrV4.compression);
AaruLogging.Debug(MODULE_NAME, "hdrV4.totalhunks = {0}", hdrV4.totalhunks);
AaruLogging.Debug(MODULE_NAME, "hdrV4.logicalbytes = {0}", hdrV4.logicalbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV4.metaoffset = {0}", hdrV4.metaoffset);
AaruLogging.Debug(MODULE_NAME, "hdrV4.hunkbytes = {0}", hdrV4.hunkbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV4.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.sha1));
AaruLogging.Debug(MODULE_NAME,
"hdrV4.parentsha1 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV4.parentsha1)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV4.parentsha1));
AaruLogging.Debug(MODULE_NAME, "hdrV4.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV4.rawsha1));
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
hunkMapStopwatch.Restart();
_hunkMap = new byte[hdrV4.totalhunks * 16];
stream.EnsureRead(_hunkMap, 0, _hunkMap.Length);
hunkMapStopwatch.Stop();
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.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
AaruLogging.Error(Localization.CHD_version_5_is_not_yet_supported);
return ErrorNumber.NotImplemented;
HeaderV5 hdrV5 = Marshal.ByteArrayToStructureBigEndian<HeaderV5>(buffer);
AaruLogging.Debug(MODULE_NAME, "hdrV5.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV5.tag));
AaruLogging.Debug(MODULE_NAME, "hdrV5.length = {0} bytes", hdrV5.length);
AaruLogging.Debug(MODULE_NAME, "hdrV5.version = {0}", hdrV5.version);
AaruLogging.Debug(MODULE_NAME,
"hdrV5.compressor0 = \"{0}\"",
Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor0)));
AaruLogging.Debug(MODULE_NAME,
"hdrV5.compressor1 = \"{0}\"",
Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor1)));
AaruLogging.Debug(MODULE_NAME,
"hdrV5.compressor2 = \"{0}\"",
Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor2)));
AaruLogging.Debug(MODULE_NAME,
"hdrV5.compressor3 = \"{0}\"",
Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(hdrV5.compressor3)));
AaruLogging.Debug(MODULE_NAME, "hdrV5.logicalbytes = {0}", hdrV5.logicalbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV5.mapoffset = {0}", hdrV5.mapoffset);
AaruLogging.Debug(MODULE_NAME, "hdrV5.metaoffset = {0}", hdrV5.metaoffset);
AaruLogging.Debug(MODULE_NAME, "hdrV5.hunkbytes = {0}", hdrV5.hunkbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV5.unitbytes = {0}", hdrV5.unitbytes);
AaruLogging.Debug(MODULE_NAME, "hdrV5.sha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.sha1));
AaruLogging.Debug(MODULE_NAME,
"hdrV5.parentsha1 = {0}",
ArrayHelpers.ArrayIsNullOrEmpty(hdrV5.parentsha1)
? "null"
: ArrayHelpers.ByteArrayToHex(hdrV5.parentsha1));
AaruLogging.Debug(MODULE_NAME, "hdrV5.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.rawsha1));
// TODO: Implement compressed CHD v5
if(hdrV5.compressor0 == 0)
{
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
hunkMapStopwatch.Restart();
_hunkTableSmall = new uint[hdrV5.logicalbytes / hdrV5.hunkbytes];
var hunkSectorCount = (uint)Math.Ceiling((double)_hunkTableSmall.Length * 4 / 512);
var hunkSectorBytes = new byte[512];
stream.Seek((long)hdrV5.mapoffset, SeekOrigin.Begin);
for(var i = 0; i < hunkSectorCount; i++)
{
stream.EnsureRead(hunkSectorBytes, 0, 512);
// This does the big-endian trick but reverses the order of elements also
Array.Reverse(hunkSectorBytes);
HunkSectorSmall hunkSector =
Marshal.ByteArrayToStructureLittleEndian<HunkSectorSmall>(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);
}
}
hunkMapStopwatch.Stop();
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.TotalSeconds);
}
else
{
AaruLogging.Error(Localization.Cannot_read_compressed_CHD_version_5);
return ErrorNumber.NotSupported;
}
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:
AaruLogging.Error(string.Format(Localization.Unsupported_CHD_version_0, version));
return ErrorNumber.NotSupported;
}
if(_mapVersion >= 3)
{
_isCdrom = false;
_isHdd = false;
_isGdrom = false;
_swapAudio = false;
_tracks = new Dictionary<uint, Track>();
AaruLogging.Debug(MODULE_NAME, Localization.Reading_metadata);
ulong currentSector = 0;
uint currentTrack = 1;
while(nextMetaOff > 0)
{
var hdrBytes = new byte[16];
stream.Seek((long)nextMetaOff, SeekOrigin.Begin);
stream.EnsureRead(hdrBytes, 0, hdrBytes.Length);
MetadataHeader header = Marshal.ByteArrayToStructureBigEndian<MetadataHeader>(hdrBytes);
var meta = new byte[header.flagsAndLength & 0xFFFFFF];
stream.EnsureRead(meta, 0, meta.Length);
AaruLogging.Debug(MODULE_NAME,
Localization.Found_metadata_0_,
Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(header.tag)));
switch(header.tag)
{
// "GDDD"
case HARD_DISK_METADATA:
if(_isCdrom || _isGdrom)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_hard_disk_and_a_CGD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
string gddd = StringHandlers.CToString(meta);
Regex gdddRegEx = MetadataHddRegex();
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)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_hard_disk_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
if(_isGdrom)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_GD_ROM_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
var 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.BytesPerSector = 2352;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.Audio;
break;
case TrackTypeOld.Mode1:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode1;
break;
case TrackTypeOld.Mode1Raw:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode1;
break;
case TrackTypeOld.Mode2:
case TrackTypeOld.Mode2FormMix:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2336;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
case TrackTypeOld.Mode2Form1:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode2Form1;
break;
case TrackTypeOld.Mode2Form2:
aaruTrack.BytesPerSector = 2324;
aaruTrack.RawBytesPerSector = 2324;
aaruTrack.Type = TrackType.CdMode2Form2;
break;
case TrackTypeOld.Mode2Raw:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_track_type_0,
chdTrack.type));
return ErrorNumber.NotSupported;
}
}
switch((SubTypeOld)chdTrack.subType)
{
case SubTypeOld.Cooked:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.PackedInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
case SubTypeOld.None:
aaruTrack.SubchannelType = TrackSubchannelType.None;
break;
case SubTypeOld.Raw:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.RawInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_subchannel_type_0,
chdTrack.type));
return ErrorNumber.NotSupported;
}
}
aaruTrack.Description = string.Format(Localization.Track_0, i + 1);
aaruTrack.EndSector = currentSector + chdTrack.frames - 1;
aaruTrack.File = imageFilter.Filename;
aaruTrack.FileType = "BINARY";
aaruTrack.Filter = imageFilter;
aaruTrack.StartSector = currentSector;
aaruTrack.Sequence = i + 1;
aaruTrack.Session = 1;
if(aaruTrack.Sequence == 1) aaruTrack.Indexes.Add(0, -150);
aaruTrack.Indexes.Add(1, (int)currentSector);
currentSector += chdTrack.frames + chdTrack.extraFrames;
_tracks.Add(aaruTrack.Sequence, aaruTrack);
}
_isCdrom = true;
break;
// "CHTR"
case CDROM_TRACK_METADATA:
if(_isHdd)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_hard_disk_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
if(_isGdrom)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_GD_ROM_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
string chtr = StringHandlers.CToString(meta);
Regex chtrRegEx = MetadataCdromRegex();
Match chtrMatch = chtrRegEx.Match(chtr);
if(chtrMatch.Success)
{
_isCdrom = true;
var trackNo = uint.Parse(chtrMatch.Groups["track"].Value);
var frames = uint.Parse(chtrMatch.Groups["frames"].Value);
string subtype = chtrMatch.Groups["sub_type"].Value;
string tracktype = chtrMatch.Groups["track_type"].Value;
if(trackNo != currentTrack)
{
AaruLogging.Error(Localization.Unsorted_tracks_cannot_proceed);
return ErrorNumber.NotSupported;
}
var aaruTrack = new Track();
switch(tracktype)
{
case TRACK_TYPE_AUDIO:
aaruTrack.BytesPerSector = 2352;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.Audio;
break;
case TRACK_TYPE_MODE1:
case TRACK_TYPE_MODE1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE1_RAW:
case TRACK_TYPE_MODE1_RAW_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE2:
case TRACK_TYPE_MODE2_2K:
case TRACK_TYPE_MODE2_FM:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2336;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
case TRACK_TYPE_MODE2_F1:
case TRACK_TYPE_MODE2_F1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode2Form1;
break;
case TRACK_TYPE_MODE2_F2:
case TRACK_TYPE_MODE2_F2_2K:
aaruTrack.BytesPerSector = 2324;
aaruTrack.RawBytesPerSector = 2324;
aaruTrack.Type = TrackType.CdMode2Form2;
break;
case TRACK_TYPE_MODE2_RAW:
case TRACK_TYPE_MODE2_RAW_2K:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_track_type_0, tracktype));
return ErrorNumber.NotSupported;
}
}
switch(subtype)
{
case SUB_TYPE_COOKED:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.PackedInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
case SUB_TYPE_NONE:
aaruTrack.SubchannelType = TrackSubchannelType.None;
break;
case SUB_TYPE_RAW:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.RawInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_subchannel_type_0,
subtype));
return ErrorNumber.NotSupported;
}
}
aaruTrack.Description = string.Format(Localization.Track_0, trackNo);
aaruTrack.EndSector = currentSector + frames - 1;
aaruTrack.File = imageFilter.Filename;
aaruTrack.FileType = "BINARY";
aaruTrack.Filter = imageFilter;
aaruTrack.StartSector = currentSector;
aaruTrack.Sequence = trackNo;
aaruTrack.Session = 1;
if(aaruTrack.Sequence == 1) aaruTrack.Indexes.Add(0, -150);
aaruTrack.Indexes.Add(1, (int)currentSector);
currentSector += frames;
currentTrack++;
_tracks.Add(aaruTrack.Sequence, aaruTrack);
}
break;
// "CHT2"
case CDROM_TRACK_METADATA2:
if(_isHdd)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_hard_disk_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
if(_isGdrom)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_GD_ROM_and_a_CD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
string cht2 = StringHandlers.CToString(meta);
Regex cht2RegEx = MetadataCdrom2Regex();
Match cht2Match = cht2RegEx.Match(cht2);
if(cht2Match.Success)
{
_isCdrom = true;
var trackNo = uint.Parse(cht2Match.Groups["track"].Value);
var frames = uint.Parse(cht2Match.Groups["frames"].Value);
string subtype = cht2Match.Groups["sub_type"].Value;
string trackType = cht2Match.Groups["track_type"].Value;
var 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?
var postgap = uint.Parse(cht2Match.Groups["postgap"].Value);
if(trackNo != currentTrack)
{
AaruLogging.Error(Localization.Unsorted_tracks_cannot_proceed);
return ErrorNumber.NotSupported;
}
var aaruTrack = new Track();
switch(trackType)
{
case TRACK_TYPE_AUDIO:
aaruTrack.BytesPerSector = 2352;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.Audio;
break;
case TRACK_TYPE_MODE1:
case TRACK_TYPE_MODE1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE1_RAW:
case TRACK_TYPE_MODE1_RAW_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE2:
case TRACK_TYPE_MODE2_2K:
case TRACK_TYPE_MODE2_FM:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2336;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
case TRACK_TYPE_MODE2_F1:
case TRACK_TYPE_MODE2_F1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode2Form1;
break;
case TRACK_TYPE_MODE2_F2:
case TRACK_TYPE_MODE2_F2_2K:
aaruTrack.BytesPerSector = 2324;
aaruTrack.RawBytesPerSector = 2324;
aaruTrack.Type = TrackType.CdMode2Form2;
break;
case TRACK_TYPE_MODE2_RAW:
case TRACK_TYPE_MODE2_RAW_2K:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_track_type_0, trackType));
return ErrorNumber.NotSupported;
}
}
switch(subtype)
{
case SUB_TYPE_COOKED:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.PackedInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
case SUB_TYPE_NONE:
aaruTrack.SubchannelType = TrackSubchannelType.None;
break;
case SUB_TYPE_RAW:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.RawInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_subchannel_type_0,
subtype));
return ErrorNumber.NotSupported;
}
}
aaruTrack.Description = string.Format(Localization.Track_0, trackNo);
aaruTrack.EndSector = currentSector + frames - 1;
aaruTrack.File = imageFilter.Filename;
aaruTrack.FileType = "BINARY";
aaruTrack.Filter = imageFilter;
aaruTrack.StartSector = currentSector;
aaruTrack.Sequence = trackNo;
aaruTrack.Session = 1;
if(aaruTrack.Sequence == 1)
{
if(pregap <= 150)
{
aaruTrack.Indexes.Add(0, -150);
aaruTrack.Pregap = 150;
}
else
{
aaruTrack.Indexes.Add(0, -1 * (int)pregap);
aaruTrack.Pregap = pregap;
}
aaruTrack.Indexes.Add(1, (int)currentSector);
}
else if(pregap > 0)
{
aaruTrack.Indexes.Add(0, (int)currentSector);
aaruTrack.Pregap = pregap;
aaruTrack.Indexes.Add(1, (int)(currentSector + pregap));
}
else
aaruTrack.Indexes.Add(1, (int)currentSector);
currentSector += frames;
currentTrack++;
_tracks.Add(aaruTrack.Sequence, aaruTrack);
}
break;
// "CHGT"
case GDROM_OLD_METADATA:
_swapAudio = true;
goto case GDROM_METADATA;
// "CHGD"
case GDROM_METADATA:
if(_isHdd)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_hard_disk_and_a_GD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
if(_isCdrom)
{
AaruLogging.Error(Localization
.Image_cannot_be_a_CD_ROM_and_a_GD_ROM_at_the_same_time_aborting);
return ErrorNumber.NotSupported;
}
string chgd = StringHandlers.CToString(meta);
Regex chgdRegEx = MetadataGdromRegex();
Match chgdMatch = chgdRegEx.Match(chgd);
if(chgdMatch.Success)
{
_isGdrom = true;
var trackNo = uint.Parse(chgdMatch.Groups["track"].Value);
var 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
var pregap = uint.Parse(chgdMatch.Groups["pregap"].Value);
string pregapType = chgdMatch.Groups["pgtype"].Value;
string pregapSubType = chgdMatch.Groups["pgsub"].Value;
var postgap = uint.Parse(chgdMatch.Groups["postgap"].Value);
var pad = uint.Parse(chgdMatch.Groups["pad"].Value);
if(trackNo != currentTrack)
{
AaruLogging.Error(Localization.Unsorted_tracks_cannot_proceed);
return ErrorNumber.NotSupported;
}
var aaruTrack = new Track();
switch(trackType)
{
case TRACK_TYPE_AUDIO:
aaruTrack.BytesPerSector = 2352;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.Audio;
break;
case TRACK_TYPE_MODE1:
case TRACK_TYPE_MODE1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE1_RAW:
case TRACK_TYPE_MODE1_RAW_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode1;
break;
case TRACK_TYPE_MODE2:
case TRACK_TYPE_MODE2_2K:
case TRACK_TYPE_MODE2_FM:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2336;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
case TRACK_TYPE_MODE2_F1:
case TRACK_TYPE_MODE2_F1_2K:
aaruTrack.BytesPerSector = 2048;
aaruTrack.RawBytesPerSector = 2048;
aaruTrack.Type = TrackType.CdMode2Form1;
break;
case TRACK_TYPE_MODE2_F2:
case TRACK_TYPE_MODE2_F2_2K:
aaruTrack.BytesPerSector = 2324;
aaruTrack.RawBytesPerSector = 2324;
aaruTrack.Type = TrackType.CdMode2Form2;
break;
case TRACK_TYPE_MODE2_RAW:
case TRACK_TYPE_MODE2_RAW_2K:
aaruTrack.BytesPerSector = 2336;
aaruTrack.RawBytesPerSector = 2352;
aaruTrack.Type = TrackType.CdMode2Formless;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_track_type_0, trackType));
return ErrorNumber.NotSupported;
}
}
switch(subtype)
{
case SUB_TYPE_COOKED:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.PackedInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
case SUB_TYPE_NONE:
aaruTrack.SubchannelType = TrackSubchannelType.None;
break;
case SUB_TYPE_RAW:
aaruTrack.SubchannelFile = imageFilter.Filename;
aaruTrack.SubchannelType = TrackSubchannelType.RawInterleaved;
aaruTrack.SubchannelFilter = imageFilter;
break;
default:
{
AaruLogging.Error(string.Format(Localization.Unsupported_subchannel_type_0,
subtype));
return ErrorNumber.NotSupported;
}
}
aaruTrack.Description = string.Format(Localization.Track_0, trackNo);
aaruTrack.EndSector = currentSector + frames - 1;
aaruTrack.File = imageFilter.Filename;
aaruTrack.FileType = "BINARY";
aaruTrack.Filter = imageFilter;
aaruTrack.StartSector = currentSector;
aaruTrack.Sequence = trackNo;
aaruTrack.Session = (ushort)(trackNo > 2 ? 2 : 1);
if(aaruTrack.Sequence == 1)
{
if(pregap <= 150)
{
aaruTrack.Indexes.Add(0, -150);
aaruTrack.Pregap = 150;
}
else
{
aaruTrack.Indexes.Add(0, -1 * (int)pregap);
aaruTrack.Pregap = pregap;
}
aaruTrack.Indexes.Add(1, (int)currentSector);
}
else if(pregap > 0)
{
aaruTrack.Indexes.Add(0, (int)currentSector);
aaruTrack.Pregap = pregap;
aaruTrack.Indexes.Add(1, (int)(currentSector + pregap));
}
else
aaruTrack.Indexes.Add(1, (int)currentSector);
currentSector += frames;
currentTrack++;
_tracks.Add(aaruTrack.Sequence, 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 is { CurrentCylinders: > 0, CurrentHeads: > 0, 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.MetadataMediaType = MetadataMediaType.BlockMedia;
}
else if(_isCdrom)
{
// Hardcoded on MAME for CD-ROM
_sectorsPerHunk = 8;
_imageInfo.MediaType = MediaType.CDROM;
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
foreach(Track aaruTrack in _tracks.Values)
_imageInfo.Sectors += aaruTrack.EndSector - aaruTrack.StartSector + 1;
}
else if(_isGdrom)
{
// Hardcoded on MAME for GD-ROM
_sectorsPerHunk = 8;
_imageInfo.MediaType = MediaType.GDROM;
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
foreach(Track aaruTrack in _tracks.Values)
_imageInfo.Sectors += aaruTrack.EndSector - aaruTrack.StartSector + 1;
}
else
{
AaruLogging.Error(Localization.Image_does_not_represent_a_known_media_aborting);
return ErrorNumber.NotSupported;
}
}
if(_isCdrom || _isGdrom)
{
_offsetmap = new Dictionary<ulong, uint>();
_partitions = [];
ulong partPos = 0;
foreach(Track aaruTrack in _tracks.Values)
{
var partition = new Partition
{
Description = aaruTrack.Description,
Size = (aaruTrack.EndSector - (ulong)aaruTrack.Indexes[1] + 1) * (ulong)aaruTrack.RawBytesPerSector,
Length = aaruTrack.EndSector - (ulong)aaruTrack.Indexes[1] + 1,
Sequence = aaruTrack.Sequence,
Offset = partPos,
Start = (ulong)aaruTrack.Indexes[1],
Type = aaruTrack.Type.ToString()
};
partPos += partition.Length;
_offsetmap.Add(aaruTrack.StartSector, aaruTrack.Sequence);
if(aaruTrack.SubchannelType != TrackSubchannelType.None)
{
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel);
}
switch(aaruTrack.Type)
{
case TrackType.CdMode1:
case TrackType.CdMode2Form1:
if(aaruTrack.RawBytesPerSector == 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.RawBytesPerSector == 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.RawBytesPerSector == 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.BytesPerSector > _imageInfo.SectorSize)
_imageInfo.SectorSize = (uint)aaruTrack.BytesPerSector;
_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<ulong, byte[]>();
_hunkCache = new Dictionary<ulong, byte[]>();
// TODO: Detect CompactFlash
// TODO: Get manufacturer and drive name from CIS if applicable
if(_cis != null) _imageInfo.MediaType = MediaType.PCCardTypeI;
_sectorBuilder = new SectorBuilder();
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
if(negative) return ErrorNumber.NotSupported;
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
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.RawBytesPerSector;
}
ulong hunkNo = sectorAddress / _sectorsPerHunk;
ulong secOff = sectorAddress * sectorSize % (_sectorsPerHunk * sectorSize);
ErrorNumber errno = GetHunk(hunkNo, out byte[] hunk);
if(errno != ErrorNumber.NoError) return errno;
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)
{
buffer = sector;
sectorStatus = SectorStatus.Dumped;
return ErrorNumber.NoError;
}
uint sectorOffset;
var mode2 = false;
switch(track.Type)
{
case TrackType.CdMode1:
{
if(track.RawBytesPerSector == 2352)
{
sectorOffset = 16;
sectorSize = 2048;
}
else
{
sectorOffset = 0;
sectorSize = 2048;
}
break;
}
case TrackType.CdMode2Form1:
{
if(track.RawBytesPerSector == 2352)
{
sectorOffset = 0;
sectorSize = 2352;
mode2 = true;
}
else
{
sectorOffset = 0;
sectorSize = 2048;
}
break;
}
case TrackType.CdMode2Form2:
{
if(track.RawBytesPerSector == 2352)
{
sectorOffset = 0;
sectorSize = 2352;
mode2 = true;
}
else
{
sectorOffset = 0;
sectorSize = 2324;
}
break;
}
case TrackType.CdMode2Formless:
{
sectorOffset = 0;
sectorSize = (uint)track.RawBytesPerSector;
mode2 = true;
break;
}
case TrackType.Audio:
{
sectorOffset = 0;
sectorSize = 2352;
break;
}
default:
return ErrorNumber.NotSupported;
}
sectorStatus = SectorStatus.Dumped;
buffer = new byte[sectorSize];
if(mode2)
buffer = Sector.GetUserDataFromMode2(sector);
else if(track.Type == TrackType.Audio && _swapAudio)
{
for(var 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 ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
{
buffer = null;
if(negative) return ErrorNumber.NotSupported;
if(_isHdd) return ErrorNumber.NotSupported;
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
var track = new Track();
uint sectorSize;
if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector))
{
track = GetTrack(sectorAddress);
sectorSize = (uint)track.RawBytesPerSector;
ulong hunkNo = sectorAddress / _sectorsPerHunk;
ulong secOff = sectorAddress * sectorSize % (_sectorsPerHunk * sectorSize);
ErrorNumber errno = GetHunk(hunkNo, out byte[] hunk);
if(errno != ErrorNumber.NoError) return errno;
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)
{
buffer = sector;
return ErrorNumber.NoError;
}
uint sectorOffset;
if(tag == SectorTagType.CdSectorSubchannel)
{
switch(track.SubchannelType)
{
case TrackSubchannelType.None:
return ErrorNumber.NoData;
case TrackSubchannelType.RawInterleaved:
sectorOffset = (uint)track.RawBytesPerSector;
sectorSize = 96;
break;
default:
return ErrorNumber.NotSupported;
}
}
else
{
switch(track.Type)
{
case TrackType.CdMode1:
case TrackType.CdMode2Form1:
{
if(track.RawBytesPerSector == 2352)
{
switch(tag)
{
case SectorTagType.CdSectorSync:
{
sectorOffset = 0;
sectorSize = 12;
break;
}
case SectorTagType.CdSectorHeader:
{
sectorOffset = 12;
sectorSize = 4;
break;
}
case SectorTagType.CdSectorSubHeader:
return ErrorNumber.NotSupported;
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:
return ErrorNumber.NotSupported;
}
}
else
return ErrorNumber.NoData;
break;
}
case TrackType.CdMode2Form2:
{
if(track.RawBytesPerSector == 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:
return ErrorNumber.NotSupported;
}
}
else
{
switch(tag)
{
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorEcc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
return ErrorNumber.NotSupported;
case SectorTagType.CdSectorSubHeader:
{
sectorOffset = 0;
sectorSize = 8;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2332;
sectorSize = 4;
break;
}
default:
return ErrorNumber.NotSupported;
}
}
break;
}
case TrackType.CdMode2Formless:
{
if(track.RawBytesPerSector == 2352)
{
switch(tag)
{
case SectorTagType.CdSectorSync:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorEcc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
return ErrorNumber.NotSupported;
case SectorTagType.CdSectorSubHeader:
{
sectorOffset = 0;
sectorSize = 8;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2332;
sectorSize = 4;
break;
}
default:
return ErrorNumber.NotSupported;
}
}
else
return ErrorNumber.NoData;
break;
}
case TrackType.Audio:
return ErrorNumber.NoData;
default:
return ErrorNumber.NotImplemented;
}
}
buffer = new byte[sectorSize];
if(track.Type == TrackType.Audio && _swapAudio)
{
for(var 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.Type == TrackType.Audio && _swapAudio)
{
for(var 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 ErrorNumber.NoError;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
out byte[] buffer)
{
buffer = 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();
for(uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSectorTag(sectorAddress + i, false, tag, out byte[] sector);
if(errno != ErrorNumber.NoError) return errno;
ms.Write(sector, 0, sector.Length);
}
buffer = ms.ToArray();
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
if(negative) return ErrorNumber.NotSupported;
if(_isHdd) return ReadSector(sectorAddress, false, out buffer, out sectorStatus);
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
var track = new Track();
if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector))
{
track = GetTrack(sectorAddress);
var sectorSize = (uint)track.RawBytesPerSector;
ulong hunkNo = sectorAddress / _sectorsPerHunk;
ulong secOff = sectorAddress * sectorSize % (_sectorsPerHunk * sectorSize);
ErrorNumber errno = GetHunk(hunkNo, out byte[] hunk);
if(errno != ErrorNumber.NoError) return errno;
sectorStatus = SectorStatus.Dumped;
sector = new byte[_imageInfo.SectorSize];
Array.Copy(hunk, (int)secOff, sector, 0, sector.Length);
if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear();
_sectorCache.Add(sectorAddress, sector);
}
sectorStatus = SectorStatus.Dumped;
buffer = new byte[track.RawBytesPerSector];
if(track.Type == TrackType.Audio && _swapAudio)
{
for(var i = 0; i < 2352; i += 2)
{
buffer[i + 1] = sector[i];
buffer[i] = sector[i + 1];
}
}
else
Array.Copy(sector, 0, buffer, 0, track.RawBytesPerSector);
switch(track.Type)
{
case TrackType.CdMode1 when track.RawBytesPerSector == 2048:
{
var 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.RawBytesPerSector == 2048:
{
var 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.RawBytesPerSector == 2324:
{
var 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.RawBytesPerSector == 2336:
{
var fullSector = new byte[2352];
_sectorBuilder.ReconstructPrefix(ref fullSector, TrackType.CdMode2Formless, (long)sectorAddress);
Array.Copy(buffer, 0, fullSector, 16, 2336);
buffer = fullSector;
break;
}
}
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(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 = ReadSectorLong(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;
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
{
buffer = null;
switch(tag)
{
case MediaTagType.ATA_IDENTIFY:
if(_imageInfo.ReadableMediaTags.Contains(MediaTagType.ATA_IDENTIFY))
buffer = _identify?.Clone() as byte[];
return buffer == null ? ErrorNumber.NoData : ErrorNumber.NoError;
case MediaTagType.PCMCIA_CIS:
if(_imageInfo.ReadableMediaTags.Contains(MediaTagType.PCMCIA_CIS)) buffer = _cis?.Clone() as byte[];
return buffer == null ? ErrorNumber.NoData : ErrorNumber.NoError;
default:
return ErrorNumber.NotSupported;
}
}
/// <inheritdoc />
public List<Track> GetSessionTracks(Session session) => _isHdd ? null : GetSessionTracks(session.Sequence);
/// <inheritdoc />
public List<Track> GetSessionTracks(ushort session) =>
_isHdd ? null : _tracks.Values.Where(track => track.Session == session).ToList();
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
return _isHdd
? ErrorNumber.NotSupported
: ReadSector(GetAbsoluteSector(sectorAddress, track), false, out buffer, out sectorStatus);
}
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag, out byte[] buffer)
{
buffer = null;
return _isHdd
? ErrorNumber.NotSupported
: ReadSectorTag(GetAbsoluteSector(sectorAddress, track), false, tag, out buffer);
}
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
return _isHdd
? ErrorNumber.NotSupported
: ReadSectors(GetAbsoluteSector(sectorAddress, track), false, length, out buffer, out sectorStatus);
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
out byte[] buffer)
{
buffer = null;
return _isHdd
? ErrorNumber.NotSupported
: ReadSectorsTag(GetAbsoluteSector(sectorAddress, track), false, length, tag, out buffer);
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
return _isHdd
? ErrorNumber.NotSupported
: ReadSectorLong(GetAbsoluteSector(sectorAddress, track), false, out buffer, out sectorStatus);
}
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
return _isHdd
? ErrorNumber.NotSupported
: ReadSectorsLong(GetAbsoluteSector(sectorAddress, track),
false,
length,
out buffer,
out sectorStatus);
}
#endregion
}