// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename       : Nero.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Disc image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
//     Manages Nero Burning ROM 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-2017 Natalia Portillo
// ****************************************************************************/

using System;
using System.IO;
using System.Collections.Generic;
using DiscImageChef.Console;
using DiscImageChef.CommonTypes;
using DiscImageChef.Filters;
using System.Linq;

namespace DiscImageChef.ImagePlugins
{
    public class Nero : ImagePlugin
    {
        #region Internal structures

        struct NeroV1Footer
        {
            /// <summary>
            /// "NERO"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Offset of first chunk in file
            /// </summary>
            public uint FirstChunkOffset;
        }

        struct NeroV2Footer
        {
            /// <summary>
            /// "NER5"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Offset of first chunk in file
            /// </summary>
            public ulong FirstChunkOffset;
        }

        struct NeroV2CueEntry
        {
            /// <summary>
            /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data
            /// </summary>
            public byte Mode;

            /// <summary>
            /// Track number in BCD
            /// </summary>
            public byte TrackNumber;

            /// <summary>
            /// Index number in BCD
            /// </summary>
            public byte IndexNumber;

            /// <summary>
            /// Always zero
            /// </summary>
            public byte Dummy;

            /// <summary>
            /// LBA sector start for this entry
            /// </summary>
            public int LBAStart;
        }

        struct NeroV2Cuesheet
        {
            /// <summary>
            /// "CUEX"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Cuesheet entries
            /// </summary>
            public List<NeroV2CueEntry> Entries;
        }

        struct NeroV1CueEntry
        {
            /// <summary>
            /// Track mode. 0x01 for audio, 0x21 for copy-protected audio, 0x41 for data
            /// </summary>
            public byte Mode;

            /// <summary>
            /// Track number in BCD
            /// </summary>
            public byte TrackNumber;

            /// <summary>
            /// Index number in BCD
            /// </summary>
            public byte IndexNumber;

            /// <summary>
            /// Always zero
            /// </summary>
            public ushort Dummy;

            /// <summary>
            /// MSF start sector's minute for this entry
            /// </summary>
            public byte Minute;

            /// <summary>
            /// MSF start sector's second for this entry
            /// </summary>
            public byte Second;

            /// <summary>
            /// MSF start sector's frame for this entry
            /// </summary>
            public byte Frame;
        }

        struct NeroV1Cuesheet
        {
            /// <summary>
            /// "CUES"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Cuesheet entries
            /// </summary>
            public List<NeroV1CueEntry> Entries;
        }

        struct NeroV1DAOEntry
        {
            /// <summary>
            /// ISRC (12 bytes)
            /// </summary>
            public byte[] ISRC;

            /// <summary>
            /// Size of sector inside image (in bytes)
            /// </summary>
            public ushort SectorSize;

            /// <summary>
            /// Sector mode in image
            /// </summary>
            public ushort Mode;

            /// <summary>
            /// Unknown
            /// </summary>
            public ushort Unknown;

            /// <summary>
            /// Index 0 start
            /// </summary>
            public uint Index0;

            /// <summary>
            /// Index 1 start
            /// </summary>
            public uint Index1;

            /// <summary>
            /// End of track + 1
            /// </summary>
            public uint EndOfTrack;
        }

        struct NeroV1DAO
        {
            /// <summary>
            /// "DAOI"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size (big endian)
            /// </summary>
            public uint ChunkSizeBe;

            /// <summary>
            /// Chunk size (little endian)
            /// </summary>
            public uint ChunkSizeLe;

            /// <summary>
            /// UPC (14 bytes, null-padded)
            /// </summary>
            public byte[] UPC;

            /// <summary>
            /// TOC type
            /// </summary>
            public ushort TocType;

            /// <summary>
            /// First track
            /// </summary>
            public byte FirstTrack;

            /// <summary>
            /// Last track
            /// </summary>
            public byte LastTrack;

            /// <summary>
            /// Tracks
            /// </summary>
            public List<NeroV1DAOEntry> Tracks;
        }

        struct NeroV2DAOEntry
        {
            /// <summary>
            /// ISRC (12 bytes)
            /// </summary>
            public byte[] ISRC;

            /// <summary>
            /// Size of sector inside image (in bytes)
            /// </summary>
            public ushort SectorSize;

            /// <summary>
            /// Sector mode in image
            /// </summary>
            public ushort Mode;

            /// <summary>
            /// Seems to be always 0.
            /// </summary>
            public ushort Unknown;

            /// <summary>
            /// Index 0 start
            /// </summary>
            public ulong Index0;

            /// <summary>
            /// Index 1 start
            /// </summary>
            public ulong Index1;

            /// <summary>
            /// End of track + 1
            /// </summary>
            public ulong EndOfTrack;
        }

        struct NeroV2DAO
        {
            /// <summary>
            /// "DAOX"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size (big endian)
            /// </summary>
            public uint ChunkSizeBe;

            /// <summary>
            /// Chunk size (little endian)
            /// </summary>
            public uint ChunkSizeLe;

            /// <summary>
            /// UPC (14 bytes, null-padded)
            /// </summary>
            public byte[] UPC;

            /// <summary>
            /// TOC type
            /// </summary>
            public ushort TocType;

            /// <summary>
            /// First track
            /// </summary>
            public byte FirstTrack;

            /// <summary>
            /// Last track
            /// </summary>
            public byte LastTrack;

            /// <summary>
            /// Tracks
            /// </summary>
            public List<NeroV2DAOEntry> Tracks;
        }

        struct NeroCDTextPack
        {
            /// <summary>
            /// Pack type
            /// </summary>
            public byte PackType;

            /// <summary>
            /// Track number
            /// </summary>
            public byte TrackNumber;

            /// <summary>
            /// Pack number in block
            /// </summary>
            public byte PackNumber;

            /// <summary>
            /// Block number
            /// </summary>
            public byte BlockNumber;

            /// <summary>
            /// 12 bytes of data
            /// </summary>
            public byte[] Text;

            /// <summary>
            /// CRC
            /// </summary>
            public ushort CRC;
        }

        struct NeroCDText
        {
            /// <summary>
            /// "CDTX"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// CD-TEXT packs
            /// </summary>
            public List<NeroCDTextPack> Packs;
        }

        struct NeroV1TAOEntry
        {
            /// <summary>
            /// Offset of track on image
            /// </summary>
            public uint Offset;

            /// <summary>
            /// Length of track in bytes
            /// </summary>
            public uint Length;

            /// <summary>
            /// Track mode
            /// </summary>
            public uint Mode;

            /// <summary>
            /// LBA track start (plus 150 lead in sectors)
            /// </summary>
            public uint StartLBA;

            /// <summary>
            /// Unknown
            /// </summary>
            public uint Unknown;
        }

        struct NeroV1TAO
        {
            /// <summary>
            /// "ETNF"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// CD-TEXT packs
            /// </summary>
            public List<NeroV1TAOEntry> Tracks;
        }

        struct NeroV2TAOEntry
        {
            /// <summary>
            /// Offset of track on image
            /// </summary>
            public ulong Offset;

            /// <summary>
            /// Length of track in bytes
            /// </summary>
            public ulong Length;

            /// <summary>
            /// Track mode
            /// </summary>
            public uint Mode;

            /// <summary>
            /// LBA track start (plus 150 lead in sectors)
            /// </summary>
            public uint StartLBA;

            /// <summary>
            /// Unknown
            /// </summary>
            public uint Unknown;

            /// <summary>
            /// Track length in sectors
            /// </summary>
            public uint Sectors;
        }

        struct NeroV2TAO
        {
            /// <summary>
            /// "ETN2"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// CD-TEXT packs
            /// </summary>
            public List<NeroV2TAOEntry> Tracks;
        }

        struct NeroSession
        {
            /// <summary>
            /// "SINF"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Tracks in session
            /// </summary>
            public uint Tracks;
        }

        struct NeroMediaType
        {
            /// <summary>
            /// "MTYP"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Media type
            /// </summary>
            public uint Type;
        }

        struct NeroDiscInformation
        {
            /// <summary>
            /// "DINF"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Unknown
            /// </summary>
            public uint Unknown;
        }

        struct NeroTOCChunk
        {
            /// <summary>
            /// "TOCT"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Unknown
            /// </summary>
            public ushort Unknown;
        }

        struct NeroRELOChunk
        {
            /// <summary>
            /// "RELO"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;

            /// <summary>
            /// Unknown
            /// </summary>
            public uint Unknown;
        }

        struct NeroEndOfChunkChain
        {
            /// <summary>
            /// "END!"
            /// </summary>
            public uint ChunkID;

            /// <summary>
            /// Chunk size
            /// </summary>
            public uint ChunkSize;
        }

        // Internal use only
        struct NeroTrack
        {
            public byte[] ISRC;
            public ushort SectorSize;
            public ulong Offset;
            public ulong Length;
            public ulong EndOfTrack;
            public uint Mode;
            public ulong StartLBA;
            public ulong Sectors;
            public ulong Index0;
            public ulong Index1;
            public uint Sequence;
        }

        #endregion

        #region Internal consts

        // "NERO"
        public const uint NeroV1FooterID = 0x4E45524F;

        // "NER5"
        public const uint NeroV2FooterID = 0x4E455235;

        // "CUES"
        public const uint NeroV1CUEID = 0x43554553;

        // "CUEX"
        public const uint NeroV2CUEID = 0x43554558;

        // "ETNF"
        public const uint NeroV1TAOID = 0x45544E46;

        // "ETN2"
        public const uint NeroV2TAOID = 0x45544E32;

        // "DAOI"
        public const uint NeroV1DAOID = 0x44414F49;

        // "DAOX"
        public const uint NeroV2DAOID = 0x44414F58;

        // "CDTX"
        public const uint NeroCDTextID = 0x43445458;

        // "SINF"
        public const uint NeroSessionID = 0x53494E46;

        // "MTYP"
        public const uint NeroDiskTypeID = 0x4D545950;

        // "DINF"
        public const uint NeroDiscInfoID = 0x44494E46;

        // "TOCT"
        public const uint NeroTOCID = 0x544F4354;

        // "RELO"
        public const uint NeroReloID = 0x52454C4F;

        // "END!"
        public const uint NeroEndID = 0x454E4421;

        public enum DAOMode : ushort
        {
            Data = 0x0000,
            DataM2F1 = 0x0002,
            DataM2F2 = 0x0003,
            DataRaw = 0x0005,
            DataM2Raw = 0x0006,
            Audio = 0x0007,
            DataRawSub = 0x000F,
            AudioSub = 0x0010,
            DataM2RawSub = 0x0011
        }

