mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
2297 lines
96 KiB
C#
2297 lines
96 KiB
C#
// /***************************************************************************
|
|
// Aaru Data Preservation Suite
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Filename : Read.cs
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
//
|
|
// Component : Disk image plugins.
|
|
//
|
|
// --[ Description ] ----------------------------------------------------------
|
|
//
|
|
// Reads BlindWrite 5 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.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Aaru.CommonTypes;
|
|
using Aaru.CommonTypes.Enums;
|
|
using Aaru.CommonTypes.Interfaces;
|
|
using Aaru.CommonTypes.Structs;
|
|
using Aaru.CommonTypes.Structs.Devices.SCSI.Modes;
|
|
using Aaru.Decoders.CD;
|
|
using Aaru.Decoders.DVD;
|
|
using Aaru.Decoders.SCSI;
|
|
using Aaru.Decoders.SCSI.MMC;
|
|
using Aaru.Filters;
|
|
using Aaru.Helpers;
|
|
using Aaru.Helpers.IO;
|
|
using Aaru.Logging;
|
|
using Humanizer;
|
|
using Sentry;
|
|
using DMI = Aaru.Decoders.Xbox.DMI;
|
|
using Sector = Aaru.Decoders.CD.Sector;
|
|
using Session = Aaru.CommonTypes.Structs.Session;
|
|
|
|
namespace Aaru.Images;
|
|
|
|
public sealed partial class BlindWrite5
|
|
{
|
|
#region IOpticalMediaImage Members
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber Open(IFilter imageFilter)
|
|
{
|
|
Stream stream = imageFilter.GetDataForkStream();
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
if(stream.Length < 276) return ErrorNumber.InvalidArgument;
|
|
|
|
var hdr = new byte[260];
|
|
stream.EnsureRead(hdr, 0, 260);
|
|
_header = Marshal.ByteArrayToStructureLittleEndian<Header>(hdr);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.signature = {0}", StringHandlers.CToString(_header.signature));
|
|
|
|
for(var i = 0; i < _header.unknown1.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown1[{1}] = 0x{0:X8}", _header.unknown1[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.profile = {0}", _header.profile);
|
|
AaruLogging.Debug(MODULE_NAME, "header.sessions = {0}", _header.sessions);
|
|
|
|
for(var i = 0; i < _header.unknown2.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown2[{1}] = 0x{0:X8}", _header.unknown2[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.mcnIsValid = {0}", _header.mcnIsValid);
|
|
AaruLogging.Debug(MODULE_NAME, "header.mcn = {0}", StringHandlers.CToString(_header.mcn));
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown3 = 0x{0:X4}", _header.unknown3);
|
|
|
|
for(var i = 0; i < _header.unknown4.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown4[{1}] = 0x{0:X8}", _header.unknown4[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.pmaLen = {0}", _header.pmaLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.atipLen = {0}", _header.atipLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.cdtLen = {0}", _header.cdtLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.cdInfoLen = {0}", _header.cdInfoLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.bcaLen = {0}", _header.bcaLen);
|
|
|
|
for(var i = 0; i < _header.unknown5.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown5[{1}] = 0x{0:X8}", _header.unknown5[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.dvdStrLen = {0}", _header.dvdStrLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.dvdInfoLen = {0}", _header.dvdInfoLen);
|
|
|
|
for(var i = 0; i < _header.unknown6.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "header.unknown6[{1}] = 0x{0:X2}", _header.unknown6[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.manufacturer = {0}", StringHandlers.CToString(_header.manufacturer));
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.product = {0}", StringHandlers.CToString(_header.product));
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.revision = {0}", StringHandlers.CToString(_header.revision));
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.vendor = {0}", StringHandlers.CToString(_header.vendor));
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.volumeId = {0}", StringHandlers.CToString(_header.volumeId));
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "header.mode2ALen = {0}", _header.mode2ALen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.unkBlkLen = {0}", _header.unkBlkLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.dataLen = {0}", _header.dataLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.sessionsLen = {0}", _header.sessionsLen);
|
|
AaruLogging.Debug(MODULE_NAME, "header.dpmLen = {0}", _header.dpmLen);
|
|
|
|
_mode2A = new byte[_header.mode2ALen];
|
|
|
|
if(_mode2A.Length > 0)
|
|
{
|
|
stream.EnsureRead(_mode2A, 0, _mode2A.Length);
|
|
_mode2A[1] -= 2;
|
|
var decoded2A = ModePage_2A.Decode(_mode2A);
|
|
|
|
if(decoded2A is not null)
|
|
AaruLogging.Debug(MODULE_NAME, Localization.mode_page_2A_0, Modes.PrettifyModePage_2A(decoded2A));
|
|
else
|
|
_mode2A = null;
|
|
}
|
|
|
|
_unkBlock = new byte[_header.unkBlkLen];
|
|
|
|
if(_unkBlock.Length > 0) stream.EnsureRead(_unkBlock, 0, _unkBlock.Length);
|
|
|
|
var temp = new byte[_header.pmaLen];
|
|
|
|
if(temp.Length > 0)
|
|
{
|
|
byte[] tushort = BitConverter.GetBytes((ushort)(temp.Length + 2));
|
|
stream.EnsureRead(temp, 0, temp.Length);
|
|
_pma = new byte[temp.Length + 4];
|
|
_pma[0] = tushort[1];
|
|
_pma[1] = tushort[0];
|
|
Array.Copy(temp, 0, _pma, 4, temp.Length);
|
|
|
|
PMA.CDPMA? decodedPma = PMA.Decode(_pma);
|
|
|
|
if(decodedPma.HasValue)
|
|
AaruLogging.Debug(MODULE_NAME, Localization.PMA_0, PMA.Prettify(decodedPma));
|
|
else
|
|
_pma = null;
|
|
}
|
|
|
|
_atip = new byte[_header.atipLen];
|
|
|
|
if(_atip.Length > 0)
|
|
stream.EnsureRead(_atip, 0, _atip.Length);
|
|
else
|
|
_atip = null;
|
|
|
|
_cdtext = new byte[_header.cdtLen];
|
|
|
|
if(_cdtext.Length > 0)
|
|
stream.EnsureRead(_cdtext, 0, _cdtext.Length);
|
|
else
|
|
_cdtext = null;
|
|
|
|
_bca = new byte[_header.bcaLen];
|
|
|
|
if(_bca.Length > 0)
|
|
stream.EnsureRead(_bca, 0, _bca.Length);
|
|
else
|
|
_bca = null;
|
|
|
|
temp = new byte[_header.dvdStrLen];
|
|
|
|
if(temp.Length > 0)
|
|
{
|
|
stream.EnsureRead(temp, 0, temp.Length);
|
|
_dmi = new byte[2052];
|
|
_pfi = new byte[2052];
|
|
|
|
// TODO: CMI
|
|
Array.Copy(temp, 2, _dmi, 4, 2048);
|
|
Array.Copy(temp, 0x802, _pfi, 4, 2048);
|
|
|
|
_pfi[0] = 0x08;
|
|
_pfi[1] = 0x02;
|
|
_dmi[0] = 0x08;
|
|
_dmi[1] = 0x02;
|
|
|
|
PFI.PhysicalFormatInformation? decodedPfi = PFI.Decode(_pfi, MediaType.DVDROM);
|
|
|
|
if(decodedPfi.HasValue)
|
|
AaruLogging.Debug(MODULE_NAME, Localization.PFI_0, PFI.Prettify(decodedPfi));
|
|
else
|
|
{
|
|
_pfi = null;
|
|
_dmi = null;
|
|
}
|
|
}
|
|
|
|
_discInformation = _header.profile switch
|
|
{
|
|
ProfileNumber.CDR
|
|
or ProfileNumber.CDROM
|
|
or ProfileNumber.CDRW
|
|
or ProfileNumber.DDCDR
|
|
or ProfileNumber.DDCDROM
|
|
or ProfileNumber.DDCDRW
|
|
or ProfileNumber.HDBURNROM
|
|
or ProfileNumber.HDBURNR
|
|
or ProfileNumber.HDBURNRW => new byte[_header.cdInfoLen],
|
|
_ => new byte[_header.dvdInfoLen]
|
|
};
|
|
|
|
if(_discInformation.Length > 0)
|
|
{
|
|
stream.EnsureRead(_discInformation, 0, _discInformation.Length);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
Localization.Disc_information_0,
|
|
PrintHex.ByteArrayToHexArrayString(_discInformation, 40));
|
|
}
|
|
else
|
|
_discInformation = null;
|
|
|
|
// How many data blocks
|
|
var tmpArray = new byte[4];
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
var dataBlockCount = BitConverter.ToUInt32(tmpArray, 0);
|
|
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
var dataPathLen = BitConverter.ToUInt32(tmpArray, 0);
|
|
var dataPathBytes = new byte[dataPathLen];
|
|
stream.EnsureRead(dataPathBytes, 0, dataPathBytes.Length);
|
|
_dataPath = Encoding.Unicode.GetString(dataPathBytes);
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Data_path_0, _dataPath);
|
|
|
|
_dataFiles = [];
|
|
|
|
for(var cD = 0; cD < dataBlockCount; cD++)
|
|
{
|
|
tmpArray = new byte[52];
|
|
|
|
var dataFile = new DataFile
|
|
{
|
|
Unknown1 = new uint[4],
|
|
Unknown2 = new uint[3]
|
|
};
|
|
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
dataFile.Type = BitConverter.ToUInt32(tmpArray, 0);
|
|
dataFile.Length = BitConverter.ToUInt32(tmpArray, 4);
|
|
dataFile.Unknown1[0] = BitConverter.ToUInt32(tmpArray, 8);
|
|
dataFile.Unknown1[1] = BitConverter.ToUInt32(tmpArray, 12);
|
|
dataFile.Unknown1[2] = BitConverter.ToUInt32(tmpArray, 16);
|
|
dataFile.Unknown1[3] = BitConverter.ToUInt32(tmpArray, 20);
|
|
dataFile.Offset = BitConverter.ToUInt32(tmpArray, 24);
|
|
dataFile.Unknown2[0] = BitConverter.ToUInt32(tmpArray, 28);
|
|
dataFile.Unknown2[1] = BitConverter.ToUInt32(tmpArray, 32);
|
|
dataFile.Unknown2[2] = BitConverter.ToUInt32(tmpArray, 36);
|
|
dataFile.StartLba = BitConverter.ToInt32(tmpArray, 40);
|
|
dataFile.Sectors = BitConverter.ToInt32(tmpArray, 44);
|
|
dataFile.FilenameLen = BitConverter.ToUInt32(tmpArray, 48);
|
|
dataFile.FilenameBytes = new byte[dataFile.FilenameLen];
|
|
|
|
tmpArray = new byte[dataFile.FilenameLen];
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
dataFile.FilenameBytes = tmpArray;
|
|
tmpArray = new byte[4];
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
dataFile.Unknown3 = BitConverter.ToUInt32(tmpArray, 0);
|
|
|
|
dataFile.Filename = Encoding.Unicode.GetString(dataFile.FilenameBytes);
|
|
_dataFiles.Add(dataFile);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.type = 0x{0:X8}", dataFile.Type);
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.length = {0}", dataFile.Length);
|
|
|
|
for(var i = 0; i < dataFile.Unknown1.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.unknown1[{1}] = {0}", dataFile.Unknown1[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.offset = {0}", dataFile.Offset);
|
|
|
|
for(var i = 0; i < dataFile.Unknown2.Length; i++)
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.unknown2[{1}] = {0}", dataFile.Unknown2[i], i);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.startLba = {0}", dataFile.StartLba);
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.sectors = {0}", dataFile.Sectors);
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.filenameLen = {0}", dataFile.FilenameLen);
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.filename = {0}", dataFile.Filename);
|
|
AaruLogging.Debug(MODULE_NAME, "dataFile.unknown3 = {0}", dataFile.Unknown3);
|
|
}
|
|
|
|
_bwSessions = [];
|
|
|
|
for(var ses = 0; ses < _header.sessions; ses++)
|
|
{
|
|
var session = new SessionDescriptor();
|
|
tmpArray = new byte[16];
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
session.Sequence = BitConverter.ToUInt16(tmpArray, 0);
|
|
session.Entries = tmpArray[2];
|
|
session.Unknown = tmpArray[3];
|
|
session.Start = BitConverter.ToInt32(tmpArray, 4);
|
|
session.End = BitConverter.ToInt32(tmpArray, 8);
|
|
session.FirstTrack = BitConverter.ToUInt16(tmpArray, 12);
|
|
session.LastTrack = BitConverter.ToUInt16(tmpArray, 14);
|
|
session.Tracks = new TrackDescriptor[session.Entries];
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].filename = {1}", ses, session.Sequence);
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].entries = {1}", ses, session.Entries);
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].unknown = {1}", ses, session.Unknown);
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].start = {1}", ses, session.Start);
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].end = {1}", ses, session.End);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].firstTrack = {1}", ses, session.FirstTrack);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "session[{0}].lastTrack = {1}", ses, session.LastTrack);
|
|
|
|
for(var tSeq = 0; tSeq < session.Entries; tSeq++)
|
|
{
|
|
var trk = new byte[72];
|
|
stream.EnsureRead(trk, 0, 72);
|
|
session.Tracks[tSeq] = Marshal.ByteArrayToStructureLittleEndian<TrackDescriptor>(trk);
|
|
|
|
if(session.Tracks[tSeq].type is TrackType.Dvd or TrackType.NotData)
|
|
{
|
|
session.Tracks[tSeq].unknown9[0] = 0;
|
|
session.Tracks[tSeq].unknown9[1] = 0;
|
|
stream.Seek(-8, SeekOrigin.Current);
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].type = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].type);
|
|
|
|
for(var i = 0; i < session.Tracks[tSeq].unknown1.Length; i++)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown1[{2}] = 0x{3:X2}",
|
|
ses,
|
|
tSeq,
|
|
i,
|
|
session.Tracks[tSeq].unknown1[i]);
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown2 = 0x{2:X8}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].unknown2);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].subchannel = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].subchannel);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown3 = 0x{2:X2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].unknown3);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].ctl = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].ctl);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].adr = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].adr);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].point = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].point);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown4 = 0x{2:X2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].tno);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].min = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].min);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].sec = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].sec);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].frame = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].frame);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].zero = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].zero);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].pmin = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].pmin);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].psec = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].psec);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].pframe = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].pframe);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown5 = 0x{2:X2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].unknown5);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].pregap = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].pregap);
|
|
|
|
for(var i = 0; i < session.Tracks[tSeq].unknown6.Length; i++)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown6[{2}] = 0x{3:X8}",
|
|
ses,
|
|
tSeq,
|
|
i,
|
|
session.Tracks[tSeq].unknown6[i]);
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].startLba = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].startLba);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].sectors = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].sectors);
|
|
|
|
for(var i = 0; i < session.Tracks[tSeq].unknown7.Length; i++)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown7[{2}] = 0x{3:X8}",
|
|
ses,
|
|
tSeq,
|
|
i,
|
|
session.Tracks[tSeq].unknown7[i]);
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].session = {2}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].session);
|
|
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown8 = 0x{2:X4}",
|
|
ses,
|
|
tSeq,
|
|
session.Tracks[tSeq].unknown8);
|
|
|
|
if(session.Tracks[tSeq].type is TrackType.Dvd or TrackType.NotData) continue;
|
|
|
|
{
|
|
for(var i = 0; i < session.Tracks[tSeq].unknown9.Length; i++)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
"session[{0}].track[{1}].unknown9[{2}] = 0x{3:X8}",
|
|
ses,
|
|
tSeq,
|
|
i,
|
|
session.Tracks[tSeq].unknown9[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
_bwSessions.Add(session);
|
|
}
|
|
|
|
_dpm = new byte[_header.dpmLen];
|
|
stream.EnsureRead(_dpm, 0, _dpm.Length);
|
|
|
|
// Unused
|
|
tmpArray = new byte[4];
|
|
stream.EnsureRead(tmpArray, 0, tmpArray.Length);
|
|
|
|
var footer = new byte[16];
|
|
stream.EnsureRead(footer, 0, footer.Length);
|
|
|
|
if(_bw5Footer.SequenceEqual(footer))
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Correctly_arrived_end_of_image);
|
|
else
|
|
{
|
|
AaruLogging.Error(Localization
|
|
.BlindWrite5_image_ends_after_expected_position_Probably_new_version_with_different_data_Errors_may_occur);
|
|
}
|
|
|
|
_filePaths = [];
|
|
|
|
foreach(DataFile dataFile in _dataFiles)
|
|
{
|
|
var chars = new DataFileCharacteristics();
|
|
string path = Path.Combine(_dataPath, dataFile.Filename);
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path)) != null)
|
|
{
|
|
chars.FileFilter = PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path));
|
|
chars.FilePath = path;
|
|
}
|
|
else
|
|
{
|
|
path = Path.Combine(_dataPath, dataFile.Filename.ToLower(CultureInfo.CurrentCulture));
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path)) != null)
|
|
{
|
|
chars.FileFilter = PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path));
|
|
chars.FilePath = path;
|
|
}
|
|
else
|
|
{
|
|
path = Path.Combine(_dataPath, dataFile.Filename.ToUpper(CultureInfo.CurrentCulture));
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path)) != null)
|
|
{
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path));
|
|
|
|
chars.FilePath = path;
|
|
}
|
|
else
|
|
{
|
|
path = Path.Combine(_dataPath.ToLower(CultureInfo.CurrentCulture), dataFile.Filename);
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path)) != null)
|
|
{
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path));
|
|
|
|
chars.FilePath = path;
|
|
}
|
|
else
|
|
{
|
|
path = Path.Combine(_dataPath.ToUpper(CultureInfo.CurrentCulture), dataFile.Filename);
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path)) != null)
|
|
{
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, path));
|
|
|
|
chars.FilePath = path;
|
|
}
|
|
else
|
|
{
|
|
path = Path.Combine(_dataPath, dataFile.Filename);
|
|
|
|
if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
path.ToLower(CultureInfo
|
|
.CurrentCulture))) !=
|
|
null)
|
|
{
|
|
chars.FilePath = path.ToLower(CultureInfo.CurrentCulture);
|
|
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
path.ToLower(CultureInfo
|
|
.CurrentCulture)));
|
|
}
|
|
else if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
path.ToUpper(CultureInfo
|
|
.CurrentCulture))) !=
|
|
null)
|
|
{
|
|
chars.FilePath = path.ToUpper(CultureInfo.CurrentCulture);
|
|
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
path.ToUpper(CultureInfo
|
|
.CurrentCulture)));
|
|
}
|
|
else if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename
|
|
.ToLower(CultureInfo
|
|
.CurrentCulture))) !=
|
|
null)
|
|
{
|
|
chars.FilePath = dataFile.Filename.ToLower(CultureInfo.CurrentCulture);
|
|
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename
|
|
.ToLower(CultureInfo
|
|
.CurrentCulture)));
|
|
}
|
|
else if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename
|
|
.ToUpper(CultureInfo
|
|
.CurrentCulture))) !=
|
|
null)
|
|
{
|
|
chars.FilePath = dataFile.Filename.ToUpper(CultureInfo.CurrentCulture);
|
|
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename
|
|
.ToUpper(CultureInfo
|
|
.CurrentCulture)));
|
|
}
|
|
else if(PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename)) !=
|
|
null)
|
|
{
|
|
chars.FilePath = dataFile.Filename;
|
|
|
|
chars.FileFilter =
|
|
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder,
|
|
dataFile.Filename));
|
|
}
|
|
else
|
|
{
|
|
AaruLogging.Error(Localization.Cannot_find_data_file_0, dataFile.Filename);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
long sectorSize = dataFile.Length / dataFile.Sectors;
|
|
|
|
if(sectorSize > 2352)
|
|
{
|
|
switch(sectorSize - 2352)
|
|
{
|
|
case 16:
|
|
chars.Subchannel = TrackSubchannelType.Q16Interleaved;
|
|
|
|
break;
|
|
case 96:
|
|
chars.Subchannel = TrackSubchannelType.PackedInterleaved;
|
|
|
|
break;
|
|
default:
|
|
AaruLogging.Error(Localization.BlindWrite5_found_unknown_subchannel_size_0, sectorSize - 2352);
|
|
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
}
|
|
else
|
|
chars.Subchannel = TrackSubchannelType.None;
|
|
|
|
chars.SectorSize = sectorSize;
|
|
chars.StartLba = dataFile.StartLba;
|
|
chars.Sectors = dataFile.Sectors;
|
|
chars.Offset = dataFile.Offset;
|
|
|
|
_filePaths.Add(chars);
|
|
}
|
|
|
|
Sessions = [];
|
|
Tracks = [];
|
|
Partitions = [];
|
|
var fullTocStream = new MemoryStream();
|
|
|
|
fullTocStream.Write(new byte[2], 0, 2);
|
|
|
|
ulong offsetBytes = 0;
|
|
_offsetMap = new Dictionary<uint, ulong>();
|
|
var isDvd = false;
|
|
byte firstSession = byte.MaxValue;
|
|
byte lastSession = 0;
|
|
_trackFlags = new Dictionary<uint, byte>();
|
|
_imageInfo.Sectors = 0;
|
|
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Building_maps);
|
|
|
|
foreach(SessionDescriptor ses in _bwSessions)
|
|
{
|
|
Sessions.Add(new Session
|
|
{
|
|
Sequence = ses.Sequence,
|
|
StartSector = ses.Start < 0 ? 0 : (ulong)ses.Start,
|
|
EndSector = (ulong)ses.End - 1,
|
|
StartTrack = ses.FirstTrack,
|
|
EndTrack = ses.LastTrack
|
|
});
|
|
|
|
if(ses.Sequence < firstSession) firstSession = (byte)ses.Sequence;
|
|
|
|
if(ses.Sequence > lastSession) lastSession = (byte)ses.Sequence;
|
|
|
|
foreach(TrackDescriptor trk in ses.Tracks)
|
|
{
|
|
var adrCtl = (byte)((trk.adr << 4) + trk.ctl);
|
|
fullTocStream.WriteByte((byte)trk.session);
|
|
fullTocStream.WriteByte(adrCtl);
|
|
fullTocStream.WriteByte(0x00);
|
|
fullTocStream.WriteByte(trk.point);
|
|
fullTocStream.WriteByte(trk.min);
|
|
fullTocStream.WriteByte(trk.sec);
|
|
fullTocStream.WriteByte(trk.frame);
|
|
fullTocStream.WriteByte(trk.zero);
|
|
fullTocStream.WriteByte(trk.pmin);
|
|
fullTocStream.WriteByte(trk.psec);
|
|
fullTocStream.WriteByte(trk.pframe);
|
|
|
|
if(trk.point >= 0xA0) continue;
|
|
|
|
var track = new Track();
|
|
var partition = new Partition();
|
|
|
|
_trackFlags.Add(trk.point, trk.ctl);
|
|
|
|
switch(trk.type)
|
|
{
|
|
case TrackType.Audio:
|
|
track.BytesPerSector = 2352;
|
|
track.RawBytesPerSector = 2352;
|
|
|
|
if(_imageInfo.SectorSize < 2352) _imageInfo.SectorSize = 2352;
|
|
|
|
break;
|
|
case TrackType.Mode1:
|
|
case TrackType.Mode2F1:
|
|
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);
|
|
|
|
track.BytesPerSector = 2048;
|
|
track.RawBytesPerSector = 2352;
|
|
|
|
if(_imageInfo.SectorSize < 2048) _imageInfo.SectorSize = 2048;
|
|
|
|
break;
|
|
case TrackType.Mode2:
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSync))
|
|
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSync);
|
|
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorHeader))
|
|
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorHeader);
|
|
|
|
track.BytesPerSector = 2336;
|
|
track.RawBytesPerSector = 2352;
|
|
|
|
if(_imageInfo.SectorSize < 2336) _imageInfo.SectorSize = 2336;
|
|
|
|
break;
|
|
case TrackType.Mode2F2:
|
|
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);
|
|
|
|
track.BytesPerSector = 2336;
|
|
track.RawBytesPerSector = 2352;
|
|
|
|
if(_imageInfo.SectorSize < 2324) _imageInfo.SectorSize = 2324;
|
|
|
|
break;
|
|
case TrackType.Dvd:
|
|
track.BytesPerSector = 2048;
|
|
track.RawBytesPerSector = 2048;
|
|
|
|
if(_imageInfo.SectorSize < 2048) _imageInfo.SectorSize = 2048;
|
|
|
|
isDvd = true;
|
|
|
|
break;
|
|
}
|
|
|
|
track.Description = string.Format(Localization.Track_0, trk.point);
|
|
track.StartSector = (ulong)(trk.startLba + trk.pregap);
|
|
track.EndSector = (ulong)(trk.sectors + trk.startLba) - 1;
|
|
|
|
var fileCharsForThisTrack = _filePaths
|
|
.Where(chars => trk.startLba >= chars.StartLba &&
|
|
trk.startLba + trk.sectors <= chars.StartLba + chars.Sectors)
|
|
.ToList();
|
|
|
|
if(fileCharsForThisTrack.Count == 0 &&
|
|
_filePaths.Any(static f => Path.GetExtension(f.FilePath).ToLowerInvariant() == ".b00"))
|
|
{
|
|
DataFileCharacteristics splitStartChars =
|
|
_filePaths.FirstOrDefault(static f => Path.GetExtension(f.FilePath).ToLowerInvariant() ==
|
|
".b00");
|
|
|
|
string filename = Path.GetFileNameWithoutExtension(splitStartChars.FilePath);
|
|
var lowerCaseExtension = false;
|
|
var lowerCaseFileName = false;
|
|
string basePath;
|
|
|
|
bool version5 = string.Equals(Path.GetExtension(imageFilter.Filename),
|
|
".B5T",
|
|
StringComparison.OrdinalIgnoreCase);
|
|
|
|
string firstExtension = version5 ? "B5I" : "B6I";
|
|
string firstExtensionLower = version5 ? "b5i" : "b6i";
|
|
|
|
if(File.Exists(Path.Combine(imageFilter.ParentFolder, $"{filename}.{firstExtension}")))
|
|
basePath = imageFilter.ParentFolder;
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder, $"{filename}.{firstExtensionLower}")))
|
|
{
|
|
basePath = imageFilter.ParentFolder;
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = imageFilter.ParentFolder;
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = imageFilter.ParentFolder;
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
|
|
else if(File.Exists(Path.Combine(_dataPath, $"{filename}.{firstExtension}")))
|
|
basePath = _dataPath;
|
|
else if(File.Exists(Path.Combine(_dataPath, $"{filename}.{firstExtensionLower}")))
|
|
{
|
|
basePath = _dataPath;
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(_dataPath,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = _dataPath;
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(_dataPath,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = _dataPath;
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
|
|
else if(File.Exists(Path.Combine(_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename}.{firstExtension}")))
|
|
basePath = _dataPath.ToLower(CultureInfo.CurrentCulture);
|
|
else if(File.Exists(Path.Combine(_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename}.{firstExtensionLower}")))
|
|
{
|
|
basePath = _dataPath.ToLower(CultureInfo.CurrentCulture);
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = _dataPath.ToLower(CultureInfo.CurrentCulture);
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = _dataPath.ToLower(CultureInfo.CurrentCulture);
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath,
|
|
$"{filename}.{firstExtension}")))
|
|
basePath = Path.Combine(imageFilter.ParentFolder, _dataPath);
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath,
|
|
$"{filename}.{firstExtensionLower}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder, _dataPath);
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder, _dataPath);
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath,
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder, _dataPath);
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename}.{firstExtension}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture));
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename}.b00")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture));
|
|
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture));
|
|
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture),
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = Path.Combine(imageFilter.ParentFolder,
|
|
_dataPath.ToLower(CultureInfo.CurrentCulture));
|
|
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine("", $"{filename}.{firstExtension}")))
|
|
basePath = "";
|
|
else if(File.Exists(Path.Combine("", $"{filename}.{firstExtensionLower}")))
|
|
{
|
|
basePath = "";
|
|
lowerCaseExtension = true;
|
|
}
|
|
else if(File.Exists(Path.Combine("",
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{firstExtension
|
|
}")))
|
|
{
|
|
basePath = "";
|
|
lowerCaseFileName = true;
|
|
}
|
|
else if(File.Exists(Path.Combine("",
|
|
$"{filename.ToLower(CultureInfo.CurrentCulture)}.{
|
|
firstExtensionLower}")))
|
|
{
|
|
basePath = "";
|
|
lowerCaseFileName = true;
|
|
lowerCaseExtension = true;
|
|
}
|
|
else
|
|
{
|
|
AaruLogging.Error(Localization.Could_not_find_image_for_track_0, trk.point);
|
|
|
|
return ErrorNumber.NoSuchFile;
|
|
}
|
|
|
|
var splitStream = new SplitJoinStream();
|
|
|
|
if(lowerCaseFileName) filename = filename.ToLower(CultureInfo.CurrentCulture);
|
|
|
|
string extension = lowerCaseExtension ? "b{0:D2}" : "B{0:D2}";
|
|
|
|
try
|
|
{
|
|
splitStream.Add(Path.Combine(basePath,
|
|
$"{filename}.{(lowerCaseExtension ? firstExtensionLower : firstExtension)}"),
|
|
FileMode.Open,
|
|
FileAccess.Read);
|
|
|
|
splitStream.AddRange(basePath, $"{filename}.{extension}");
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
SentrySdk.CaptureException(ex);
|
|
AaruLogging.Exception(ex, Localization.Could_not_find_image_for_track_0, trk.point);
|
|
AaruLogging.Error(Localization.Could_not_find_image_for_track_0, trk.point);
|
|
|
|
return ErrorNumber.NoSuchFile;
|
|
}
|
|
|
|
track.Filter = new ZZZNoFilter();
|
|
track.Filter.Open(splitStream);
|
|
track.File = $"{filename}.{extension}";
|
|
|
|
if(trk.startLba >= 0)
|
|
track.FileOffset = (ulong)(trk.startLba * splitStartChars.SectorSize + splitStartChars.Offset);
|
|
else
|
|
track.FileOffset = (ulong)(trk.startLba * -1 * splitStartChars.SectorSize);
|
|
|
|
track.FileType = "BINARY";
|
|
|
|
if(splitStartChars.Subchannel != TrackSubchannelType.None)
|
|
{
|
|
track.SubchannelFilter = track.Filter;
|
|
track.SubchannelFile = track.File;
|
|
track.SubchannelType = splitStartChars.Subchannel;
|
|
track.SubchannelOffset = track.FileOffset;
|
|
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel);
|
|
}
|
|
|
|
splitStartChars.FileFilter = new ZZZNoFilter();
|
|
splitStartChars.FileFilter.Open(splitStream);
|
|
splitStartChars.Sectors = trk.sectors;
|
|
splitStartChars.StartLba = trk.startLba;
|
|
_filePaths.Clear();
|
|
_filePaths.Add(splitStartChars);
|
|
}
|
|
else
|
|
{
|
|
foreach(DataFileCharacteristics chars in fileCharsForThisTrack)
|
|
{
|
|
track.Filter = chars.FileFilter;
|
|
track.File = chars.FileFilter.Filename;
|
|
|
|
if(trk.startLba >= 0)
|
|
{
|
|
track.FileOffset = (ulong)((trk.startLba - chars.StartLba) * chars.SectorSize) +
|
|
chars.Offset;
|
|
}
|
|
else
|
|
track.FileOffset = (ulong)(trk.startLba * -1 * chars.SectorSize);
|
|
|
|
track.FileType = "BINARY";
|
|
|
|
if(chars.Subchannel != TrackSubchannelType.None)
|
|
{
|
|
track.SubchannelFilter = track.Filter;
|
|
track.SubchannelFile = track.File;
|
|
track.SubchannelType = chars.Subchannel;
|
|
track.SubchannelOffset = track.FileOffset;
|
|
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdSectorSubchannel);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(track.Filter is null)
|
|
{
|
|
AaruLogging.Error(Localization.Could_not_find_image_for_track_0, trk.point);
|
|
|
|
return ErrorNumber.NoSuchFile;
|
|
}
|
|
|
|
track.Pregap = trk.pregap;
|
|
track.Sequence = trk.point;
|
|
track.Type = BlindWriteTrackTypeToTrackType(trk.type);
|
|
|
|
if(trk.pregap > 0 && track.StartSector > 0)
|
|
{
|
|
track.Indexes[0] = (int)track.StartSector - (int)trk.pregap;
|
|
|
|
if(track.Indexes[0] < 0) track.Indexes[0] = 0;
|
|
}
|
|
|
|
track.Indexes[1] = (int)track.StartSector;
|
|
|
|
partition.Description = track.Description;
|
|
|
|
partition.Size = (track.EndSector - track.StartSector) * (ulong)track.RawBytesPerSector;
|
|
|
|
partition.Length = track.EndSector - track.StartSector + 1;
|
|
partition.Sequence = track.Sequence;
|
|
partition.Offset = offsetBytes;
|
|
partition.Start = track.StartSector;
|
|
partition.Type = track.Type.Humanize();
|
|
|
|
offsetBytes += partition.Size;
|
|
|
|
if(track.StartSector >= trk.pregap) track.StartSector -= trk.pregap;
|
|
|
|
if(track.EndSector > _imageInfo.Sectors) _imageInfo.Sectors = track.EndSector + 1;
|
|
|
|
Tracks.Add(track);
|
|
Partitions.Add(partition);
|
|
_offsetMap.Add(track.Sequence, track.StartSector);
|
|
}
|
|
}
|
|
|
|
foreach(Track track in Tracks)
|
|
{
|
|
Session trackSession =
|
|
Sessions.FirstOrDefault(s => track.Sequence >= s.StartTrack && track.Sequence <= s.EndTrack);
|
|
|
|
track.Session = trackSession.Sequence;
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME, Localization.printing_track_map);
|
|
|
|
foreach(Track track in Tracks)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Partition_sequence_0, track.Sequence);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Track_description_0, track.Description);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Track_type_0, track.Type);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Track_starting_sector_0, track.StartSector);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Track_ending_sector_0, track.EndSector);
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME, Localization.printing_partition_map);
|
|
|
|
foreach(Partition partition in Partitions)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Partition_sequence_0, partition.Sequence);
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_name_0, partition.Name);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_description_0, partition.Description);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_type_0, partition.Type);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_starting_sector_0, partition.Start);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_sectors_0, partition.Length);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_starting_offset_0, partition.Offset);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "\t" + Localization.Partition_size_in_bytes_0, partition.Size);
|
|
}
|
|
|
|
if(!isDvd)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME, Localization.Rebuilding_TOC);
|
|
|
|
_fullToc = fullTocStream.ToArray();
|
|
AaruLogging.Debug(MODULE_NAME, Localization.TOC_len_0, _fullToc.Length);
|
|
|
|
_fullToc[0] = firstSession;
|
|
_fullToc[1] = lastSession;
|
|
|
|
_imageInfo.ReadableSectorTags.Add(SectorTagType.CdTrackFlags);
|
|
}
|
|
|
|
_imageInfo.MediaType = BlindWriteProfileToMediaType(_header.profile);
|
|
|
|
if(_dmi != null && _pfi != null)
|
|
{
|
|
PFI.PhysicalFormatInformation? pfi0 = PFI.Decode(_pfi, _imageInfo.MediaType);
|
|
|
|
// All discs I tested the disk category and part version (as well as the start PSN for DVD-RAM) where modified by Alcohol
|
|
// So much for archival value
|
|
if(pfi0.HasValue)
|
|
{
|
|
_imageInfo.MediaType = pfi0.Value.DiskCategory switch
|
|
{
|
|
DiskCategory.DVDPR => MediaType.DVDPR,
|
|
DiskCategory.DVDPRDL => MediaType.DVDPRDL,
|
|
DiskCategory.DVDPRW => MediaType.DVDPRW,
|
|
DiskCategory.DVDPRWDL => MediaType.DVDPRWDL,
|
|
DiskCategory.DVDR => pfi0.Value.PartVersion >= 6
|
|
? MediaType.DVDRDL
|
|
: MediaType.DVDR,
|
|
DiskCategory.DVDRAM => MediaType.DVDRAM,
|
|
DiskCategory.DVDRW => pfi0.Value.PartVersion >= 15
|
|
? MediaType.DVDRWDL
|
|
: MediaType.DVDRW,
|
|
DiskCategory.HDDVDR => MediaType.HDDVDR,
|
|
DiskCategory.HDDVDRAM => MediaType.HDDVDRAM,
|
|
DiskCategory.HDDVDROM => MediaType.HDDVDROM,
|
|
DiskCategory.HDDVDRW => MediaType.HDDVDRW,
|
|
DiskCategory.Nintendo => pfi0.Value.DiscSize == DVDSize.Eighty
|
|
? MediaType.GOD
|
|
: MediaType.WOD,
|
|
DiskCategory.UMD => MediaType.UMD,
|
|
_ => MediaType.DVDROM
|
|
};
|
|
|
|
if(DMI.IsXbox(_dmi))
|
|
_imageInfo.MediaType = MediaType.XGD;
|
|
else if(DMI.IsXbox360(_dmi)) _imageInfo.MediaType = MediaType.XGD2;
|
|
}
|
|
}
|
|
else if(_imageInfo.MediaType is MediaType.CD or MediaType.CDROM)
|
|
{
|
|
var data = false;
|
|
var mode2 = false;
|
|
var firstAudio = false;
|
|
var firstData = false;
|
|
var audio = false;
|
|
|
|
foreach(Track bwTrack in Tracks)
|
|
{
|
|
// First track is audio
|
|
firstAudio |= bwTrack.Sequence == 1 && bwTrack.Type == CommonTypes.Enums.TrackType.Audio;
|
|
|
|
// First track is data
|
|
firstData |= bwTrack.Sequence == 1 && bwTrack.Type != CommonTypes.Enums.TrackType.Audio;
|
|
|
|
// Any non first track is data
|
|
data |= bwTrack.Sequence != 1 && bwTrack.Type != CommonTypes.Enums.TrackType.Audio;
|
|
|
|
// Any non first track is audio
|
|
audio |= bwTrack.Sequence != 1 && bwTrack.Type == CommonTypes.Enums.TrackType.Audio;
|
|
|
|
mode2 = bwTrack.Type switch
|
|
{
|
|
CommonTypes.Enums.TrackType.CdMode2Formless
|
|
or CommonTypes.Enums.TrackType.CdMode2Form1
|
|
or CommonTypes.Enums.TrackType.CdMode2Form2 => true,
|
|
_ => mode2
|
|
};
|
|
}
|
|
|
|
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.DriveManufacturer = StringHandlers.CToString(_header.manufacturer);
|
|
_imageInfo.DriveModel = StringHandlers.CToString(_header.product);
|
|
_imageInfo.DriveFirmwareRevision = StringHandlers.CToString(_header.revision);
|
|
_imageInfo.Application = "BlindWrite";
|
|
|
|
if(string.Equals(Path.GetExtension(imageFilter.Filename), ".B5T", StringComparison.OrdinalIgnoreCase))
|
|
_imageInfo.ApplicationVersion = "5";
|
|
else if(string.Equals(Path.GetExtension(imageFilter.Filename), ".B6T", StringComparison.OrdinalIgnoreCase))
|
|
_imageInfo.ApplicationVersion = "6";
|
|
|
|
_imageInfo.Version = "5";
|
|
|
|
_imageInfo.ImageSize = (ulong)imageFilter.DataForkLength;
|
|
_imageInfo.CreationTime = imageFilter.CreationTime;
|
|
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
|
|
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
|
|
|
|
if(_pma != null)
|
|
{
|
|
PMA.CDPMA pma0 = PMA.Decode(_pma).Value;
|
|
|
|
foreach(uint id in from descriptor in pma0.PMADescriptors
|
|
where descriptor.ADR == 2
|
|
select (uint)((descriptor.Min << 16) + (descriptor.Sec << 8) + descriptor.Frame))
|
|
_imageInfo.MediaSerialNumber = $"{id & 0x00FFFFFF:X6}";
|
|
}
|
|
|
|
if(_atip != null)
|
|
{
|
|
var atipTmp = new byte[_atip.Length + 4];
|
|
Array.Copy(_atip, 0, atipTmp, 4, _atip.Length);
|
|
atipTmp[0] = (byte)((_atip.Length & 0xFF00) >> 8);
|
|
atipTmp[1] = (byte)(_atip.Length & 0xFF);
|
|
|
|
ATIP.CDATIP atip0 = ATIP.Decode(atipTmp);
|
|
|
|
_imageInfo.MediaType = atip0?.DiscType ?? false ? MediaType.CDRW : MediaType.CDR;
|
|
|
|
if(atip0.LeadInStartMin == 97)
|
|
{
|
|
int type = atip0.LeadInStartFrame % 10;
|
|
int frm = atip0.LeadInStartFrame - type;
|
|
_imageInfo.MediaManufacturer = ATIP.ManufacturerFromATIP(atip0.LeadInStartSec, frm);
|
|
}
|
|
}
|
|
|
|
var isBd = false;
|
|
|
|
if(_imageInfo.MediaType is MediaType.BDR or MediaType.BDRE or MediaType.BDROM)
|
|
{
|
|
isDvd = false;
|
|
isBd = true;
|
|
}
|
|
|
|
if(isBd && _imageInfo.Sectors > 24438784)
|
|
{
|
|
_imageInfo.MediaType = _imageInfo.MediaType switch
|
|
{
|
|
MediaType.BDR => MediaType.BDRXL,
|
|
MediaType.BDRE => MediaType.BDREXL,
|
|
_ => _imageInfo.MediaType
|
|
};
|
|
}
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "ImageInfo.mediaType = {0}", _imageInfo.MediaType);
|
|
|
|
if(_mode2A != null) _imageInfo.ReadableMediaTags.Add(MediaTagType.SCSI_MODEPAGE_2A);
|
|
|
|
if(_pma != null) _imageInfo.ReadableMediaTags.Add(MediaTagType.CD_PMA);
|
|
|
|
if(_atip != null) _imageInfo.ReadableMediaTags.Add(MediaTagType.CD_ATIP);
|
|
|
|
if(_cdtext != null) _imageInfo.ReadableMediaTags.Add(MediaTagType.CD_TEXT);
|
|
|
|
if(_bca != null)
|
|
{
|
|
if(isDvd)
|
|
_imageInfo.ReadableMediaTags.Add(MediaTagType.DVD_BCA);
|
|
else if(isBd) _imageInfo.ReadableMediaTags.Add(MediaTagType.BD_BCA);
|
|
}
|
|
|
|
byte[] tmp;
|
|
|
|
if(_dmi != null)
|
|
{
|
|
tmp = new byte[2048];
|
|
Array.Copy(_dmi, 4, tmp, 0, 2048);
|
|
_dmi = tmp;
|
|
_imageInfo.ReadableMediaTags.Add(MediaTagType.DVD_DMI);
|
|
}
|
|
|
|
if(_pfi != null)
|
|
{
|
|
tmp = new byte[2048];
|
|
Array.Copy(_pfi, 4, tmp, 0, 2048);
|
|
_pfi = tmp;
|
|
_imageInfo.ReadableMediaTags.Add(MediaTagType.DVD_PFI);
|
|
}
|
|
|
|
if(_fullToc != null) _imageInfo.ReadableMediaTags.Add(MediaTagType.CD_FullTOC);
|
|
|
|
if(_imageInfo is { MediaType: MediaType.XGD2, Sectors: 25063 or 4229664 or 4246304 })
|
|
|
|
// Wxripper unlock
|
|
_imageInfo.MediaType = MediaType.XGD3;
|
|
|
|
AaruLogging.Verbose(Localization.BlindWrite_image_describes_a_disc_of_type_0, _imageInfo.MediaType);
|
|
|
|
if(_header.profile is ProfileNumber.CDR or ProfileNumber.CDRW or ProfileNumber.CDROM)
|
|
return ErrorNumber.NoError;
|
|
|
|
foreach(Track track in Tracks)
|
|
{
|
|
track.Pregap = 0;
|
|
track.Indexes?.Clear();
|
|
}
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
|
|
{
|
|
buffer = null;
|
|
|
|
switch(tag)
|
|
{
|
|
case MediaTagType.SCSI_MODEPAGE_2A:
|
|
buffer = _mode2A?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.CD_PMA:
|
|
buffer = _pma?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.CD_ATIP:
|
|
buffer = _atip?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.CD_TEXT:
|
|
buffer = _cdtext?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.DVD_BCA:
|
|
case MediaTagType.BD_BCA:
|
|
buffer = _bca?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.DVD_PFI:
|
|
buffer = _pfi?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.DVD_DMI:
|
|
buffer = _dmi?.Clone() as byte[];
|
|
|
|
return buffer != null ? ErrorNumber.NoError : ErrorNumber.NoData;
|
|
case MediaTagType.CD_FullTOC:
|
|
buffer = _fullToc?.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)
|
|
{
|
|
buffer = null;
|
|
sectorStatus = SectorStatus.Dumped;
|
|
|
|
if(negative) return ErrorNumber.NotSupported;
|
|
|
|
return ReadSectors(sectorAddress, false, 1, out buffer, out _);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) =>
|
|
ReadSectorsTag(sectorAddress, false, 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 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 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;
|
|
|
|
// TODO: Cross data files
|
|
Track aaruTrack = Tracks.FirstOrDefault(bwTrack => bwTrack.Sequence == track);
|
|
|
|
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
|
|
|
|
if(length + sectorAddress > aaruTrack.EndSector - aaruTrack.StartSector + 1) return ErrorNumber.OutOfRange;
|
|
|
|
sectorStatus = new SectorStatus[length];
|
|
for(uint i = 0; i < length; i++) sectorStatus[i] = SectorStatus.Dumped;
|
|
|
|
DataFileCharacteristics chars = (from characteristics in _filePaths
|
|
let firstSector = characteristics.StartLba
|
|
let lastSector = firstSector + characteristics.Sectors - 1
|
|
let wantedSector = (int)(sectorAddress + aaruTrack.StartSector)
|
|
where wantedSector >= firstSector && wantedSector <= lastSector
|
|
select characteristics).FirstOrDefault();
|
|
|
|
if(string.IsNullOrEmpty(chars.FilePath) || chars.FileFilter == null) return ErrorNumber.SectorNotFound;
|
|
|
|
uint sectorOffset;
|
|
uint sectorSize;
|
|
uint sectorSkip;
|
|
var mode2 = false;
|
|
|
|
switch(aaruTrack.Type)
|
|
{
|
|
case CommonTypes.Enums.TrackType.CdMode1:
|
|
{
|
|
sectorOffset = 16;
|
|
sectorSize = 2048;
|
|
sectorSkip = 288;
|
|
|
|
break;
|
|
}
|
|
case CommonTypes.Enums.TrackType.CdMode2Formless:
|
|
case CommonTypes.Enums.TrackType.CdMode2Form1:
|
|
case CommonTypes.Enums.TrackType.CdMode2Form2:
|
|
{
|
|
mode2 = true;
|
|
sectorOffset = 0;
|
|
sectorSize = 2352;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case CommonTypes.Enums.TrackType.Audio:
|
|
{
|
|
sectorOffset = 0;
|
|
sectorSize = 2352;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case CommonTypes.Enums.TrackType.Data:
|
|
{
|
|
sectorOffset = 0;
|
|
sectorSize = 2048;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.None:
|
|
sectorSkip += 0;
|
|
|
|
break;
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
sectorSkip += 16;
|
|
|
|
break;
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
sectorSkip += 96;
|
|
|
|
break;
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
buffer = new byte[sectorSize * length];
|
|
|
|
_imageStream = chars.FileFilter.GetDataForkStream();
|
|
var br = new BinaryReader(_imageStream);
|
|
|
|
br.BaseStream.Seek((long)aaruTrack.FileOffset +
|
|
(long)(sectorAddress * (sectorOffset + sectorSize + sectorSkip)),
|
|
SeekOrigin.Begin);
|
|
|
|
if(mode2)
|
|
{
|
|
var mode2Ms = new MemoryStream((int)(sectorSize * length));
|
|
|
|
buffer = br.ReadBytes((int)((sectorSize + sectorSkip) * length));
|
|
|
|
for(var i = 0; i < length; i++)
|
|
{
|
|
var sector = new byte[sectorSize];
|
|
Array.Copy(buffer, (sectorSize + sectorSkip) * i, sector, 0, sectorSize);
|
|
sector = Sector.GetUserDataFromMode2(sector);
|
|
mode2Ms.Write(sector, 0, sector.Length);
|
|
}
|
|
|
|
buffer = mode2Ms.ToArray();
|
|
}
|
|
else if(sectorOffset == 0 && sectorSkip == 0)
|
|
buffer = br.ReadBytes((int)(sectorSize * length));
|
|
else
|
|
{
|
|
for(var i = 0; i < length; i++)
|
|
{
|
|
br.BaseStream.Seek(sectorOffset, SeekOrigin.Current);
|
|
byte[] sector = br.ReadBytes((int)sectorSize);
|
|
br.BaseStream.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;
|
|
|
|
// TODO: Cross data files
|
|
Track aaruTrack = Tracks.FirstOrDefault(bwTrack => bwTrack.Sequence == track);
|
|
|
|
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
|
|
|
|
if(length + sectorAddress > aaruTrack.EndSector - aaruTrack.StartSector + 1) return ErrorNumber.OutOfRange;
|
|
|
|
DataFileCharacteristics chars = (from characteristics in _filePaths
|
|
let firstSector = characteristics.StartLba
|
|
let lastSector = firstSector + characteristics.Sectors - 1
|
|
let wantedSector = (int)(sectorAddress + aaruTrack.StartSector)
|
|
where wantedSector >= firstSector && wantedSector <= lastSector
|
|
select characteristics).FirstOrDefault();
|
|
|
|
if(string.IsNullOrEmpty(chars.FilePath) || chars.FileFilter == null) return ErrorNumber.SectorNotFound;
|
|
|
|
if(aaruTrack.Type == CommonTypes.Enums.TrackType.Data) return ErrorNumber.NotSupported;
|
|
|
|
switch(tag)
|
|
{
|
|
case SectorTagType.CdSectorEcc:
|
|
case SectorTagType.CdSectorEccP:
|
|
case SectorTagType.CdSectorEccQ:
|
|
case SectorTagType.CdSectorEdc:
|
|
case SectorTagType.CdSectorHeader:
|
|
case SectorTagType.CdSectorSubchannel:
|
|
case SectorTagType.CdSectorSubHeader:
|
|
case SectorTagType.CdSectorSync:
|
|
break;
|
|
case SectorTagType.CdTrackFlags:
|
|
if(!_trackFlags.TryGetValue((uint)sectorAddress, out byte flag)) return ErrorNumber.NoData;
|
|
|
|
buffer = [flag];
|
|
|
|
return ErrorNumber.NoError;
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
uint sectorOffset;
|
|
uint sectorSize;
|
|
uint sectorSkip;
|
|
|
|
switch(aaruTrack.Type)
|
|
{
|
|
case CommonTypes.Enums.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;
|
|
}
|
|
case SectorTagType.CdSectorSubchannel:
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 96;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 16;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case TrackSubchannelType.None:
|
|
{
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
return ErrorNumber.NotSupported;
|
|
|
|
buffer = new byte[length * 96];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
case CommonTypes.Enums.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;
|
|
}
|
|
case SectorTagType.CdSectorSubchannel:
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 96;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 16;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case TrackSubchannelType.None:
|
|
{
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
return ErrorNumber.NotSupported;
|
|
|
|
buffer = new byte[length * 96];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CommonTypes.Enums.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;
|
|
}
|
|
case SectorTagType.CdSectorSubchannel:
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 96;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 16;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case TrackSubchannelType.None:
|
|
{
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
return ErrorNumber.NotSupported;
|
|
|
|
buffer = new byte[length * 96];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
case CommonTypes.Enums.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;
|
|
}
|
|
case SectorTagType.CdSectorSubchannel:
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 96;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 16;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case TrackSubchannelType.None:
|
|
{
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
return ErrorNumber.NotSupported;
|
|
|
|
buffer = new byte[length * 96];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
case CommonTypes.Enums.TrackType.Audio:
|
|
{
|
|
switch(tag)
|
|
{
|
|
case SectorTagType.CdSectorSubchannel:
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 96;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
{
|
|
sectorOffset = 2352;
|
|
sectorSize = 16;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case TrackSubchannelType.None:
|
|
{
|
|
if(!_imageInfo.ReadableSectorTags.Contains(SectorTagType.CdSectorSubchannel))
|
|
return ErrorNumber.NotSupported;
|
|
|
|
buffer = new byte[length * 96];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
if(tag != SectorTagType.CdSectorSubchannel)
|
|
{
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.None:
|
|
sectorSkip += 0;
|
|
|
|
break;
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
sectorSkip += 16;
|
|
|
|
break;
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
sectorSkip += 96;
|
|
|
|
break;
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
}
|
|
|
|
buffer = new byte[sectorSize * length];
|
|
|
|
_imageStream = aaruTrack.Filter.GetDataForkStream();
|
|
var br = new BinaryReader(_imageStream);
|
|
|
|
br.BaseStream.Seek((long)aaruTrack.FileOffset +
|
|
(long)(sectorAddress * (sectorOffset + sectorSize + sectorSkip)),
|
|
SeekOrigin.Begin);
|
|
|
|
if(sectorOffset == 0 && sectorSkip == 0)
|
|
buffer = br.ReadBytes((int)(sectorSize * length));
|
|
else
|
|
{
|
|
for(var i = 0; i < length; i++)
|
|
{
|
|
br.BaseStream.Seek(sectorOffset, SeekOrigin.Current);
|
|
byte[] sector = br.ReadBytes((int)sectorSize);
|
|
br.BaseStream.Seek(sectorSkip, SeekOrigin.Current);
|
|
Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize);
|
|
}
|
|
}
|
|
|
|
if(tag != SectorTagType.CdSectorSubchannel) return ErrorNumber.NoError;
|
|
|
|
buffer = chars.Subchannel switch
|
|
{
|
|
TrackSubchannelType.Q16Interleaved => Subchannel.ConvertQToRaw(buffer),
|
|
TrackSubchannelType.PackedInterleaved => Subchannel.Interleave(buffer),
|
|
_ => buffer
|
|
};
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
|
|
out SectorStatus sectorStatus)
|
|
{
|
|
sectorStatus = SectorStatus.Dumped;
|
|
|
|
return ReadSectorsLong(sectorAddress, false, 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;
|
|
|
|
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;
|
|
|
|
// TODO: Cross data files
|
|
Track aaruTrack = Tracks.FirstOrDefault(bwTrack => bwTrack.Sequence == track);
|
|
|
|
if(aaruTrack is null) return ErrorNumber.SectorNotFound;
|
|
|
|
if(length + sectorAddress > aaruTrack.EndSector - aaruTrack.StartSector + 1) return ErrorNumber.OutOfRange;
|
|
|
|
sectorStatus = new SectorStatus[length];
|
|
for(uint i = 0; i < length; i++) sectorStatus[i] = SectorStatus.Dumped;
|
|
|
|
DataFileCharacteristics chars = (from characteristics in _filePaths
|
|
let firstSector = characteristics.StartLba
|
|
let lastSector = firstSector + characteristics.Sectors - 1
|
|
let wantedSector = (int)(sectorAddress + aaruTrack.StartSector)
|
|
where wantedSector >= firstSector && wantedSector <= lastSector
|
|
select characteristics).FirstOrDefault();
|
|
|
|
if(string.IsNullOrEmpty(chars.FilePath) || chars.FileFilter == null) return ErrorNumber.SectorNotFound;
|
|
|
|
uint sectorOffset;
|
|
uint sectorSize;
|
|
uint sectorSkip;
|
|
|
|
switch(aaruTrack.Type)
|
|
{
|
|
case CommonTypes.Enums.TrackType.CdMode1:
|
|
case CommonTypes.Enums.TrackType.CdMode2Formless:
|
|
case CommonTypes.Enums.TrackType.CdMode2Form1:
|
|
case CommonTypes.Enums.TrackType.CdMode2Form2:
|
|
case CommonTypes.Enums.TrackType.Audio:
|
|
{
|
|
sectorOffset = 0;
|
|
sectorSize = 2352;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
case CommonTypes.Enums.TrackType.Data:
|
|
{
|
|
sectorOffset = 0;
|
|
sectorSize = 2048;
|
|
sectorSkip = 0;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
switch(chars.Subchannel)
|
|
{
|
|
case TrackSubchannelType.None:
|
|
sectorSkip += 0;
|
|
|
|
break;
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
sectorSkip += 16;
|
|
|
|
break;
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
sectorSkip += 96;
|
|
|
|
break;
|
|
default:
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
buffer = new byte[sectorSize * length];
|
|
|
|
_imageStream = aaruTrack.Filter.GetDataForkStream();
|
|
var br = new BinaryReader(_imageStream);
|
|
|
|
br.BaseStream.Seek((long)aaruTrack.FileOffset + (long)(sectorAddress * (sectorSize + sectorSkip)),
|
|
SeekOrigin.Begin);
|
|
|
|
if(sectorSkip == 0)
|
|
buffer = br.ReadBytes((int)(sectorSize * length));
|
|
else
|
|
{
|
|
for(var i = 0; i < length; i++)
|
|
{
|
|
br.BaseStream.Seek(sectorOffset, SeekOrigin.Current);
|
|
byte[] sector = br.ReadBytes((int)sectorSize);
|
|
br.BaseStream.Seek(sectorSkip, SeekOrigin.Current);
|
|
Array.Copy(sector, 0, buffer, i * sectorSize, sectorSize);
|
|
}
|
|
}
|
|
|
|
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(aaruTrack => aaruTrack.Session == session).ToList();
|
|
|
|
#endregion
|
|
} |