mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
2064 lines
86 KiB
C#
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
|
|
} |