// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Read.cs // Author(s) : Natalia Portillo // // 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 . // // ---------------------------------------------------------------------------- // 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 /// 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
(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(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(); var isDvd = false; byte firstSession = byte.MaxValue; byte lastSession = 0; _trackFlags = new Dictionary(); _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; } /// 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; } } /// 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 _); } /// public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) => ReadSectorsTag(sectorAddress, false, 1, tag, out buffer); /// public ErrorNumber ReadSector(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus) { sectorStatus = SectorStatus.Dumped; return ReadSectors(sectorAddress, 1, track, out buffer, out _); } /// public ErrorNumber ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag, out byte[] buffer) => ReadSectorsTag(sectorAddress, 1, track, tag, out buffer); /// 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 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; } /// public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag, out byte[] buffer) { buffer = null; if(negative) return ErrorNumber.NotSupported; foreach(KeyValuePair 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; } /// 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; } /// 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; } /// public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) { sectorStatus = SectorStatus.Dumped; return ReadSectorsLong(sectorAddress, false, 1, out buffer, out _); } /// public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus) { sectorStatus = SectorStatus.Dumped; return ReadSectorsLong(sectorAddress, 1, track, out buffer, out _); } /// public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer, out SectorStatus[] sectorStatus) { buffer = null; sectorStatus = null; foreach(KeyValuePair 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; } /// 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; } /// public List GetSessionTracks(Session session) => Sessions.Contains(session) ? GetSessionTracks(session.Sequence) : null; /// public List GetSessionTracks(ushort session) => Tracks.Where(aaruTrack => aaruTrack.Session == session).ToList(); #endregion }