// /***************************************************************************
// 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-2026 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 => string.Equals(Path.GetExtension(f.FilePath),
                                                            ".b00",
                                                            StringComparison.InvariantCultureIgnoreCase)))
                {
                    DataFileCharacteristics splitStartChars =
                        _filePaths.FirstOrDefault(static f => string.Equals(Path.GetExtension(f.FilePath),
                                                                            ".b00",
                                                                            StringComparison
                                                                               .InvariantCultureIgnoreCase));

                    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
}