        [Flags]
        public enum NeroMediaTypes : uint
        {
            /// <summary>
            /// No media
            /// </summary>
            NERO_MTYP_NONE = 0x00000,
            /// <summary>
            /// CD-R/RW
            /// </summary>
            NERO_MTYP_CD = 0x00001,
            /// <summary>
            /// DDCD-R/RW
            /// </summary>
            NERO_MTYP_DDCD = 0x00002,
            /// <summary>
            /// DVD-R/RW
            /// </summary>
            NERO_MTYP_DVD_M = 0x00004,
            /// <summary>
            /// DVD+RW
            /// </summary>
            NERO_MTYP_DVD_P = 0x00008,
            /// <summary>
            /// DVD-RAM
            /// </summary>
            NERO_MTYP_DVD_RAM = 0x00010,
            /// <summary>
            /// Multi-level disc
            /// </summary>
            NERO_MTYP_ML = 0x00020,
            /// <summary>
            /// Mount Rainier
            /// </summary>
            NERO_MTYP_MRW = 0x00040,
            /// <summary>
            /// Exclude CD-R
            /// </summary>
            NERO_MTYP_NO_CDR = 0x00080,
            /// <summary>
            /// Exclude CD-RW
            /// </summary>
            NERO_MTYP_NO_CDRW = 0x00100,
            /// <summary>
            /// CD-RW
            /// </summary>
            NERO_MTYP_CDRW = NERO_MTYP_CD | NERO_MTYP_NO_CDR,
            /// <summary>
            /// CD-R
            /// </summary>
            NERO_MTYP_CDR = NERO_MTYP_CD | NERO_MTYP_NO_CDRW,
            /// <summary>
            /// DVD-ROM
            /// </summary>
            NERO_MTYP_DVD_ROM = 0x00200,
            /// <summary>
            /// CD-ROM
            /// </summary>
            NERO_MTYP_CDROM = 0x00400,
            /// <summary>
            /// Exclude DVD-RW
            /// </summary>
            NERO_MTYP_NO_DVD_M_RW = 0x00800,
            /// <summary>
            /// Exclude DVD-R
            /// </summary>
            NERO_MTYP_NO_DVD_M_R = 0x01000,
            /// <summary>
            /// Exclude DVD+RW
            /// </summary>
            NERO_MTYP_NO_DVD_P_RW = 0x02000,
            /// <summary>
            /// Exclude DVD+R
            /// </summary>
            NERO_MTYP_NO_DVD_P_R = 0x04000,
            /// <summary>
            /// DVD-R
            /// </summary>
            NERO_MTYP_DVD_M_R = NERO_MTYP_DVD_M | NERO_MTYP_NO_DVD_M_RW,
            /// <summary>
            /// DVD-RW
            /// </summary>
            NERO_MTYP_DVD_M_RW = NERO_MTYP_DVD_M | NERO_MTYP_NO_DVD_M_R,
            /// <summary>
            /// DVD+R
            /// </summary>
            NERO_MTYP_DVD_P_R = NERO_MTYP_DVD_P | NERO_MTYP_NO_DVD_P_RW,
            /// <summary>
            /// DVD+RW
            /// </summary>
            NERO_MTYP_DVD_P_RW = NERO_MTYP_DVD_P | NERO_MTYP_NO_DVD_P_R,
            /// <summary>
            /// Packet-writing (fixed)
            /// </summary>
            NERO_MTYP_FPACKET = 0x08000,
            /// <summary>
            /// Packet-writing (variable)
            /// </summary>
            NERO_MTYP_VPACKET = 0x10000,
            /// <summary>
            /// Packet-writing (any)
            /// </summary>
            NERO_MTYP_PACKETW = NERO_MTYP_MRW | NERO_MTYP_FPACKET | NERO_MTYP_VPACKET,
            /// <summary>
            /// HD-Burn
            /// </summary>
            NERO_MTYP_HDB = 0x20000,
            /// <summary>
            /// DVD+R DL
            /// </summary>
            NERO_MTYP_DVD_P_R9 = 0x40000,
            /// <summary>
            /// DVD-R DL
            /// </summary>
            NERO_MTYP_DVD_M_R9 = 0x80000,
            /// <summary>
            /// Any DVD double-layer
            /// </summary>
            NERO_MTYP_DVD_ANY_R9 = NERO_MTYP_DVD_P_R9 | NERO_MTYP_DVD_M_R9,
            /// <summary>
            /// Any DVD
            /// </summary>
            NERO_MTYP_DVD_ANY = NERO_MTYP_DVD_M | NERO_MTYP_DVD_P | NERO_MTYP_DVD_RAM | NERO_MTYP_DVD_ANY_R9,
            /// <summary>
            /// BD-ROM
            /// </summary>
            NERO_MTYP_BD_ROM = 0x100000,
            /// <summary>
            /// BD-R
            /// </summary>
            NERO_MTYP_BD_R = 0x200000,
            /// <summary>
            /// BD-RE
            /// </summary>
            NERO_MTYP_BD_RE = 0x400000,
            /// <summary>
            /// BD-R/RE
            /// </summary>
            NERO_MTYP_BD = NERO_MTYP_BD_R | NERO_MTYP_BD_RE,
            /// <summary>
            /// Any BD
            /// </summary>
            NERO_MTYP_BD_ANY = NERO_MTYP_BD | NERO_MTYP_BD_ROM,
            /// <summary>
            /// HD DVD-ROM
            /// </summary>
            NERO_MTYP_HD_DVD_ROM = 0x0800000,
            /// <summary>
            /// HD DVD-R
            /// </summary>
            NERO_MTYP_HD_DVD_R = 0x1000000,
            /// <summary>
            /// HD DVD-RW
            /// </summary>
            NERO_MTYP_HD_DVD_RW = 0x2000000,
            /// <summary>
            /// HD DVD-R/RW
            /// </summary>
            NERO_MTYP_HD_DVD = NERO_MTYP_HD_DVD_R | NERO_MTYP_HD_DVD_RW,
            /// <summary>
            /// Any HD DVD
            /// </summary>
            NERO_MTYP_HD_DVD_ANY = NERO_MTYP_HD_DVD | NERO_MTYP_HD_DVD_ROM,
            /// <summary>
            /// Any DVD, old
            /// </summary>
            NERO_MTYP_DVD_ANY_OLD = NERO_MTYP_DVD_M | NERO_MTYP_DVD_P | NERO_MTYP_DVD_RAM,
        }

        #endregion

        #region Internal variables

        Filter _imageFilter;
        Stream imageStream;
        bool imageNewFormat;
        Dictionary<ushort, uint> neroSessions;
        NeroV1Cuesheet neroCuesheetV1;
        NeroV2Cuesheet neroCuesheetV2;
        NeroV1DAO neroDAOV1;
        NeroV2DAO neroDAOV2;
        NeroCDText neroCDTXT;
        NeroV1TAO neroTAOV1;
        NeroV2TAO neroTAOV2;
        NeroMediaType neroMediaTyp;
        NeroDiscInformation neroDiscInfo;
        NeroTOCChunk neroTOC;
        NeroRELOChunk neroRELO;

        List<Track> imageTracks;
        Dictionary<uint, byte[]> TrackISRCs;
        byte[] UPC;
        Dictionary<uint, NeroTrack> neroTracks;
        Dictionary<uint, ulong> offsetmap;
        List<Session> imageSessions;
        List<Partition> ImagePartitions;

        #endregion

        #region Methods

        public Nero()
        {
            Name = "Nero Burning ROM image";
            PluginUUID = new Guid("D160F9FF-5941-43FC-B037-AD81DD141F05");
            imageNewFormat = false;
            ImageInfo = new ImageInfo();
            ImageInfo.readableSectorTags = new List<SectorTagType>();
            ImageInfo.readableMediaTags = new List<MediaTagType>();
            neroSessions = new Dictionary<ushort, uint>();
            neroTracks = new Dictionary<uint, NeroTrack>();
            offsetmap = new Dictionary<uint, ulong>();
            imageSessions = new List<Session>();
            ImagePartitions = new List<Partition>();
        }

        // Due to .cue format, this method must parse whole file, ignoring errors (those will be thrown by OpenImage()).
        public override bool IdentifyImage(Filter imageFilter)
        {
            imageStream = imageFilter.GetDataForkStream();
            BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian;

            byte[] buffer;
            NeroV1Footer footerV1 = new NeroV1Footer();
            NeroV2Footer footerV2 = new NeroV2Footer();

            imageStream.Seek(-8, SeekOrigin.End);
            buffer = new byte[8];
            imageStream.Read(buffer, 0, 8);
            footerV1.ChunkID = BigEndianBitConverter.ToUInt32(buffer, 0);
            footerV1.FirstChunkOffset = BigEndianBitConverter.ToUInt32(buffer, 4);

            imageStream.Seek(-12, SeekOrigin.End);
            buffer = new byte[12];
            imageStream.Read(buffer, 0, 12);
            footerV2.ChunkID = BigEndianBitConverter.ToUInt32(buffer, 0);
            footerV2.FirstChunkOffset = BigEndianBitConverter.ToUInt64(buffer, 4);

            DicConsole.DebugWriteLine("Nero plugin", "imageStream.Length = {0}", imageStream.Length);
            DicConsole.DebugWriteLine("Nero plugin", "footerV1.ChunkID = 0x{0:X8}", footerV1.ChunkID);
            DicConsole.DebugWriteLine("Nero plugin", "footerV1.FirstChunkOffset = {0}", footerV1.FirstChunkOffset);
            DicConsole.DebugWriteLine("Nero plugin", "footerV2.ChunkID = 0x{0:X8}", footerV2.ChunkID);
            DicConsole.DebugWriteLine("Nero plugin", "footerV2.FirstChunkOffset = {0}", footerV2.FirstChunkOffset);

            if(footerV2.ChunkID == NeroV2FooterID && footerV2.FirstChunkOffset < (ulong)imageStream.Length)
            {
                return true;
            }
            if(footerV1.ChunkID == NeroV1FooterID && footerV1.FirstChunkOffset < (ulong)imageStream.Length)
            {
                return true;
            }

            return false;
        }

