Files
Aaru/Aaru.Images/CloneCD/Read.cs

1394 lines
58 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads CloneCD disc 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.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Structs;
using Aaru.Decoders.CD;
using Aaru.Helpers;
using Aaru.Logging;
using Humanizer;
using Session = Aaru.CommonTypes.Structs.Session;
namespace Aaru.Images;
public sealed partial class CloneCd
{
#region IWritableOpticalImage Members
/// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter)
{
if(imageFilter == null) return ErrorNumber.NoSuchFile;
_ccdFilter = imageFilter;
try
{
imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin);
_cueStream = new StreamReader(imageFilter.GetDataForkStream());
var lineNumber = 0;
var ccdIdRegex = new Regex(CCD_IDENTIFIER);
var discIdRegex = new Regex(DISC_IDENTIFIER);
var sessIdRegex = new Regex(SESSION_IDENTIFIER);
var entryIdRegex = new Regex(ENTRY_IDENTIFIER);
var trackIdRegex = new Regex(TRACK_IDENTIFIER);
var cdtIdRegex = new Regex(CDTEXT_IDENTIFIER);
var ccdVerRegex = new Regex(CCD_VERSION);
var discEntRegex = new Regex(DISC_ENTRIES);
var discSessRegex = new Regex(DISC_SESSIONS);
var discScrRegex = new Regex(DISC_SCRAMBLED);
var cdtLenRegex = new Regex(CDTEXT_LENGTH);
var discCatRegex = new Regex(DISC_CATALOG);
var sessPregRegex = new Regex(SESSION_PREGAP);
var sessSubcRegex = new Regex(SESSION_SUBCHANNEL);
var entSessRegex = new Regex(ENTRY_SESSION);
var entPointRegex = new Regex(ENTRY_POINT);
var entAdrRegex = new Regex(ENTRY_ADR);
var entCtrlRegex = new Regex(ENTRY_CONTROL);
var entTnoRegex = new Regex(ENTRY_TRACKNO);
var entAMinRegex = new Regex(ENTRY_AMIN);
var entASecRegex = new Regex(ENTRY_ASEC);
var entAFrameRegex = new Regex(ENTRY_AFRAME);
var entAlbaRegex = new Regex(ENTRY_ALBA);
var entZeroRegex = new Regex(ENTRY_ZERO);
var entPMinRegex = new Regex(ENTRY_PMIN);
var entPSecRegex = new Regex(ENTRY_PSEC);
var entPFrameRegex = new Regex(ENTRY_PFRAME);
var entPlbaRegex = new Regex(ENTRY_PLBA);
var cdtEntsRegex = new Regex(CDTEXT_ENTRIES);
var cdtEntRegex = new Regex(CDTEXT_ENTRY);
var trkModeRegex = new Regex(TRACK_MODE);
var trkIndexRegex = new Regex(TRACK_INDEX);
var inCcd = false;
var inDisk = false;
var inSession = false;
var inEntry = false;
var inTrack = false;
var inCdText = false;
var cdtMs = new MemoryStream();
int minSession = int.MaxValue;
int maxSession = int.MinValue;
var currentEntry = new FullTOC.TrackDataDescriptor();
byte currentTrackEntry = 0;
Dictionary<byte, int> trackModes = new();
Dictionary<byte, Dictionary<byte, int>> trackIndexes = new();
List<FullTOC.TrackDataDescriptor> entries = [];
_scrambled = false;
_catalog = null;
while(_cueStream.Peek() >= 0)
{
lineNumber++;
string line = _cueStream.ReadLine();
Match ccdIdMatch = ccdIdRegex.Match(line);
Match discIdMatch = discIdRegex.Match(line);
Match sessIdMatch = sessIdRegex.Match(line);
Match entryIdMatch = entryIdRegex.Match(line);
Match trackIdMatch = trackIdRegex.Match(line);
Match cdtIdMatch = cdtIdRegex.Match(line);
// [CloneCD]
if(ccdIdMatch.Success)
{
if(inDisk || inSession || inEntry || inTrack || inCdText)
{
AaruLogging.Error(string.Format(Localization.Found_CloneCD_out_of_order_in_line_0, lineNumber));
return ErrorNumber.InvalidArgument;
}
inCcd = true;
inDisk = false;
inSession = false;
inEntry = false;
inTrack = false;
inCdText = false;
}
else if(discIdMatch.Success ||
sessIdMatch.Success ||
entryIdMatch.Success ||
trackIdMatch.Success ||
cdtIdMatch.Success)
{
if(inEntry)
{
entries.Add(currentEntry);
currentEntry = new FullTOC.TrackDataDescriptor();
}
inCcd = false;
inDisk = discIdMatch.Success;
inSession = sessIdMatch.Success;
inEntry = entryIdMatch.Success;
inTrack = trackIdMatch.Success;
inCdText = cdtIdMatch.Success;
if(inTrack) currentTrackEntry = Convert.ToByte(trackIdMatch.Groups["number"].Value, 10);
}
else
{
if(inCcd)
{
Match ccdVerMatch = ccdVerRegex.Match(line);
if(!ccdVerMatch.Success) continue;
AaruLogging.Debug(MODULE_NAME, Localization.Found_Version_at_line_0, lineNumber);
_imageInfo.Version = ccdVerMatch.Groups["value"].Value;
if(_imageInfo.Version != "2" && _imageInfo.Version != "3")
{
AaruLogging.Error(Localization
.CloneCD_plugin_Warning_Unknown_CCD_image_version_0_may_not_work,
_imageInfo.Version);
}
}
else if(inDisk)
{
Match discEntMatch = discEntRegex.Match(line);
Match discSessMatch = discSessRegex.Match(line);
Match discScrMatch = discScrRegex.Match(line);
Match cdtLenMatch = cdtLenRegex.Match(line);
Match discCatMatch = discCatRegex.Match(line);
if(discEntMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_TocEntries_at_line_0, lineNumber);
else if(discSessMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_Sessions_at_line_0, lineNumber);
else if(discScrMatch.Success)
{
AaruLogging.Debug(MODULE_NAME,
Localization.Found_DataTracksScrambled_at_line_0,
lineNumber);
_scrambled |= discScrMatch.Groups["value"].Value == "1";
}
else if(cdtLenMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_CDTextLength_at_line_0, lineNumber);
else if(discCatMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_Catalog_at_line_0_smallcase, lineNumber);
_catalog = discCatMatch.Groups["value"].Value;
}
}
// TODO: Do not suppose here entries come sorted
else if(inCdText)
{
Match cdtEntsMatch = cdtEntsRegex.Match(line);
Match cdtEntMatch = cdtEntRegex.Match(line);
if(cdtEntsMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_CD_Text_Entries_at_line_0, lineNumber);
else if(cdtEntMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_CD_Text_Entry_at_line_0, lineNumber);
string[] bytes = cdtEntMatch.Groups["value"]
.Value.Split([' '], StringSplitOptions.RemoveEmptyEntries);
foreach(string byt in bytes) cdtMs.WriteByte(Convert.ToByte(byt, 16));
}
}
// Is this useful?
else if(inSession)
{
Match sessPregMatch = sessPregRegex.Match(line);
Match sessSubcMatch = sessSubcRegex.Match(line);
if(sessPregMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_PreGapMode_at_line_0, lineNumber);
else if(sessSubcMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_PreGapSubC_at_line_0, lineNumber);
}
else if(inEntry)
{
Match entSessMatch = entSessRegex.Match(line);
Match entPointMatch = entPointRegex.Match(line);
Match entAdrMatch = entAdrRegex.Match(line);
Match entCtrlMatch = entCtrlRegex.Match(line);
Match entTnoMatch = entTnoRegex.Match(line);
Match entAMinMatch = entAMinRegex.Match(line);
Match entASecMatch = entASecRegex.Match(line);
Match entAFrameMatch = entAFrameRegex.Match(line);
Match entAlbaMatch = entAlbaRegex.Match(line);
Match entZeroMatch = entZeroRegex.Match(line);
Match entPMinMatch = entPMinRegex.Match(line);
Match entPSecMatch = entPSecRegex.Match(line);
Match entPFrameMatch = entPFrameRegex.Match(line);
Match entPlbaMatch = entPlbaRegex.Match(line);
if(entSessMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_Session_at_line_0, lineNumber);
currentEntry.SessionNumber = Convert.ToByte(entSessMatch.Groups["value"].Value, 10);
if(currentEntry.SessionNumber < minSession) minSession = currentEntry.SessionNumber;
if(currentEntry.SessionNumber > maxSession) maxSession = currentEntry.SessionNumber;
}
else if(entPointMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_Point_at_line_0, lineNumber);
currentEntry.POINT = Convert.ToByte(entPointMatch.Groups["value"].Value, 16);
}
else if(entAdrMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_ADR_at_line_0, lineNumber);
currentEntry.ADR = Convert.ToByte(entAdrMatch.Groups["value"].Value, 16);
}
else if(entCtrlMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_Control_at_line_0, lineNumber);
currentEntry.CONTROL = Convert.ToByte(entCtrlMatch.Groups["value"].Value, 16);
}
else if(entTnoMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_TrackNo_at_line_0, lineNumber);
currentEntry.TNO = Convert.ToByte(entTnoMatch.Groups["value"].Value, 10);
}
else if(entAMinMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_AMin_at_line_0, lineNumber);
currentEntry.Min = Convert.ToByte(entAMinMatch.Groups["value"].Value, 10);
}
else if(entASecMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_ASec_at_line_0, lineNumber);
currentEntry.Sec = Convert.ToByte(entASecMatch.Groups["value"].Value, 10);
}
else if(entAFrameMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_AFrame_at_line_0, lineNumber);
currentEntry.Frame = Convert.ToByte(entAFrameMatch.Groups["value"].Value, 10);
}
else if(entAlbaMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_ALBA_at_line_0, lineNumber);
else if(entZeroMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_Zero_at_line_0, lineNumber);
currentEntry.Zero = Convert.ToByte(entZeroMatch.Groups["value"].Value, 10);
currentEntry.HOUR = (byte)((currentEntry.Zero & 0xF0) >> 4);
currentEntry.PHOUR = (byte)(currentEntry.Zero & 0x0F);
}
else if(entPMinMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_PMin_at_line_0, lineNumber);
currentEntry.PMIN = Convert.ToByte(entPMinMatch.Groups["value"].Value, 10);
}
else if(entPSecMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_PSec_at_line_0, lineNumber);
currentEntry.PSEC = Convert.ToByte(entPSecMatch.Groups["value"].Value, 10);
}
else if(entPFrameMatch.Success)
{
AaruLogging.Debug(MODULE_NAME, Localization.Found_PFrame_at_line_0, lineNumber);
currentEntry.PFRAME = Convert.ToByte(entPFrameMatch.Groups["value"].Value, 10);
}
else if(entPlbaMatch.Success)
AaruLogging.Debug(MODULE_NAME, Localization.Found_PLBA_at_line_0, lineNumber);
}
else if(inTrack)
{
Match trkModeMatch = trkModeRegex.Match(line);
Match trkIndexMatch = trkIndexRegex.Match(line);
if(trkModeMatch.Success && currentTrackEntry > 0)
trackModes[currentTrackEntry] = Convert.ToByte(trkModeMatch.Groups["value"].Value, 10);
else if(trkIndexMatch.Success && currentTrackEntry > 0)
{
var indexNo = Convert.ToByte(trkIndexMatch.Groups["index"].Value, 10);
var indexLba = Convert.ToInt32(trkIndexMatch.Groups["lba"].Value, 10);
if(!trackIndexes.TryGetValue(currentTrackEntry, out _))
trackIndexes[currentTrackEntry] = new Dictionary<byte, int>();
trackIndexes[currentTrackEntry][indexNo] = indexLba;
}
}
}
}
if(inEntry) entries.Add(currentEntry);
if(entries.Count == 0)
{
AaruLogging.Error(Localization.Did_not_find_any_track);
return ErrorNumber.InvalidArgument;
}
FullTOC.CDFullTOC toc;
toc.TrackDescriptors = entries.ToArray();
toc.LastCompleteSession = (byte)maxSession;
toc.FirstCompleteSession = (byte)minSession;
toc.DataLength = (ushort)(entries.Count * 11 + 2);
var tocMs = new MemoryStream();
tocMs.WriteByte(toc.FirstCompleteSession);
tocMs.WriteByte(toc.LastCompleteSession);
foreach(FullTOC.TrackDataDescriptor descriptor in toc.TrackDescriptors)
{
tocMs.WriteByte(descriptor.SessionNumber);
tocMs.WriteByte((byte)((descriptor.ADR << 4) + descriptor.CONTROL));
tocMs.WriteByte(descriptor.TNO);
tocMs.WriteByte(descriptor.POINT);
tocMs.WriteByte(descriptor.Min);
tocMs.WriteByte(descriptor.Sec);
tocMs.WriteByte(descriptor.Frame);
tocMs.WriteByte(descriptor.Zero);
tocMs.WriteByte(descriptor.PMIN);
tocMs.WriteByte(descriptor.PSEC);
tocMs.WriteByte(descriptor.PFRAME);
}
_fullToc = tocMs.ToArray();
_imageInfo.ReadableMediaTags.Add(MediaTagType.CD_FullTOC);
string dataFile = Path.GetFileNameWithoutExtension(imageFilter.BasePath) + ".img";
string subFile = Path.GetFileNameWithoutExtension(imageFilter.BasePath) + ".sub";
_dataFilter = PluginRegister.Singleton.GetFilter(dataFile);
if(_dataFilter == null)
{
AaruLogging.Error(Localization.Cannot_open_data_file);
return ErrorNumber.NoSuchFile;
}
_subFilter = PluginRegister.Singleton.GetFilter(subFile);
var curSessionNo = 0;
var currentTrack = new Track();
var firstTrackInSession = true;
Tracks = [];
ulong leadOutStart = 0;
_dataStream = _dataFilter.GetDataForkStream();
if(_subFilter != null) _subStream = _subFilter.GetDataForkStream();
_trackFlags = new Dictionary<byte, byte>();
foreach(FullTOC.TrackDataDescriptor descriptor in entries)
{
if(descriptor.SessionNumber > curSessionNo)
{
curSessionNo = descriptor.SessionNumber;
if(!firstTrackInSession)
{
currentTrack.EndSector = leadOutStart - 1;
Tracks.Add(currentTrack);
}
firstTrackInSession = true;
}
switch(descriptor.ADR)
{
case 1:
case 4:
switch(descriptor.POINT)
{
case 0xA0:
byte discType = descriptor.PSEC;
AaruLogging.Debug(MODULE_NAME, Localization.Disc_Type_0, discType);
break;
case 0xA2:
leadOutStart = GetLba(descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME);
break;
default:
if(descriptor.POINT is >= 0x01 and <= 0x63)
{
if(!firstTrackInSession)
{
currentTrack.EndSector =
GetLba(descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME) - 1;
Tracks.Add(currentTrack);
}
currentTrack = new Track
{
BytesPerSector = 2352,
File = _dataFilter.Filename,
FileType = _scrambled ? "SCRAMBLED" : "BINARY",
Filter = _dataFilter,
RawBytesPerSector = 2352,
Sequence = descriptor.POINT,
StartSector = GetLba(descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME),
Session = descriptor.SessionNumber
};
if(descriptor.POINT == 1)
{
currentTrack.Pregap = currentTrack.StartSector + 150;
currentTrack.Indexes[0] = -150;
currentTrack.Indexes[1] = (int)currentTrack.StartSector;
currentTrack.StartSector = 0;
}
else
{
if(firstTrackInSession)
{
currentTrack.Pregap = 150;
if(currentTrack.StartSector > 0)
{
currentTrack.Indexes[0] = (int)currentTrack.StartSector - 150;
if(currentTrack.Indexes[0] < 0) currentTrack.Indexes[0] = 0;
}
currentTrack.Indexes[1] = (int)currentTrack.StartSector;
currentTrack.StartSector -= 150;
}
else
currentTrack.Indexes[1] = (int)currentTrack.StartSector;
}
firstTrackInSession = false;
// Need to check exact data type later
if((TocControl)(descriptor.CONTROL & 0x0D) == TocControl.DataTrack ||
(TocControl)(descriptor.CONTROL & 0x0D) == TocControl.DataTrackIncremental)
currentTrack.Type = TrackType.Data;
else
currentTrack.Type = TrackType.Audio;
_trackFlags.TryAdd(descriptor.POINT, descriptor.CONTROL);
if(_subFilter != null)
{
currentTrack.SubchannelFile = _subFilter.Filename;
currentTrack.SubchannelFilter = _subFilter;
currentTrack.SubchannelType = TrackSubchannelType.Raw;
}
else
currentTrack.SubchannelType = TrackSubchannelType.None;
}
break;
}
break;
case 5:
switch(descriptor.POINT)
{
case 0xC0:
if(descriptor.PMIN == 97)
{
int type = descriptor.PFRAME % 10;
int frm = descriptor.PFRAME - type;
_imageInfo.MediaManufacturer = ATIP.ManufacturerFromATIP(descriptor.PSEC, frm);
if(_imageInfo.MediaManufacturer != "")
{
AaruLogging.Debug(MODULE_NAME,
Localization.Disc_manufactured_by_0,
_imageInfo.MediaManufacturer);
}
}
break;
}
break;
case 6:
{
var id = (uint)((descriptor.Min << 16) + (descriptor.Sec << 8) + descriptor.Frame);
AaruLogging.Debug(MODULE_NAME, Localization.Disc_ID_0_X6, id & 0x00FFFFFF);
_imageInfo.MediaSerialNumber = $"{id & 0x00FFFFFF:X6}";
break;
}
}
}
if(!firstTrackInSession)
{
currentTrack.EndSector = leadOutStart - 1;
Tracks.Add(currentTrack);
}
Track[] tmpTracks = Tracks.OrderBy(static t => t.Sequence).ToArray();
ulong currentDataOffset = 0;
ulong currentSubchannelOffset = 0;
foreach(Track tmpTrack in tmpTracks)
{
tmpTrack.FileOffset = currentDataOffset;
currentDataOffset += 2352 * (tmpTrack.EndSector - (ulong)tmpTrack.Indexes[1] + 1);
if(_subFilter != null)
{
tmpTrack.SubchannelOffset = currentSubchannelOffset;
currentSubchannelOffset += 96 * (tmpTrack.EndSector - (ulong)tmpTrack.Indexes[1] + 1);
}
if(tmpTrack.Indexes.TryGetValue(0, out int idx0))
{
if(idx0 < 0)
{
tmpTrack.FileOffset = 0;
tmpTrack.SubchannelOffset = 0;
}
else
{
int indexDifference = tmpTrack.Indexes[1] - idx0;
tmpTrack.FileOffset -= (ulong)(2352 * indexDifference);
if(_subFilter != null) tmpTrack.SubchannelOffset -= (ulong)(96 * indexDifference);
}
}
if(trackModes.TryGetValue((byte)tmpTrack.Sequence, out int trackMode))
{
tmpTrack.Type = trackMode switch
{
0 => TrackType.Audio,
1 => TrackType.CdMode1,
2 => TrackType.CdMode2Formless,
_ => TrackType.Data
};
}
if(trackIndexes.TryGetValue((byte)tmpTrack.Sequence, out Dictionary<byte, int> indexes))
{
foreach((byte index, int value) in indexes.OrderBy(static i => i.Key)
.Where(static trackIndex => trackIndex.Key > 1))
// Untested as of 20210711
tmpTrack.Indexes[index] = value;
}
if(tmpTrack.Type == TrackType.Data)
{
for(var s = 225; s < 750; s++)
{
var syncTest = new byte[12];
var sectTest = new byte[2352];
long pos = (long)tmpTrack.FileOffset + s * 2352;
if(pos >= _dataStream.Length + 2352 || s >= (int)(tmpTrack.EndSector - tmpTrack.StartSector))
break;
_dataStream.Seek(pos, SeekOrigin.Begin);
_dataStream.EnsureRead(sectTest, 0, 2352);
Array.Copy(sectTest, 0, syncTest, 0, 12);
if(!Sector.SyncMark.SequenceEqual(syncTest)) continue;
if(_scrambled) sectTest = Sector.Scramble(sectTest);
if(sectTest[15] == 1)
{
tmpTrack.BytesPerSector = 2048;
tmpTrack.Type = TrackType.CdMode1;
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.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);
if(_imageInfo.SectorSize < 2048) _imageInfo.SectorSize = 2048;
break;
}
if(sectTest[15] != 2) continue;
var subHdr1 = new byte[4];
var subHdr2 = new byte[4];
var empHdr = new byte[4];
Array.Copy(sectTest, 16, subHdr1, 0, 4);
Array.Copy(sectTest, 20, subHdr2, 0, 4);
if(subHdr1.SequenceEqual(subHdr2) && !empHdr.SequenceEqual(subHdr1))
{
if((subHdr1[2] & 0x20) == 0x20)
{
tmpTrack.BytesPerSector = 2324;
tmpTrack.Type = TrackType.CdMode2Form2;
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);
if(_imageInfo.SectorSize < 2324) _imageInfo.SectorSize = 2324;
break;
}
tmpTrack.BytesPerSector = 2048;
tmpTrack.Type = TrackType.CdMode2Form1;
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);
if(_imageInfo.SectorSize < 2048) _imageInfo.SectorSize = 2048;
break;
}
tmpTrack.BytesPerSector = 2336;
tmpTrack.Type = TrackType.CdMode2Formless;
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync))
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync);
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader))
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader);
if(_imageInfo.SectorSize < 2336) _imageInfo.SectorSize = 2336;
break;
}
}
else
{
if(_imageInfo.SectorSize < 2352) _imageInfo.SectorSize = 2352;
}
}
Tracks = tmpTracks.ToList();
if(_subFilter != null && !_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel);
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdTrackFlags);
Sessions = [];
var currentSession = new Session
{
EndTrack = uint.MinValue,
StartTrack = uint.MaxValue,
Sequence = 1
};
Partitions = [];
_offsetMap = new Dictionary<uint, ulong>();
foreach(Track track in Tracks)
{
if(track.EndSector + 1 > _imageInfo.Sectors) _imageInfo.Sectors = track.EndSector + 1;
if(track.Session == currentSession.Sequence)
{
if(track.Sequence > currentSession.EndTrack)
{
currentSession.EndSector = track.EndSector;
currentSession.EndTrack = track.Sequence;
}
if(track.Sequence < currentSession.StartTrack)
{
currentSession.StartSector = track.StartSector;
currentSession.StartTrack = track.Sequence;
}
}
else
{
Sessions.Add(currentSession);
currentSession = new Session
{
EndTrack = track.Sequence,
StartTrack = track.Sequence,
StartSector = track.StartSector,
EndSector = track.EndSector,
Sequence = track.Session
};
}
var partition = new Partition
{
Description = track.Description,
Size = (track.EndSector - (ulong)track.Indexes[1] + 1) * (ulong)track.RawBytesPerSector,
Length = track.EndSector - (ulong)track.Indexes[1] + 1,
Sequence = track.Sequence,
Offset = track.FileOffset,
Start = (ulong)track.Indexes[1],
Type = track.Type.Humanize()
};
Partitions.Add(partition);
_offsetMap.Add(track.Sequence, track.StartSector);
}
Sessions.Add(currentSession);
var data = false;
var mode2 = false;
var firstAudio = false;
var firstData = false;
var audio = false;
for(var i = 0; i < Tracks.Count; i++)
{
// First track is audio
firstAudio |= i == 0 && Tracks[i].Type == TrackType.Audio;
// First track is data
firstData |= i == 0 && Tracks[i].Type != TrackType.Audio;
// Any non first track is data
data |= i != 0 && Tracks[i].Type != TrackType.Audio;
// Any non first track is audio
audio |= i != 0 && Tracks[i].Type == TrackType.Audio;
mode2 = Tracks[i].Type switch
{
TrackType.CdMode2Form1 or TrackType.CdMode2Form2 or TrackType.CdMode2Formless => true,
_ => mode2
};
}
// TODO: Check format
_cdtext = cdtMs.ToArray();
if(!data && !firstData)
_imageInfo.MediaType = MediaType.CDDA;
else if(firstAudio && data && Sessions.Count > 1 && mode2)
_imageInfo.MediaType = MediaType.CDPLUS;
else if(firstData && audio || mode2)
_imageInfo.MediaType = MediaType.CDROMXA;
else if(!audio)
_imageInfo.MediaType = MediaType.CDROM;
else
_imageInfo.MediaType = MediaType.CD;
_imageInfo.Application = "CloneCD";
_imageInfo.ImageSize = (ulong)imageFilter.DataForkLength;
_imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
return ErrorNumber.NoError;
}
catch(Exception ex)
{
AaruLogging.Error(Localization.Exception_trying_to_identify_image_file_0, imageFilter.Filename);
AaruLogging.Exception(ex, Localization.Exception_trying_to_identify_image_file_0, imageFilter.Filename);
return ErrorNumber.UnexpectedException;
}
}
/// <inheritdoc />
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
{
buffer = null;
switch(tag)
{
case MediaTagType.CD_FullTOC:
buffer = _fullToc?.Clone() as byte[];
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
case MediaTagType.CD_TEXT:
{
if(_cdtext is { Length: > 0 }) buffer = _cdtext?.Clone() as byte[];
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
}
default:
return ErrorNumber.NotSupported;
}
}
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
return ReadSectors(sectorAddress, negative, 1, out buffer, out _);
}
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) =>
ReadSectorsTag(sectorAddress, negative, 1, tag, out buffer);
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
return ReadSectors(sectorAddress, 1, track, out buffer, out _);
}
/// <inheritdoc />
public ErrorNumber ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag, out byte[] buffer) =>
ReadSectorsTag(sectorAddress, 1, track, tag, out buffer);
/// <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;
foreach(KeyValuePair<uint, ulong> kvp in from kvp in _offsetMap
where sectorAddress >= kvp.Value
from track in Tracks
where track.Sequence == kvp.Key
where sectorAddress - kvp.Value <
track.EndSector - track.StartSector + 1
select kvp)
return ReadSectors(sectorAddress - kvp.Value, length, kvp.Key, out buffer, out sectorStatus);
return ErrorNumber.SectorNotFound;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
out byte[] buffer)
{
buffer = null;
if(negative) return ErrorNumber.NotSupported;
foreach(KeyValuePair<uint, ulong> kvp in _offsetMap.Where(kvp => sectorAddress >= kvp.Value)
.SelectMany(_ => Tracks,
static (kvp, track) => new
{
kvp,
track
})
.Where(static t => t.track.Sequence == t.kvp.Key)
.Where(t => sectorAddress - t.kvp.Value <
t.track.EndSector - t.track.StartSector + 1)
.Select(static t => t.kvp))
return ReadSectorsTag(sectorAddress - kvp.Value, length, kvp.Key, tag, out buffer);
return ErrorNumber.SectorNotFound;
}
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
Track aaruTrack = Tracks.FirstOrDefault(linqTrack => linqTrack.Sequence == track);
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
if(length + sectorAddress - 1 > aaruTrack.EndSector) return ErrorNumber.OutOfRange;
sectorStatus = new SectorStatus[length];
for(uint i = 0; i < length; i++) sectorStatus[i] = SectorStatus.Dumped;
uint sectorOffset;
uint sectorSize;
uint sectorSkip;
var mode2 = false;
switch(aaruTrack.Type)
{
case TrackType.Audio:
{
sectorOffset = 0;
sectorSize = 2352;
sectorSkip = 0;
break;
}
case TrackType.CdMode1:
{
sectorOffset = 16;
sectorSize = 2048;
sectorSkip = 288;
break;
}
case TrackType.CdMode2Formless:
case TrackType.CdMode2Form1:
case TrackType.CdMode2Form2:
{
mode2 = true;
sectorOffset = 0;
sectorSize = 2352;
sectorSkip = 0;
break;
}
default:
return ErrorNumber.NotSupported;
}
buffer = new byte[sectorSize * length];
_dataStream.Seek((long)(aaruTrack.FileOffset + sectorAddress * 2352), SeekOrigin.Begin);
if(mode2)
{
var mode2Ms = new MemoryStream((int)(sectorSize * length));
_dataStream.EnsureRead(buffer, 0, buffer.Length);
for(var i = 0; i < length; i++)
{
var sector = new byte[sectorSize];
Array.Copy(buffer, sectorSize * i, sector, 0, sectorSize);
sector = Sector.GetUserDataFromMode2(sector);
mode2Ms.Write(sector, 0, sector.Length);
}
buffer = mode2Ms.ToArray();
}
else if(sectorOffset == 0 && sectorSkip == 0)
_dataStream.EnsureRead(buffer, 0, buffer.Length);
else
{
for(var i = 0; i < length; i++)
{
var sector = new byte[sectorSize];
_dataStream.Seek(sectorOffset, SeekOrigin.Current);
_dataStream.EnsureRead(sector, 0, sector.Length);
_dataStream.Seek(sectorSkip, SeekOrigin.Current);
Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize);
}
}
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
out byte[] buffer)
{
buffer = null;
if(tag == SectorTagType.CdTrackFlags) track = (uint)sectorAddress;
Track aaruTrack = Tracks.FirstOrDefault(linqTrack => linqTrack.Sequence == track);
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
if(length + sectorAddress - 1 > aaruTrack.EndSector) return ErrorNumber.OutOfRange;
if(aaruTrack.Type == TrackType.Data) return ErrorNumber.NotSupported;
switch(tag)
{
case SectorTagType.CdSectorEcc:
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEdc:
case SectorTagType.CdSectorHeader:
case SectorTagType.CdSectorSubHeader:
case SectorTagType.CdSectorSync:
break;
case SectorTagType.CdTrackFlags:
if(!_trackFlags.TryGetValue((byte)aaruTrack.Sequence, out byte flags)) return ErrorNumber.NoData;
buffer = [flags];
return ErrorNumber.NoError;
case SectorTagType.CdSectorSubchannel:
buffer = new byte[96 * length];
_subStream.Seek((long)(aaruTrack.SubchannelOffset + sectorAddress * 96), SeekOrigin.Begin);
_subStream.EnsureRead(buffer, 0, buffer.Length);
buffer = Subchannel.Interleave(buffer);
return ErrorNumber.NoError;
default:
return ErrorNumber.NotSupported;
}
uint sectorOffset = 0;
uint sectorSize = 0;
uint sectorSkip = 0;
switch(aaruTrack.Type)
{
case TrackType.CdMode1:
switch(tag)
{
case SectorTagType.CdSectorSync:
{
sectorOffset = 0;
sectorSize = 12;
sectorSkip = 2340;
break;
}
case SectorTagType.CdSectorHeader:
{
sectorOffset = 12;
sectorSize = 4;
sectorSkip = 2336;
break;
}
case SectorTagType.CdSectorSubHeader:
return ErrorNumber.NotSupported;
case SectorTagType.CdSectorEcc:
{
sectorOffset = 2076;
sectorSize = 276;
sectorSkip = 0;
break;
}
case SectorTagType.CdSectorEccP:
{
sectorOffset = 2076;
sectorSize = 172;
sectorSkip = 104;
break;
}
case SectorTagType.CdSectorEccQ:
{
sectorOffset = 2248;
sectorSize = 104;
sectorSkip = 0;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2064;
sectorSize = 4;
sectorSkip = 284;
break;
}
}
break;
case TrackType.CdMode2Formless:
{
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;
sectorSkip = 2328;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2332;
sectorSize = 4;
sectorSkip = 0;
break;
}
}
break;
}
case TrackType.CdMode2Form1:
switch(tag)
{
case SectorTagType.CdSectorSync:
{
sectorOffset = 0;
sectorSize = 12;
sectorSkip = 2340;
break;
}
case SectorTagType.CdSectorHeader:
{
sectorOffset = 12;
sectorSize = 4;
sectorSkip = 2336;
break;
}
case SectorTagType.CdSectorSubHeader:
{
sectorOffset = 16;
sectorSize = 8;
sectorSkip = 2328;
break;
}
case SectorTagType.CdSectorEcc:
{
sectorOffset = 2076;
sectorSize = 276;
sectorSkip = 0;
break;
}
case SectorTagType.CdSectorEccP:
{
sectorOffset = 2076;
sectorSize = 172;
sectorSkip = 104;
break;
}
case SectorTagType.CdSectorEccQ:
{
sectorOffset = 2248;
sectorSize = 104;
sectorSkip = 0;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2072;
sectorSize = 4;
sectorSkip = 276;
break;
}
}
break;
case TrackType.CdMode2Form2:
switch(tag)
{
case SectorTagType.CdSectorSync:
{
sectorOffset = 0;
sectorSize = 12;
sectorSkip = 2340;
break;
}
case SectorTagType.CdSectorHeader:
{
sectorOffset = 12;
sectorSize = 4;
sectorSkip = 2336;
break;
}
case SectorTagType.CdSectorSubHeader:
{
sectorOffset = 16;
sectorSize = 8;
sectorSkip = 2328;
break;
}
case SectorTagType.CdSectorEdc:
{
sectorOffset = 2348;
sectorSize = 4;
sectorSkip = 0;
break;
}
default:
return ErrorNumber.NotSupported;
}
break;
case TrackType.Audio:
{
return ErrorNumber.NotSupported;
}
default:
return ErrorNumber.NotSupported;
}
buffer = new byte[sectorSize * length];
_dataStream.Seek((long)(aaruTrack.FileOffset + sectorAddress * 2352), SeekOrigin.Begin);
if(sectorOffset == 0 && sectorSkip == 0)
_dataStream.EnsureRead(buffer, 0, buffer.Length);
else
{
for(var i = 0; i < length; i++)
{
var sector = new byte[sectorSize];
_dataStream.Seek(sectorOffset, SeekOrigin.Current);
_dataStream.EnsureRead(sector, 0, sector.Length);
_dataStream.Seek(sectorSkip, SeekOrigin.Current);
Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize);
}
}
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
return ReadSectorsLong(sectorAddress, negative, 1, out buffer, out _);
}
/// <inheritdoc />
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus)
{
sectorStatus = SectorStatus.Dumped;
return ReadSectorsLong(sectorAddress, 1, track, out buffer, out _);
}
/// <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;
foreach(KeyValuePair<uint, ulong> kvp in from kvp in _offsetMap
where sectorAddress >= kvp.Value
from track in Tracks
where track.Sequence == kvp.Key
where sectorAddress - kvp.Value <
track.EndSector - track.StartSector + 1
select kvp)
return ReadSectorsLong(sectorAddress - kvp.Value, length, kvp.Key, out buffer, out sectorStatus);
return ErrorNumber.SectorNotFound;
}
/// <inheritdoc />
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
Track aaruTrack = Tracks.FirstOrDefault(linqTrack => linqTrack.Sequence == track);
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
if(length + sectorAddress - 1 > aaruTrack.EndSector) return ErrorNumber.OutOfMemory;
buffer = new byte[2352 * length];
sectorStatus = new SectorStatus[length];
for(uint i = 0; i < length; i++) sectorStatus[i] = SectorStatus.Dumped;
_dataStream.Seek((long)(aaruTrack.FileOffset + sectorAddress * 2352), SeekOrigin.Begin);
_dataStream.EnsureRead(buffer, 0, buffer.Length);
return ErrorNumber.NoError;
}
/// <inheritdoc />
public List<Track> GetSessionTracks(Session session) =>
Sessions.Contains(session) ? GetSessionTracks(session.Sequence) : null;
/// <inheritdoc />
public List<Track> GetSessionTracks(ushort session) => Tracks.Where(track => track.Session == session).ToList();
#endregion
}