        public override bool OpenImage(Filter imageFilter)
        {
            try
            {
                imageStream = imageFilter.GetDataForkStream();
                BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian;

                byte[] buffer;
                NeroV1Footer footerV1 = new NeroV1Footer();
                NeroV2Footer footerV2 = new NeroV2Footer();

                imageStream.Seek(-8, SeekOrigin.End);
                buffer = new byte[8];
                imageStream.Read(buffer, 0, 8);
                footerV1.ChunkID = BigEndianBitConverter.ToUInt32(buffer, 0);
                footerV1.FirstChunkOffset = BigEndianBitConverter.ToUInt32(buffer, 4);

                imageStream.Seek(-12, SeekOrigin.End);
                buffer = new byte[12];
                imageStream.Read(buffer, 0, 12);
                footerV2.ChunkID = BigEndianBitConverter.ToUInt32(buffer, 0);
                footerV2.FirstChunkOffset = BigEndianBitConverter.ToUInt64(buffer, 4);

                DicConsole.DebugWriteLine("Nero plugin", "imageStream.Length = {0}", imageStream.Length);
                DicConsole.DebugWriteLine("Nero plugin", "footerV1.ChunkID = 0x{0:X8} (\"{1}\")", footerV1.ChunkID, System.Text.Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(footerV1.ChunkID)));
                DicConsole.DebugWriteLine("Nero plugin", "footerV1.FirstChunkOffset = {0}", footerV1.FirstChunkOffset);
                DicConsole.DebugWriteLine("Nero plugin", "footerV2.ChunkID = 0x{0:X8} (\"{1}\")", footerV2.ChunkID, System.Text.Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(footerV2.ChunkID)));
                DicConsole.DebugWriteLine("Nero plugin", "footerV2.FirstChunkOffset = {0}", footerV2.FirstChunkOffset);

				if(footerV1.ChunkID == NeroV1FooterID && footerV1.FirstChunkOffset < (ulong)imageStream.Length)
					imageNewFormat = false;
				else if(footerV2.ChunkID == NeroV2FooterID && footerV2.FirstChunkOffset < (ulong)imageStream.Length)
					imageNewFormat = true;
				else
				{
					DicConsole.DebugWrite("Nero plugin", "Nero version not recognized.");
					return false;
				}

                if(imageNewFormat)
                    imageStream.Seek((long)footerV2.FirstChunkOffset, SeekOrigin.Begin);
                else
                    imageStream.Seek(footerV1.FirstChunkOffset, SeekOrigin.Begin);

                bool parsing = true;
                ushort currentsession = 1;
                uint currenttrack = 1;

                imageTracks = new List<Track>();
                TrackISRCs = new Dictionary<uint, byte[]>();

                ImageInfo.mediaType = MediaType.CD;
                ImageInfo.sectors = 0;
                ImageInfo.sectorSize = 0;

                while(parsing)
                {
                    byte[] ChunkHeaderBuffer = new byte[8];
                    uint ChunkID;
                    uint ChunkLength;

                    imageStream.Read(ChunkHeaderBuffer, 0, 8);
                    ChunkID = BigEndianBitConverter.ToUInt32(ChunkHeaderBuffer, 0);
                    ChunkLength = BigEndianBitConverter.ToUInt32(ChunkHeaderBuffer, 4);

                    DicConsole.DebugWriteLine("Nero plugin", "ChunkID = 0x{0:X8} (\"{1}\")", ChunkID, System.Text.Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(ChunkID)));
                    DicConsole.DebugWriteLine("Nero plugin", "ChunkLength = {0}", ChunkLength);

                    switch(ChunkID)
                    {
                        case NeroV1CUEID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"CUES\" chunk, parsing {0} bytes", ChunkLength);

                                neroCuesheetV1 = new NeroV1Cuesheet();
                                neroCuesheetV1.ChunkID = ChunkID;
                                neroCuesheetV1.ChunkSize = ChunkLength;
                                neroCuesheetV1.Entries = new List<NeroV1CueEntry>();

                                byte[] tmpbuffer = new byte[8];
                                for(int i = 0; i < neroCuesheetV1.ChunkSize; i += 8)
                                {
                                    NeroV1CueEntry _entry = new NeroV1CueEntry();
                                    imageStream.Read(tmpbuffer, 0, 8);
                                    _entry.Mode = tmpbuffer[0];
                                    _entry.TrackNumber = tmpbuffer[1];
                                    _entry.IndexNumber = tmpbuffer[2];
                                    _entry.Dummy = BigEndianBitConverter.ToUInt16(tmpbuffer, 3);
                                    _entry.Minute = tmpbuffer[5];
                                    _entry.Second = tmpbuffer[6];
                                    _entry.Frame = tmpbuffer[7];

                                    DicConsole.DebugWriteLine("Nero plugin", "Cuesheet entry {0}", (i / 8) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = {1:X2}", (i / 8) + 1, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].TrackNumber = {1:X2}", (i / 8) + 1, _entry.TrackNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].IndexNumber = {1:X2}", (i / 8) + 1, _entry.IndexNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Dummy = {1:X4}", (i / 8) + 1, _entry.Dummy);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Minute = {1:X2}", (i / 8) + 1, _entry.Minute);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Second = {1:X2}", (i / 8) + 1, _entry.Second);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Frame = {1:X2}", (i / 8) + 1, _entry.Frame);

                                    neroCuesheetV1.Entries.Add(_entry);
                                }

                                break;
                            }
                        case NeroV2CUEID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"CUEX\" chunk, parsing {0} bytes", ChunkLength);

                                neroCuesheetV2 = new NeroV2Cuesheet();
                                neroCuesheetV2.ChunkID = ChunkID;
                                neroCuesheetV2.ChunkSize = ChunkLength;
                                neroCuesheetV2.Entries = new List<NeroV2CueEntry>();

                                byte[] tmpbuffer = new byte[8];
                                for(int i = 0; i < neroCuesheetV2.ChunkSize; i += 8)
                                {
                                    NeroV2CueEntry _entry = new NeroV2CueEntry();
                                    imageStream.Read(tmpbuffer, 0, 8);
                                    _entry.Mode = tmpbuffer[0];
                                    _entry.TrackNumber = tmpbuffer[1];
                                    _entry.IndexNumber = tmpbuffer[2];
                                    _entry.Dummy = tmpbuffer[3];
                                    _entry.LBAStart = BigEndianBitConverter.ToInt32(tmpbuffer, 4);

                                    DicConsole.DebugWriteLine("Nero plugin", "Cuesheet entry {0}", (i / 8) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = 0x{1:X2}", (i / 8) + 1, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].TrackNumber = {1:X2}", (i / 8) + 1, _entry.TrackNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].IndexNumber = {1:X2}", (i / 8) + 1, _entry.IndexNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Dummy = {1:X2}", (i / 8) + 1, _entry.Dummy);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].LBAStart = {1}", (i / 8) + 1, _entry.LBAStart);

                                    neroCuesheetV2.Entries.Add(_entry);
                                }

                                break;
                            }
                        case NeroV1DAOID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"DAOI\" chunk, parsing {0} bytes", ChunkLength);

                                neroDAOV1 = new NeroV1DAO();
                                neroDAOV1.ChunkID = ChunkID;
                                neroDAOV1.ChunkSizeBe = ChunkLength;

                                byte[] tmpbuffer = new byte[22];
                                imageStream.Read(tmpbuffer, 0, 22);
                                neroDAOV1.ChunkSizeLe = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);
                                neroDAOV1.UPC = new byte[14];
                                Array.Copy(tmpbuffer, 4, neroDAOV1.UPC, 0, 14);
                                neroDAOV1.TocType = BigEndianBitConverter.ToUInt16(tmpbuffer, 18);
                                neroDAOV1.FirstTrack = tmpbuffer[20];
                                neroDAOV1.LastTrack = tmpbuffer[21];
                                neroDAOV1.Tracks = new List<NeroV1DAOEntry>();

                                if(!ImageInfo.readableMediaTags.Contains(MediaTagType.CD_MCN))
                                    ImageInfo.readableMediaTags.Add(MediaTagType.CD_MCN);

                                if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDTrackISRC))
                                    ImageInfo.readableSectorTags.Add(SectorTagType.CDTrackISRC);

                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV1.ChunkSizeLe = {0} bytes", neroDAOV1.ChunkSizeLe);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV1.UPC = \"{0}\"", StringHandlers.CToString(neroDAOV1.UPC));
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV1.TocType = 0x{0:X4}", neroDAOV1.TocType);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV1.FirstTrack = {0}", neroDAOV1.FirstTrack);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV1.LastTrack = {0}", neroDAOV1.LastTrack);

                                UPC = neroDAOV1.UPC;

                                tmpbuffer = new byte[30];
                                for(int i = 0; i < (neroDAOV1.ChunkSizeBe - 22); i += 30)
                                {
                                    NeroV1DAOEntry _entry = new NeroV1DAOEntry();
                                    imageStream.Read(tmpbuffer, 0, 30);
                                    _entry.ISRC = new byte[12];
                                    Array.Copy(tmpbuffer, 4, _entry.ISRC, 0, 12);
                                    _entry.SectorSize = BigEndianBitConverter.ToUInt16(tmpbuffer, 12);
                                    _entry.Mode = BitConverter.ToUInt16(tmpbuffer, 14);
                                    _entry.Unknown = BigEndianBitConverter.ToUInt16(tmpbuffer, 16);
                                    _entry.Index0 = BigEndianBitConverter.ToUInt32(tmpbuffer, 18);
                                    _entry.Index1 = BigEndianBitConverter.ToUInt32(tmpbuffer, 22);
                                    _entry.EndOfTrack = BigEndianBitConverter.ToUInt32(tmpbuffer, 26);

                                    DicConsole.DebugWriteLine("Nero plugin", "Disc-At-Once entry {0}", (i / 32) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].ISRC = \"{1}\"", (i / 32) + 1, StringHandlers.CToString(_entry.ISRC));
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].SectorSize = {1}", (i / 32) + 1, _entry.SectorSize);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = {1} (0x{2:X4})", (i / 32) + 1, (DAOMode)_entry.Mode, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Unknown = 0x{1:X4}", (i / 32) + 1, _entry.Unknown);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Index0 = {1}", (i / 32) + 1, _entry.Index0);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Index1 = {1}", (i / 32) + 1, _entry.Index1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].EndOfTrack = {1}", (i / 32) + 1, _entry.EndOfTrack);

                                    neroDAOV1.Tracks.Add(_entry);

                                    if(_entry.SectorSize > ImageInfo.sectorSize)
                                        ImageInfo.sectorSize = _entry.SectorSize;

                                    TrackISRCs.Add(currenttrack, _entry.ISRC);
                                    if(currenttrack == 1)
                                        _entry.Index0 = _entry.Index1;

                                    NeroTrack _neroTrack = new NeroTrack();
                                    _neroTrack.EndOfTrack = _entry.EndOfTrack;
                                    _neroTrack.ISRC = _entry.ISRC;
                                    _neroTrack.Length = _entry.EndOfTrack - _entry.Index0;
                                    _neroTrack.Mode = _entry.Mode;
                                    _neroTrack.Offset = _entry.Index0;
                                    _neroTrack.Sectors = _neroTrack.Length / _entry.SectorSize;
                                    _neroTrack.SectorSize = _entry.SectorSize;
                                    _neroTrack.StartLBA = ImageInfo.sectors;
                                    _neroTrack.Index0 = _entry.Index0;
                                    _neroTrack.Index1 = _entry.Index1;
                                    _neroTrack.Sequence = currenttrack;
                                    neroTracks.Add(currenttrack, _neroTrack);

                                    ImageInfo.sectors += _neroTrack.Sectors;

                                    currenttrack++;
                                }

                                break;
                            }
                        case NeroV2DAOID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"DAOX\" chunk, parsing {0} bytes", ChunkLength);

                                neroDAOV2 = new NeroV2DAO();
                                neroDAOV2.ChunkID = ChunkID;
                                neroDAOV2.ChunkSizeBe = ChunkLength;

                                byte[] tmpbuffer = new byte[22];
                                imageStream.Read(tmpbuffer, 0, 22);
                                neroDAOV2.ChunkSizeLe = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);
                                neroDAOV2.UPC = new byte[14];
                                Array.Copy(tmpbuffer, 4, neroDAOV2.UPC, 0, 14);
                                neroDAOV2.TocType = BigEndianBitConverter.ToUInt16(tmpbuffer, 18);
                                neroDAOV1.FirstTrack = tmpbuffer[20];
                                neroDAOV2.LastTrack = tmpbuffer[21];
                                neroDAOV2.Tracks = new List<NeroV2DAOEntry>();

                                if(!ImageInfo.readableMediaTags.Contains(MediaTagType.CD_MCN))
                                    ImageInfo.readableMediaTags.Add(MediaTagType.CD_MCN);

                                if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDTrackISRC))
                                    ImageInfo.readableSectorTags.Add(SectorTagType.CDTrackISRC);

                                UPC = neroDAOV2.UPC;

                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV2.ChunkSizeLe = {0} bytes", neroDAOV2.ChunkSizeLe);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV2.UPC = \"{0}\"", StringHandlers.CToString(neroDAOV2.UPC));
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV2.TocType = 0x{0:X4}", neroDAOV2.TocType);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV2.FirstTrack = {0}", neroDAOV2.FirstTrack);
                                DicConsole.DebugWriteLine("Nero plugin", "neroDAOV2.LastTrack = {0}", neroDAOV2.LastTrack);

                                tmpbuffer = new byte[42];
                                for(int i = 0; i < (neroDAOV2.ChunkSizeBe - 22); i += 42)
                                {
                                    NeroV2DAOEntry _entry = new NeroV2DAOEntry();
                                    imageStream.Read(tmpbuffer, 0, 42);
                                    _entry.ISRC = new byte[12];
                                    Array.Copy(tmpbuffer, 4, _entry.ISRC, 0, 12);
                                    _entry.SectorSize = BigEndianBitConverter.ToUInt16(tmpbuffer, 12);
                                    _entry.Mode = BitConverter.ToUInt16(tmpbuffer, 14);
                                    _entry.Unknown = BigEndianBitConverter.ToUInt16(tmpbuffer, 16);
                                    _entry.Index0 = BigEndianBitConverter.ToUInt64(tmpbuffer, 18);
                                    _entry.Index1 = BigEndianBitConverter.ToUInt64(tmpbuffer, 26);
                                    _entry.EndOfTrack = BigEndianBitConverter.ToUInt64(tmpbuffer, 34);

                                    DicConsole.DebugWriteLine("Nero plugin", "Disc-At-Once entry {0}", (i / 32) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].ISRC = \"{1}\"", (i / 32) + 1, StringHandlers.CToString(_entry.ISRC));
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].SectorSize = {1}", (i / 32) + 1, _entry.SectorSize);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = {1} (0x{2:X4})", (i / 32) + 1, (DAOMode)_entry.Mode, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Unknown = {1:X2}", (i / 32) + 1, _entry.Unknown);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Index0 = {1}", (i / 32) + 1, _entry.Index0);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Index1 = {1}", (i / 32) + 1, _entry.Index1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].EndOfTrack = {1}", (i / 32) + 1, _entry.EndOfTrack);

                                    neroDAOV2.Tracks.Add(_entry);

                                    if(_entry.SectorSize > ImageInfo.sectorSize)
                                        ImageInfo.sectorSize = _entry.SectorSize;

                                    TrackISRCs.Add(currenttrack, _entry.ISRC);

                                    if(currenttrack == 1)
                                        _entry.Index0 = _entry.Index1;

                                    NeroTrack _neroTrack = new NeroTrack();
                                    _neroTrack.EndOfTrack = _entry.EndOfTrack;
                                    _neroTrack.ISRC = _entry.ISRC;
                                    _neroTrack.Length = _entry.EndOfTrack - _entry.Index0;
                                    _neroTrack.Mode = _entry.Mode;
                                    _neroTrack.Offset = _entry.Index0;
                                    _neroTrack.Sectors = _neroTrack.Length / _entry.SectorSize;
                                    _neroTrack.SectorSize = _entry.SectorSize;
                                    _neroTrack.StartLBA = ImageInfo.sectors;
                                    _neroTrack.Index0 = _entry.Index0;
                                    _neroTrack.Index1 = _entry.Index1;
                                    _neroTrack.Sequence = currenttrack;
                                    neroTracks.Add(currenttrack, _neroTrack);

                                    ImageInfo.sectors += _neroTrack.Sectors;

                                    currenttrack++;
                                }

                                break;
                            }
                        case NeroCDTextID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"CDTX\" chunk, parsing {0} bytes", ChunkLength);

                                neroCDTXT = new NeroCDText();
                                neroCDTXT.ChunkID = ChunkID;
                                neroCDTXT.ChunkSize = ChunkLength;
                                neroCDTXT.Packs = new List<NeroCDTextPack>();

                                byte[] tmpbuffer = new byte[18];
                                for(int i = 0; i < (neroCDTXT.ChunkSize); i += 18)
                                {
                                    NeroCDTextPack _entry = new NeroCDTextPack();
                                    imageStream.Read(tmpbuffer, 0, 18);

                                    _entry.PackType = tmpbuffer[0];
                                    _entry.TrackNumber = tmpbuffer[1];
                                    _entry.PackNumber = tmpbuffer[2];
                                    _entry.BlockNumber = tmpbuffer[3];
                                    _entry.Text = new byte[12];
                                    Array.Copy(tmpbuffer, 4, _entry.Text, 0, 12);
                                    _entry.CRC = BigEndianBitConverter.ToUInt16(tmpbuffer, 16);

                                    DicConsole.DebugWriteLine("Nero plugin", "CD-TEXT entry {0}", (i / 18) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].PackType = 0x{1:X2}", (i / 18) + 1, _entry.PackType);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].TrackNumber = 0x{1:X2}", (i / 18) + 1, _entry.TrackNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].PackNumber = 0x{1:X2}", (i / 18) + 1, _entry.PackNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].BlockNumber = 0x{1:X2}", (i / 18) + 1, _entry.BlockNumber);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Text = \"{1}\"", (i / 18) + 1, StringHandlers.CToString(_entry.Text));
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].CRC = 0x{1:X4}", (i / 18) + 1, _entry.CRC);

                                    neroCDTXT.Packs.Add(_entry);
                                }

                                break;
                            }
                        case NeroV1TAOID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"ETNF\" chunk, parsing {0} bytes", ChunkLength);

                                neroTAOV1 = new NeroV1TAO();
                                neroTAOV1.ChunkID = ChunkID;
                                neroTAOV1.ChunkSize = ChunkLength;
                                neroTAOV1.Tracks = new List<NeroV1TAOEntry>();

                                byte[] tmpbuffer = new byte[20];
                                for(int i = 0; i < (neroTAOV1.ChunkSize); i += 20)
                                {
                                    NeroV1TAOEntry _entry = new NeroV1TAOEntry();
                                    imageStream.Read(tmpbuffer, 0, 20);

                                    _entry.Offset = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);
                                    _entry.Length = BigEndianBitConverter.ToUInt32(tmpbuffer, 4);
                                    _entry.Mode = BigEndianBitConverter.ToUInt32(tmpbuffer, 8);
                                    _entry.StartLBA = BigEndianBitConverter.ToUInt32(tmpbuffer, 12);
                                    _entry.Unknown = BigEndianBitConverter.ToUInt32(tmpbuffer, 16);

                                    DicConsole.DebugWriteLine("Nero plugin", "Track-at-Once entry {0}", (i / 20) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Offset = {1}", (i / 20) + 1, _entry.Offset);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Length = {1} bytes", (i / 20) + 1, _entry.Length);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = {1} (0x{2:X4})", (i / 20) + 1, (DAOMode)_entry.Mode, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].StartLBA = {1}", (i / 20) + 1, _entry.StartLBA);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Unknown = 0x{1:X4}", (i / 20) + 1, _entry.Unknown);

                                    neroTAOV1.Tracks.Add(_entry);

                                    if(NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode) > ImageInfo.sectorSize)
                                        ImageInfo.sectorSize = NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);

                                    NeroTrack _neroTrack = new NeroTrack();
                                    _neroTrack.EndOfTrack = _entry.Offset + _entry.Length;
                                    _neroTrack.ISRC = new byte[12];
                                    _neroTrack.Length = _entry.Length;
                                    _neroTrack.Mode = _entry.Mode;
                                    _neroTrack.Offset = _entry.Offset;
                                    _neroTrack.Sectors = _neroTrack.Length / NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);
                                    _neroTrack.SectorSize = NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);
                                    _neroTrack.StartLBA = ImageInfo.sectors;
                                    _neroTrack.Index0 = _entry.Offset;
                                    _neroTrack.Index1 = _entry.Offset;
                                    _neroTrack.Sequence = currenttrack;
                                    neroTracks.Add(currenttrack, _neroTrack);

                                    ImageInfo.sectors += _neroTrack.Sectors;

                                    currenttrack++;
                                }

                                break;
                            }
                        case NeroV2TAOID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"ETN2\" chunk, parsing {0} bytes", ChunkLength);

                                neroTAOV2 = new NeroV2TAO();
                                neroTAOV2.ChunkID = ChunkID;
                                neroTAOV2.ChunkSize = ChunkLength;
                                neroTAOV2.Tracks = new List<NeroV2TAOEntry>();

                                byte[] tmpbuffer = new byte[32];
                                for(int i = 0; i < (neroTAOV2.ChunkSize); i += 32)
                                {
                                    NeroV2TAOEntry _entry = new NeroV2TAOEntry();
                                    imageStream.Read(tmpbuffer, 0, 32);

                                    _entry.Offset = BigEndianBitConverter.ToUInt64(tmpbuffer, 0);
                                    _entry.Length = BigEndianBitConverter.ToUInt64(tmpbuffer, 8);
                                    _entry.Mode = BigEndianBitConverter.ToUInt32(tmpbuffer, 16);
                                    _entry.StartLBA = BigEndianBitConverter.ToUInt32(tmpbuffer, 20);
                                    _entry.Unknown = BigEndianBitConverter.ToUInt32(tmpbuffer, 24);
                                    _entry.Sectors = BigEndianBitConverter.ToUInt32(tmpbuffer, 28);

                                    DicConsole.DebugWriteLine("Nero plugin", "Track-at-Once entry {0}", (i / 32) + 1);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Offset = {1}", (i / 32) + 1, _entry.Offset);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Length = {1} bytes", (i / 32) + 1, _entry.Length);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Mode = {1} (0x{2:X4})", (i / 32) + 1, (DAOMode)_entry.Mode, _entry.Mode);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].StartLBA = {1}", (i / 32) + 1, _entry.StartLBA);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Unknown = 0x{1:X4}", (i / 32) + 1, _entry.Unknown);
                                    DicConsole.DebugWriteLine("Nero plugin", "\t _entry[{0}].Sectors = {1}", (i / 32) + 1, _entry.Sectors);

                                    neroTAOV2.Tracks.Add(_entry);

                                    if(NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode) > ImageInfo.sectorSize)
                                        ImageInfo.sectorSize = NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);

                                    NeroTrack _neroTrack = new NeroTrack();
                                    _neroTrack.EndOfTrack = _entry.Offset + _entry.Length;
                                    _neroTrack.ISRC = new byte[12];
                                    _neroTrack.Length = _entry.Length;
                                    _neroTrack.Mode = _entry.Mode;
                                    _neroTrack.Offset = _entry.Offset;
                                    _neroTrack.Sectors = _neroTrack.Length / NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);
                                    _neroTrack.SectorSize = NeroTrackModeToBytesPerSector((DAOMode)_entry.Mode);
                                    _neroTrack.StartLBA = ImageInfo.sectors;
                                    _neroTrack.Index0 = _entry.Offset;
                                    _neroTrack.Index1 = _entry.Offset;
                                    _neroTrack.Sequence = currenttrack;
                                    neroTracks.Add(currenttrack, _neroTrack);

                                    ImageInfo.sectors += _neroTrack.Sectors;

                                    currenttrack++;
                                }

                                break;
                            }
                        case NeroSessionID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"SINF\" chunk, parsing {0} bytes", ChunkLength);

                                uint sessionTracks;
                                byte[] tmpbuffer = new byte[4];
                                imageStream.Read(tmpbuffer, 0, 4);
                                sessionTracks = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);
                                neroSessions.Add(currentsession, sessionTracks);

                                DicConsole.DebugWriteLine("Nero plugin", "\tSession {0} has {1} tracks", currentsession, sessionTracks);

                                currentsession++;
                                break;
                            }
                        case NeroDiskTypeID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"MTYP\" chunk, parsing {0} bytes", ChunkLength);

                                neroMediaTyp = new NeroMediaType();

                                neroMediaTyp.ChunkID = ChunkID;
                                neroMediaTyp.ChunkSize = ChunkLength;

                                byte[] tmpbuffer = new byte[4];
                                imageStream.Read(tmpbuffer, 0, 4);
                                neroMediaTyp.Type = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);

                                DicConsole.DebugWriteLine("Nero plugin", "\tMedia type is {0} ({1})", (NeroMediaTypes)neroMediaTyp.Type, neroMediaTyp.Type);

                                ImageInfo.mediaType = NeroMediaTypeToMediaType((NeroMediaTypes)neroMediaTyp.Type);

                                break;
                            }
                        case NeroDiscInfoID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"DINF\" chunk, parsing {0} bytes", ChunkLength);

                                neroDiscInfo = new NeroDiscInformation();
                                neroDiscInfo.ChunkID = ChunkID;
                                neroDiscInfo.ChunkSize = ChunkLength;
                                byte[] tmpbuffer = new byte[4];
                                imageStream.Read(tmpbuffer, 0, 4);
                                neroDiscInfo.Unknown = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);

                                DicConsole.DebugWriteLine("Nero plugin", "\tneroDiscInfo.Unknown = 0x{0:X4} ({0})", neroDiscInfo.Unknown);

                                break;
                            }
                        case NeroReloID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"RELO\" chunk, parsing {0} bytes", ChunkLength);

                                neroRELO = new NeroRELOChunk();
                                neroRELO.ChunkID = ChunkID;
                                neroRELO.ChunkSize = ChunkLength;
                                byte[] tmpbuffer = new byte[4];
                                imageStream.Read(tmpbuffer, 0, 4);
                                neroRELO.Unknown = BigEndianBitConverter.ToUInt32(tmpbuffer, 0);

                                DicConsole.DebugWriteLine("Nero plugin", "\tneroRELO.Unknown = 0x{0:X4} ({0})", neroRELO.Unknown);

                                break;
                            }
                        case NeroTOCID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"TOCT\" chunk, parsing {0} bytes", ChunkLength);

                                neroTOC = new NeroTOCChunk();
                                neroTOC.ChunkID = ChunkID;
                                neroTOC.ChunkSize = ChunkLength;
                                byte[] tmpbuffer = new byte[2];
                                imageStream.Read(tmpbuffer, 0, 2);
                                neroTOC.Unknown = BigEndianBitConverter.ToUInt16(tmpbuffer, 0);

                                DicConsole.DebugWriteLine("Nero plugin", "\tneroTOC.Unknown = 0x{0:X4} ({0})", neroTOC.Unknown);

                                break;
                            }
                        case NeroEndID:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Found \"END!\" chunk, finishing parse");
                                parsing = false;
                                break;
                            }
                        default:
                            {
                                DicConsole.DebugWriteLine("Nero plugin", "Unknown chunk ID \"{0}\", skipping...", System.Text.Encoding.ASCII.GetString(BigEndianBitConverter.GetBytes(ChunkID)));
                                imageStream.Seek(ChunkLength, SeekOrigin.Current);
                                break;
                            }
                    }
                }

                ImageInfo.imageHasPartitions = true;
                ImageInfo.imageHasSessions = true;
                ImageInfo.imageCreator = null;
                ImageInfo.imageCreationTime = imageFilter.GetCreationTime();
                ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime();
                ImageInfo.imageName = Path.GetFileNameWithoutExtension(imageFilter.GetFilename());
                ImageInfo.imageComments = null;
                ImageInfo.mediaManufacturer = null;
                ImageInfo.mediaModel = null;
                ImageInfo.mediaSerialNumber = null;
                ImageInfo.mediaBarcode = null;
                ImageInfo.mediaPartNumber = null;
                ImageInfo.driveManufacturer = null;
                ImageInfo.driveModel = null;
                ImageInfo.driveSerialNumber = null;
                ImageInfo.driveFirmwareRevision = null;
                ImageInfo.mediaSequence = 0;
                ImageInfo.lastMediaSequence = 0;
                if(imageNewFormat)
                {
                    ImageInfo.imageSize = footerV2.FirstChunkOffset;
                    ImageInfo.imageVersion = "Nero Burning ROM >= 5.5";
                    ImageInfo.imageApplication = "Nero Burning ROM";
                    ImageInfo.imageApplicationVersion = ">= 5.5";
                }
                else
                {
                    ImageInfo.imageSize = footerV1.FirstChunkOffset;
                    ImageInfo.imageVersion = "Nero Burning ROM <= 5.0";
                    ImageInfo.imageApplication = "Nero Burning ROM";
                    ImageInfo.imageApplicationVersion = "<= 5.0";
                }

                if(neroSessions.Count == 0)
                    neroSessions.Add(1, currenttrack);

                DicConsole.DebugWriteLine("Nero plugin", "Building offset, track and session maps");

                currentsession = 1;
                uint currentsessionmaxtrack;
                neroSessions.TryGetValue(1, out currentsessionmaxtrack);
                uint currentsessioncurrenttrack = 1;
                Session currentsessionstruct = new Session();
                ulong PartitionSequence = 0;
                ulong partitionStartByte = 0;
                for(uint i = 1; i <= neroTracks.Count; i++)
                {
                    NeroTrack _neroTrack;
                    if(neroTracks.TryGetValue(i, out _neroTrack))
                    {
                        DicConsole.DebugWriteLine("Nero plugin", "\tcurrentsession = {0}", currentsession);
                        DicConsole.DebugWriteLine("Nero plugin", "\tcurrentsessionmaxtrack = {0}", currentsessionmaxtrack);
                        DicConsole.DebugWriteLine("Nero plugin", "\tcurrentsessioncurrenttrack = {0}", currentsessioncurrenttrack);

                        Track _track = new Track();
                        if(_neroTrack.Sequence == 1)
                            _neroTrack.Index0 = _neroTrack.Index1;

                        _track.Indexes = new Dictionary<int, ulong>();
                        if(_neroTrack.Index0 < _neroTrack.Index1)
                            _track.Indexes.Add(0, _neroTrack.Index0 / _neroTrack.SectorSize);
                        _track.Indexes.Add(1, _neroTrack.Index1 / _neroTrack.SectorSize);
                        _track.TrackDescription = StringHandlers.CToString(_neroTrack.ISRC);
                        _track.TrackEndSector = (_neroTrack.Length / _neroTrack.SectorSize) + _neroTrack.StartLBA - 1;
                        _track.TrackPregap = (_neroTrack.Index1 - _neroTrack.Index0) / _neroTrack.SectorSize;
                        _track.TrackSequence = _neroTrack.Sequence;
                        _track.TrackSession = currentsession;
                        _track.TrackStartSector = _neroTrack.StartLBA;
                        _track.TrackType = NeroTrackModeToTrackType((DAOMode)_neroTrack.Mode);
                        _track.TrackFile = imageFilter.GetFilename();
                        _track.TrackFilter = imageFilter;
                        _track.TrackFileOffset = _neroTrack.Offset;
                        _track.TrackFileType = "BINARY";
                        _track.TrackSubchannelType = TrackSubchannelType.None;
                        switch((DAOMode)_neroTrack.Mode)
                        {
                            case DAOMode.Audio:
                                _track.TrackBytesPerSector = 2352;
                                _track.TrackRawBytesPerSector = 2352;
                                break;
                            case DAOMode.AudioSub:
                                _track.TrackBytesPerSector = 2352;
                                _track.TrackRawBytesPerSector = 2448;
                                _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved;
                                break;
                            case DAOMode.Data:
                            case DAOMode.DataM2F1:
                                _track.TrackBytesPerSector = 2048;
                                _track.TrackRawBytesPerSector = 2048;
                                break;
                            case DAOMode.DataM2F2:
                                _track.TrackBytesPerSector = 2336;
                                _track.TrackRawBytesPerSector = 2336;
                                break;
                            case DAOMode.DataM2Raw:
                                _track.TrackBytesPerSector = 2352;
                                _track.TrackRawBytesPerSector = 2352;
                                break;
                            case DAOMode.DataM2RawSub:
                                _track.TrackBytesPerSector = 2352;
                                _track.TrackRawBytesPerSector = 2448;
                                _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved;
                                break;
                            case DAOMode.DataRaw:
                                _track.TrackBytesPerSector = 2048;
                                _track.TrackRawBytesPerSector = 2352;
                                break;
                            case DAOMode.DataRawSub:
                                _track.TrackBytesPerSector = 2048;
                                _track.TrackRawBytesPerSector = 2448;
                                _track.TrackSubchannelType = TrackSubchannelType.RawInterleaved;
                                break;
                        }

                        if(_track.TrackSubchannelType == TrackSubchannelType.RawInterleaved)
                        {
                            _track.TrackSubchannelFilter = imageFilter;
                            _track.TrackSubchannelFile = imageFilter.GetFilename();
                            _track.TrackSubchannelOffset = _neroTrack.Offset;
                        }

                        imageTracks.Add(_track);

                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackDescription = {0}", _track.TrackDescription);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackEndSector = {0}", _track.TrackEndSector);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackPregap = {0}", _track.TrackPregap);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackSequence = {0}", _track.TrackSequence);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackSession = {0}", _track.TrackSession);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackStartSector = {0}", _track.TrackStartSector);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t _track.TrackType = {0}", _track.TrackType);

                        if(currentsessioncurrenttrack == 1)
                        {
                            currentsessionstruct = new Session();
                            currentsessionstruct.SessionSequence = currentsession;
                            currentsessionstruct.StartSector = _track.TrackStartSector;
                            currentsessionstruct.StartTrack = _track.TrackSequence;
                        }
                        currentsessioncurrenttrack++;
                        if(currentsessioncurrenttrack > currentsessionmaxtrack)
                        {
                            currentsession++;
                            neroSessions.TryGetValue(currentsession, out currentsessionmaxtrack);
                            currentsessioncurrenttrack = 1;
                            currentsessionstruct.EndTrack = _track.TrackSequence;
                            currentsessionstruct.EndSector = _track.TrackEndSector;
                            imageSessions.Add(currentsessionstruct);
                        }

                        if(i == neroTracks.Count)
                        {
                            neroSessions.TryGetValue(currentsession, out currentsessionmaxtrack);
                            currentsessioncurrenttrack = 1;
                            currentsessionstruct.EndTrack = _track.TrackSequence;
                            currentsessionstruct.EndSector = _track.TrackEndSector;
                            imageSessions.Add(currentsessionstruct);
                        }

                        offsetmap.Add(_track.TrackSequence, _track.TrackStartSector);
                        DicConsole.DebugWriteLine("Nero plugin", "\t\t Offset[{0}]: {1}", _track.TrackSequence, _track.TrackStartSector);

                        Partition partition;

                        /*if(_neroTrack.Index0 < _neroTrack.Index1)
                        {
                            partition = new Partition();
                            partition.PartitionDescription = string.Format("Track {0} Index 0", _track.TrackSequence);
                            partition.PartitionLength = (_neroTrack.Index1 - _neroTrack.Index0);
                            partition.PartitionName = StringHandlers.CToString(_neroTrack.ISRC);
                            partition.PartitionSectors = partition.PartitionLength / _neroTrack.SectorSize;
                            partition.PartitionSequence = PartitionSequence;
                            partition.PartitionStart = _neroTrack.Index0;
                            partition.PartitionStartSector = _neroTrack.StartLBA;
                            partition.PartitionType = NeroTrackModeToTrackType((DAOMode)_neroTrack.Mode).ToString();
                            ImagePartitions.Add(partition);
                            PartitionSequence++;
                        }*/

                        partition = new Partition();
                        partition.Description = string.Format("Track {0} Index 1", _track.TrackSequence);
                        partition.Size = (_neroTrack.EndOfTrack - _neroTrack.Index1);
                        partition.Name = StringHandlers.CToString(_neroTrack.ISRC);
                        partition.Length = partition.Size / _neroTrack.SectorSize;
                        partition.Sequence = PartitionSequence;
                        partition.Offset = partitionStartByte;
                        partition.Start = _neroTrack.StartLBA + ((_neroTrack.Index1 - _neroTrack.Index0) / _neroTrack.SectorSize);
                        partition.Type = NeroTrackModeToTrackType((DAOMode)_neroTrack.Mode).ToString();
                        ImagePartitions.Add(partition);
                        PartitionSequence++;
                        partitionStartByte += partition.Size;
                    }
                }

                _imageFilter = imageFilter;

                if(ImageInfo.mediaType == MediaType.Unknown || ImageInfo.mediaType == MediaType.CD)
                {
                    bool data = false;
                    bool mode2 = false;
                    bool firstaudio = false;
                    bool firstdata = false;
                    bool audio = false;

                    for(int i = 0; i < neroTracks.Count; i++)
                    {
                        // First track is audio
						firstaudio |= i == 0 && ((DAOMode)neroTracks.ElementAt(i).Value.Mode == DAOMode.Audio || (DAOMode)neroTracks.ElementAt(i).Value.Mode == DAOMode.AudioSub);

                        // First track is data
                        firstdata |= i == 0 && ((DAOMode)neroTracks.ElementAt(i).Value.Mode != DAOMode.Audio && (DAOMode)neroTracks.ElementAt(i).Value.Mode != DAOMode.AudioSub);

                        // Any non first track is data
                        data |= i != 0 && ((DAOMode)neroTracks.ElementAt(i).Value.Mode != DAOMode.Audio && (DAOMode)neroTracks.ElementAt(i).Value.Mode != DAOMode.AudioSub);

                        // Any non first track is audio
                        audio |= i != 0 && ((DAOMode)neroTracks.ElementAt(i).Value.Mode == DAOMode.Audio || (DAOMode)neroTracks.ElementAt(i).Value.Mode == DAOMode.AudioSub);

                        switch((DAOMode)neroTracks.ElementAt(i).Value.Mode)
                        {
                            case DAOMode.DataM2F1:
                            case DAOMode.DataM2F2:
                            case DAOMode.DataM2Raw:
                            case DAOMode.DataM2RawSub:
                                mode2 = true;
                                break;
                        }
                    }

                    if(!data && !firstdata)
                        ImageInfo.mediaType = MediaType.CDDA;
                    else if(firstaudio && data && imageSessions.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.xmlMediaType = XmlMediaType.OpticalDisc;
                DicConsole.VerboseWriteLine("Nero image contains a disc of type {0}", ImageInfo.mediaType);

                return true;
            }
            catch
            {
				DicConsole.DebugWrite("Nero plugin", "Exception ocurred opening file.");
				return false;
            }
        }

        public override bool ImageHasPartitions()
        {
            // Even if they only have 1 track, there is a partition (track 1)
            return true;
        }

        public override ulong GetImageSize()
        {
            return ImageInfo.imageSize;
        }

        public override ulong GetSectors()
        {
            return ImageInfo.sectors;
        }

        public override uint GetSectorSize()
        {
            return ImageInfo.sectorSize;
        }

        public override byte[] ReadDiskTag(MediaTagType tag)
        {
            switch(tag)
            {
                case MediaTagType.CD_MCN:
                    return UPC;
                case MediaTagType.CD_TEXT:
                    throw new NotImplementedException("Not yet implemented");
                default:
                    throw new FeaturedNotSupportedByDiscImageException("Requested disk tag not supported by image");
            }
        }

        public override byte[] ReadSector(ulong sectorAddress)
        {
            return ReadSectors(sectorAddress, 1);
        }

        public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag)
        {
            return ReadSectorsTag(sectorAddress, 1, tag);
        }

        public override byte[] ReadSector(ulong sectorAddress, uint track)
        {
            return ReadSectors(sectorAddress, 1, track);
        }

        public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag)
        {
            return ReadSectorsTag(sectorAddress, 1, track, tag);
        }

        public override byte[] ReadSectors(ulong sectorAddress, uint length)
        {
            foreach(KeyValuePair<uint, ulong> kvp in offsetmap)
            {
                if(sectorAddress >= kvp.Value)
                {
                    foreach(Track _track in imageTracks)
                    {
                        if(_track.TrackSequence == kvp.Key)
                        {
                            if((sectorAddress - kvp.Value) < (_track.TrackEndSector - _track.TrackStartSector))
                                return ReadSectors((sectorAddress - kvp.Value), length, kvp.Key);
                        }
                    }
                }
            }

            throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress));
        }

        public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag)
        {
            foreach(KeyValuePair<uint, ulong> kvp in offsetmap)
            {
                if(sectorAddress >= kvp.Value)
                {
                    foreach(Track _track in imageTracks)
                    {
                        if(_track.TrackSequence == kvp.Key)
                        {
                            if((sectorAddress - kvp.Value) < (_track.TrackEndSector - _track.TrackStartSector))
                                return ReadSectorsTag((sectorAddress - kvp.Value), length, kvp.Key, tag);
                        }
                    }
                }
            }

            throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress));
        }

        public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track)
        {
            NeroTrack _track;

            if(!neroTracks.TryGetValue(track, out _track))
                throw new ArgumentOutOfRangeException(nameof(track), "Track not found");

            if(length > _track.Sectors)
                throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length, _track.Sectors));

            uint sector_offset;
            uint sector_size;
            uint sector_skip;

            switch((DAOMode)_track.Mode)
            {
                case DAOMode.Data:
                case DAOMode.DataM2F1:
                    {
                        sector_offset = 0;
                        sector_size = 2048;
                        sector_skip = 0;
                        break;
                    }
                case DAOMode.DataM2F2:
                    {
                        sector_offset = 8;
                        sector_size = 2324;
                        sector_skip = 4;
                        break;
                    }
                case DAOMode.Audio:
                    {
                        sector_offset = 0;
                        sector_size = 2352;
                        sector_skip = 0;
                        break;
                    }
                case DAOMode.DataRaw:
                    {
                        sector_offset = 16;
                        sector_size = 2048;
                        sector_skip = 288;
                        break;
                    }
                case DAOMode.DataM2Raw:
                    {
                        sector_offset = 16;
                        sector_size = 2336;
                        sector_skip = 0;
                        break;
                    }
                // TODO: Supposing Nero suffixes the subchannel to the channel
                case DAOMode.DataRawSub:
                    {
                        sector_offset = 16;
                        sector_size = 2048;
                        sector_skip = 288 + 96;
                        break;
                    }
                case DAOMode.DataM2RawSub:
                    {
                        sector_offset = 16;
                        sector_size = 2336;
                        sector_skip = 96;
                        break;
                    }
                case DAOMode.AudioSub:
                    {
                        sector_offset = 0;
                        sector_size = 2352;
                        sector_skip = 96;
                        break;
                    }
                default:
                    throw new FeatureSupportedButNotImplementedImageException("Unsupported track type");
            }

            byte[] buffer = new byte[sector_size * length];

            imageStream = _imageFilter.GetDataForkStream();
            BinaryReader br = new BinaryReader(imageStream);
            br.BaseStream.Seek((long)_track.Offset + (long)(sectorAddress * (sector_offset + sector_size + sector_skip)), SeekOrigin.Begin);
            if(sector_offset == 0 && sector_skip == 0)
                buffer = br.ReadBytes((int)(sector_size * length));
            else
            {
                for(int i = 0; i < length; i++)
                {
                    byte[] sector;
                    br.BaseStream.Seek(sector_offset, SeekOrigin.Current);
                    sector = br.ReadBytes((int)sector_size);
                    br.BaseStream.Seek(sector_skip, SeekOrigin.Current);
                    Array.Copy(sector, 0, buffer, i * sector_size, sector_size);
                }
            }

            return buffer;
        }

        public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag)
        {
            NeroTrack _track;

            if(!neroTracks.TryGetValue(track, out _track))
                throw new ArgumentOutOfRangeException(nameof(track), "Track not found");

            if(length > _track.Sectors)
                throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length, _track.Sectors));

            uint sector_offset;
            uint sector_size;
            uint sector_skip;

            switch(tag)
            {
                case SectorTagType.CDSectorECC:
                case SectorTagType.CDSectorECC_P:
                case SectorTagType.CDSectorECC_Q:
                case SectorTagType.CDSectorEDC:
                case SectorTagType.CDSectorHeader:
                case SectorTagType.CDSectorSubchannel:
                case SectorTagType.CDSectorSubHeader:
                case SectorTagType.CDSectorSync:
                    break;
                case SectorTagType.CDTrackFlags:
                    {
                        byte[] flags = new byte[1];
                        flags[0] = 0x00;

                        if((DAOMode)_track.Mode != DAOMode.Audio && (DAOMode)_track.Mode != DAOMode.AudioSub)
                            flags[0] += 0x40;

                        return flags;
                    }
                case SectorTagType.CDTrackISRC:
                    return _track.ISRC;
                case SectorTagType.CDTrackText:
                    throw new FeatureSupportedButNotImplementedImageException("Feature not yet implemented");
                default:
                    throw new ArgumentException("Unsupported tag requested", nameof(tag));
            }

            switch((DAOMode)_track.Mode)
            {
                case DAOMode.Data:
                case DAOMode.DataM2F1:
                    throw new ArgumentException("No tags in image for requested track", nameof(tag));
                case DAOMode.DataM2F2:
                    {
                        switch(tag)
                        {
                            case SectorTagType.CDSectorSync:
                            case SectorTagType.CDSectorHeader:
                            case SectorTagType.CDSectorSubchannel:
                            case SectorTagType.CDSectorECC:
                            case SectorTagType.CDSectorECC_P:
                            case SectorTagType.CDSectorECC_Q:
                                throw new ArgumentException("Unsupported tag requested for this track", nameof(tag));
                            case SectorTagType.CDSectorSubHeader:
                                {
                                    sector_offset = 0;
                                    sector_size = 8;
                                    sector_skip = 2328;
                                    break;
                                }
                            case SectorTagType.CDSectorEDC:
                                {
                                    sector_offset = 2332;
                                    sector_size = 4;
                                    sector_skip = 0;
                                    break;
                                }
                            default:
                                throw new ArgumentException("Unsupported tag requested", nameof(tag));
                        }
                        break;
                    }
                case DAOMode.Audio:
                    throw new ArgumentException("There are no tags on audio tracks", nameof(tag));
                case DAOMode.DataRaw:
                    {
                        switch(tag)
                        {
                            case SectorTagType.CDSectorSync:
                                {
                                    sector_offset = 0;
                                    sector_size = 12;
                                    sector_skip = 2340;
                                    break;
                                }
                            case SectorTagType.CDSectorHeader:
                                {
                                    sector_offset = 12;
                                    sector_size = 4;
                                    sector_skip = 2336;
                                    break;
                                }
                            case SectorTagType.CDSectorSubchannel:
                            case SectorTagType.CDSectorSubHeader:
                                throw new ArgumentException("Unsupported tag requested for this track", nameof(tag));
                            case SectorTagType.CDSectorECC:
                                {
                                    sector_offset = 2076;
                                    sector_size = 276;
                                    sector_skip = 0;
                                    break;
                                }
                            case SectorTagType.CDSectorECC_P:
                                {
                                    sector_offset = 2076;
                                    sector_size = 172;
                                    sector_skip = 104;
                                    break;
                                }
                            case SectorTagType.CDSectorECC_Q:
                                {
                                    sector_offset = 2248;
                                    sector_size = 104;
                                    sector_skip = 0;
                                    break;
                                }
                            case SectorTagType.CDSectorEDC:
                                {
                                    sector_offset = 2064;
                                    sector_size = 4;
                                    sector_skip = 284;
                                    break;
                                }
                            default:
                                throw new ArgumentException("Unsupported tag requested", nameof(tag));
                        }
                        break;
                    }
                // TODO
                case DAOMode.DataM2RawSub:
                    throw new FeatureSupportedButNotImplementedImageException("Feature not yet implemented");
                case DAOMode.DataRawSub:
                    {
                        switch(tag)
                        {
                            case SectorTagType.CDSectorSync:
                                {
                                    sector_offset = 0;
                                    sector_size = 12;
                                    sector_skip = 2340 + 96;
                                    break;
                                }
                            case SectorTagType.CDSectorHeader:
                                {
                                    sector_offset = 12;
                                    sector_size = 4;
                                    sector_skip = 2336 + 96;
                                    break;
                                }
                            case SectorTagType.CDSectorSubchannel:
                                {
                                    sector_offset = 2352;
                                    sector_size = 96;
                                    sector_skip = 0;
                                    break;
                                }
                            case SectorTagType.CDSectorSubHeader:
                                throw new ArgumentException("Unsupported tag requested for this track", nameof(tag));
                            case SectorTagType.CDSectorECC:
                                {
                                    sector_offset = 2076;
                                    sector_size = 276;
                                    sector_skip = 0 + 96;
                                    break;
                                }
                            case SectorTagType.CDSectorECC_P:
                                {
                                    sector_offset = 2076;
                                    sector_size = 172;
                                    sector_skip = 104 + 96;
                                    break;
                                }
                            case SectorTagType.CDSectorECC_Q:
                                {
                                    sector_offset = 2248;
                                    sector_size = 104;
                                    sector_skip = 0 + 96;
                                    break;
                                }
                            case SectorTagType.CDSectorEDC:
                                {
                                    sector_offset = 2064;
                                    sector_size = 4;
                                    sector_skip = 284 + 96;
                                    break;
                                }
                            default:
                                throw new ArgumentException("Unsupported tag requested", nameof(tag));
                        }
                        break;
                    }
                case DAOMode.AudioSub:
                    {
                        if(tag != SectorTagType.CDSectorSubchannel)
                            throw new ArgumentException("Unsupported tag requested for this track", nameof(tag));

                        sector_offset = 2352;
                        sector_size = 96;
                        sector_skip = 0;
                        break;
                    }
                default:
                    throw new FeatureSupportedButNotImplementedImageException("Unsupported track type");
            }

            byte[] buffer = new byte[sector_size * length];

            imageStream = _imageFilter.GetDataForkStream();
            BinaryReader br = new BinaryReader(imageStream);
            br.BaseStream.Seek((long)_track.Offset + (long)(sectorAddress * (sector_offset + sector_size + sector_skip)), SeekOrigin.Begin);
            if(sector_offset == 0 && sector_skip == 0)
                buffer = br.ReadBytes((int)(sector_size * length));
            else
            {
                for(int i = 0; i < length; i++)
                {
                    byte[] sector;
                    br.BaseStream.Seek(sector_offset, SeekOrigin.Current);
                    sector = br.ReadBytes((int)sector_size);
                    br.BaseStream.Seek(sector_skip, SeekOrigin.Current);
                    Array.Copy(sector, 0, buffer, i * sector_size, sector_size);
                }
            }

            return buffer;
        }

        public override byte[] ReadSectorLong(ulong sectorAddress)
        {
            return ReadSectorsLong(sectorAddress, 1);
        }

        public override byte[] ReadSectorLong(ulong sectorAddress, uint track)
        {
            return ReadSectorsLong(sectorAddress, 1, track);
        }

        public override byte[] ReadSectorsLong(ulong sectorAddress, uint length)
        {
            foreach(KeyValuePair<uint, ulong> kvp in offsetmap)
            {
                if(sectorAddress >= kvp.Value)
                {
                    foreach(Track _track in imageTracks)
                    {
                        if(_track.TrackSequence == kvp.Key)
                        {
                            if((sectorAddress - kvp.Value) < (_track.TrackEndSector - _track.TrackStartSector))
                                return ReadSectorsLong((sectorAddress - kvp.Value), length, kvp.Key);
                        }
                    }
                }
            }

            throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress));
        }

        public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track)
        {
            NeroTrack _track;

            if(!neroTracks.TryGetValue(track, out _track))
                throw new ArgumentOutOfRangeException(nameof(track), "Track not found");

            if(length > _track.Sectors)
                throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length, _track.Sectors));

            uint sector_offset;
            uint sector_size;
            uint sector_skip;

            switch((DAOMode)_track.Mode)
            {
                case DAOMode.Data:
                case DAOMode.DataM2F1:
                    {
                        sector_offset = 0;
                        sector_size = 2048;
                        sector_skip = 0;
                        break;
                    }
                case DAOMode.DataM2F2:
                    {
                        sector_offset = 0;
                        sector_size = 2336;
                        sector_skip = 0;
                        break;
                    }
                case DAOMode.DataRaw:
                case DAOMode.DataM2Raw:
                case DAOMode.Audio:
                    {
                        sector_offset = 0;
                        sector_size = 2352;
                        sector_skip = 0;
                        break;
                    }
                case DAOMode.DataRawSub:
                case DAOMode.DataM2RawSub:
                case DAOMode.AudioSub:
                    {
                        sector_offset = 0;
                        sector_size = 2448;
                        sector_skip = 0;
                        break;
                    }
                default:
                    throw new FeatureSupportedButNotImplementedImageException("Unsupported track type");
            }

            byte[] buffer = new byte[sector_size * length];

            imageStream = _imageFilter.GetDataForkStream();
            BinaryReader br = new BinaryReader(imageStream);

            br.BaseStream.Seek((long)_track.Offset + (long)(sectorAddress * (sector_offset + sector_size + sector_skip)), SeekOrigin.Begin);

            if(sector_offset == 0 && sector_skip == 0)
                buffer = br.ReadBytes((int)(sector_size * length));
            else
            {
                for(int i = 0; i < length; i++)
                {
                    byte[] sector;
                    br.BaseStream.Seek(sector_offset, SeekOrigin.Current);
                    sector = br.ReadBytes((int)sector_size);
                    br.BaseStream.Seek(sector_skip, SeekOrigin.Current);

                    Array.Copy(sector, 0, buffer, i * sector_size, sector_size);
                }
            }

            return buffer;
        }

        public override string GetImageFormat()
        {
            return "Nero Burning ROM";
        }

        public override string GetImageVersion()
        {
            return ImageInfo.imageVersion;
        }

        public override string GetImageApplication()
        {
            return ImageInfo.imageApplication;
        }

        public override string GetImageApplicationVersion()
        {
            return ImageInfo.imageApplicationVersion;
        }

        public override DateTime GetImageCreationTime()
        {
            return ImageInfo.imageCreationTime;
        }

        public override DateTime GetImageLastModificationTime()
        {
            return ImageInfo.imageLastModificationTime;
        }

        public override string GetMediaBarcode()
        {
            return ImageInfo.mediaBarcode;
        }

        public override MediaType GetMediaType()
        {
            return ImageInfo.mediaType;
        }

        public override List<Partition> GetPartitions()
        {
            return ImagePartitions;
        }

        public override List<Track> GetTracks()
        {
            return imageTracks;
        }

        public override List<Track> GetSessionTracks(Session session)
        {
            return GetSessionTracks(session.SessionSequence);
        }

        public override List<Track> GetSessionTracks(ushort session)
        {
            List<Track> sessionTracks = new List<Track>();
            foreach(Track _track in imageTracks)
                if(_track.TrackSession == session)
                    sessionTracks.Add(_track);

            return sessionTracks;
        }

        public override List<Session> GetSessions()
        {
            return imageSessions;
        }

        public override bool? VerifySector(ulong sectorAddress)
        {
            byte[] buffer = ReadSectorLong(sectorAddress);
            return Checksums.CDChecksums.CheckCDSector(buffer);
        }

        public override bool? VerifySector(ulong sectorAddress, uint track)
        {
            byte[] buffer = ReadSectorLong(sectorAddress, track);
            return Checksums.CDChecksums.CheckCDSector(buffer);
        }

        public override bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> FailingLBAs, out List<ulong> UnknownLBAs)
        {
            byte[] buffer = ReadSectorsLong(sectorAddress, length);
            int bps = (int)(buffer.Length / length);
            byte[] sector = new byte[bps];
            FailingLBAs = new List<ulong>();
            UnknownLBAs = new List<ulong>();

            for(int i = 0; i < length; i++)
            {
                Array.Copy(buffer, i * bps, sector, 0, bps);
                bool? sectorStatus = Checksums.CDChecksums.CheckCDSector(sector);

                switch(sectorStatus)
                {
                    case null:
                        UnknownLBAs.Add((ulong)i + sectorAddress);
                        break;
                    case false:
                        FailingLBAs.Add((ulong)i + sectorAddress);
                        break;
                }
            }

            if(UnknownLBAs.Count > 0)
                return null;
            if(FailingLBAs.Count > 0)
                return false;
            return true;
        }

        public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> FailingLBAs, out List<ulong> UnknownLBAs)
        {
            byte[] buffer = ReadSectorsLong(sectorAddress, length, track);
            int bps = (int)(buffer.Length / length);
            byte[] sector = new byte[bps];
            FailingLBAs = new List<ulong>();
            UnknownLBAs = new List<ulong>();

            for(int i = 0; i < length; i++)
            {
                Array.Copy(buffer, i * bps, sector, 0, bps);
                bool? sectorStatus = Checksums.CDChecksums.CheckCDSector(sector);

                switch(sectorStatus)
                {
                    case null:
                        UnknownLBAs.Add((ulong)i + sectorAddress);
                        break;
                    case false:
                        FailingLBAs.Add((ulong)i + sectorAddress);
                        break;
                }
            }

            if(UnknownLBAs.Count > 0)
                return null;
            if(FailingLBAs.Count > 0)
                return false;
            return true;
        }

        public override bool? VerifyMediaImage()
        {
            return null;
        }

        #endregion

        #region Private methods

        static MediaType NeroMediaTypeToMediaType(NeroMediaTypes type)
        {
            switch(type)
            {
                case NeroMediaTypes.NERO_MTYP_DDCD:
                    return MediaType.DDCD;
                case NeroMediaTypes.NERO_MTYP_DVD_M:
                case NeroMediaTypes.NERO_MTYP_DVD_M_R:
                    return MediaType.DVDR;
                case NeroMediaTypes.NERO_MTYP_DVD_P:
                case NeroMediaTypes.NERO_MTYP_DVD_P_R:
                    return MediaType.DVDPR;
                case NeroMediaTypes.NERO_MTYP_DVD_RAM:
                    return MediaType.DVDRAM;
                case NeroMediaTypes.NERO_MTYP_ML:
                case NeroMediaTypes.NERO_MTYP_MRW:
                case NeroMediaTypes.NERO_MTYP_CDRW:
                    return MediaType.CDRW;
                case NeroMediaTypes.NERO_MTYP_CDR:
                    return MediaType.CDR;
                case NeroMediaTypes.NERO_MTYP_DVD_ROM:
                case NeroMediaTypes.NERO_MTYP_DVD_ANY:
                case NeroMediaTypes.NERO_MTYP_DVD_ANY_R9:
                case NeroMediaTypes.NERO_MTYP_DVD_ANY_OLD:
                    return MediaType.DVDROM;
                case NeroMediaTypes.NERO_MTYP_CDROM:
                    return MediaType.CDROM;
                case NeroMediaTypes.NERO_MTYP_DVD_M_RW:
                    return MediaType.DVDRW;
                case NeroMediaTypes.NERO_MTYP_DVD_P_RW:
                    return MediaType.DVDPRW;
                case NeroMediaTypes.NERO_MTYP_DVD_P_R9:
                    return MediaType.DVDPRDL;
                case NeroMediaTypes.NERO_MTYP_DVD_M_R9:
                    return MediaType.DVDRDL;
                case NeroMediaTypes.NERO_MTYP_BD:
                case NeroMediaTypes.NERO_MTYP_BD_ANY:
                case NeroMediaTypes.NERO_MTYP_BD_ROM:
                    return MediaType.BDROM;
                case NeroMediaTypes.NERO_MTYP_BD_R:
                    return MediaType.BDR;
                case NeroMediaTypes.NERO_MTYP_BD_RE:
                    return MediaType.BDRE;
                case NeroMediaTypes.NERO_MTYP_HD_DVD:
                case NeroMediaTypes.NERO_MTYP_HD_DVD_ANY:
                case NeroMediaTypes.NERO_MTYP_HD_DVD_ROM:
                    return MediaType.HDDVDROM;
                case NeroMediaTypes.NERO_MTYP_HD_DVD_R:
                    return MediaType.HDDVDR;
                case NeroMediaTypes.NERO_MTYP_HD_DVD_RW:
                    return MediaType.HDDVDRW;
                default:
                    return MediaType.CD;
            }
        }

        static TrackType NeroTrackModeToTrackType(DAOMode mode)
        {
            switch(mode)
            {
                case DAOMode.Data:
                case DAOMode.DataRaw:
                case DAOMode.DataRawSub:
                    return TrackType.CDMode1;
                case DAOMode.DataM2F1:
                    return TrackType.CDMode2Form1;
                case DAOMode.DataM2F2:
                    return TrackType.CDMode2Form2;
                case DAOMode.DataM2RawSub:
                case DAOMode.DataM2Raw:
                    return TrackType.CDMode2Formless;
                case DAOMode.Audio:
                case DAOMode.AudioSub:
                    return TrackType.Audio;
                default:
                    return TrackType.Data;
            }
        }

        static ushort NeroTrackModeToBytesPerSector(DAOMode mode)
        {
            switch(mode)
            {
                case DAOMode.Data:
                case DAOMode.DataM2F1:
                    return 2048;
                case DAOMode.DataM2F2:
                    return 2336;
                case DAOMode.DataRaw:
                case DAOMode.DataM2Raw:
                case DAOMode.Audio:
                    return 2352;
                case DAOMode.DataM2RawSub:
                case DAOMode.DataRawSub:
                case DAOMode.AudioSub:
                    return 2448;
                default:
                    return 2352;
            }
        }

        #endregion

        #region Unsupported features

        public override int GetMediaSequence()
        {
            return ImageInfo.mediaSequence;
        }

        public override int GetLastDiskSequence()
        {
            return ImageInfo.lastMediaSequence;
        }

        public override string GetDriveManufacturer()
        {
            return ImageInfo.driveManufacturer;
        }

        public override string GetDriveModel()
        {
            return ImageInfo.driveModel;
        }

        public override string GetDriveSerialNumber()
        {
            return ImageInfo.driveSerialNumber;
        }

        public override string GetMediaPartNumber()
        {
            return ImageInfo.mediaPartNumber;
        }

        public override string GetMediaManufacturer()
        {
            return ImageInfo.mediaManufacturer;
        }

        public override string GetMediaModel()
        {
            return ImageInfo.mediaModel;
        }

        public override string GetImageName()
        {
            return ImageInfo.imageName;
        }

        public override string GetImageCreator()
        {
            return ImageInfo.imageCreator;
        }

        public override string GetImageComments()
        {
            return ImageInfo.imageComments;
        }

        public override string GetMediaSerialNumber()
        {
            return ImageInfo.mediaSerialNumber;
        }

        #endregion
    }
}