// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : CDRDAO.cs // Author(s) : Natalia Portillo // // Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages cdrdao cuesheets (toc/bin). // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2018 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using DiscImageChef.Console; using DiscImageChef.CommonTypes; using System.Text; using DiscImageChef.Filters; namespace DiscImageChef.ImagePlugins { // TODO: Doesn't support compositing from several files // TODO: Doesn't support silences that are not in files public class CDRDAO : ImagePlugin { #region Internal structures struct CDRDAOTrackFile { /// Track # public uint sequence; /// Filter of file containing track public Filter datafilter; /// Path of file containing track public string datafile; /// Offset of track start in file public ulong offset; /// Type of file public string filetype; } struct CDRDAOTrack { /// Track # public uint sequence; /// Track title (from CD-Text) public string title; /// Track genre (from CD-Text) public string genre; /// Track arranger (from CD-Text) public string arranger; /// Track composer (from CD-Text) public string composer; /// Track performer (from CD-Text) public string performer; /// Track song writer (from CD-Text) public string songwriter; /// Track ISRC public string isrc; /// Disk provider's message (from CD-Text) public string message; /// File struct for this track public CDRDAOTrackFile trackfile; /// Indexes on this track public Dictionary indexes; /// Track pre-gap in sectors public ulong pregap; /// Track post-gap in sectors public ulong postgap; /// Digical Copy Permitted public bool flag_dcp; /// Track is quadraphonic public bool flag_4ch; /// Track has preemphasis public bool flag_pre; /// Bytes per sector public ushort bps; /// Sectors in track public ulong sectors; /// Starting sector in track public ulong startSector; /// Track type public string tracktype; public bool subchannel; public bool packedsubchannel; } struct CDRDAODisc { /// Disk title (from CD-Text) public string title; /// Disk genre (from CD-Text) public string genre; /// Disk arranger (from CD-Text) public string arranger; /// Disk composer (from CD-Text) public string composer; /// Disk performer (from CD-Text) public string performer; /// Disk song writer (from CD-Text) public string songwriter; /// Disk provider's message (from CD-Text) public string message; /// Media catalog number public string mcn; /// Disk type public MediaType disktype; /// Disk type string public string disktypestr; /// Disk CDDB ID public string disk_id; /// Disk UPC/EAN public string barcode; /// Tracks public List tracks; /// Disk comment public string comment; } #endregion Internal structures #region Internal consts /// Audio track, 2352 bytes/sector const string CDRDAOTrackTypeAudio = "AUDIO"; /// Mode 1 track, cooked, 2048 bytes/sector const string CDRDAOTrackTypeMode1 = "MODE1"; /// Mode 1 track, raw, 2352 bytes/sector const string CDRDAOTrackTypeMode1Raw = "MODE1_RAW"; /// Mode 2 mixed formless, cooked, 2336 bytes/sector const string CDRDAOTrackTypeMode2 = "MODE2"; /// Mode 2 form 1 track, cooked, 2048 bytes/sector const string CDRDAOTrackTypeMode2Form1 = "MODE2_FORM1"; /// Mode 2 form 2 track, cooked, 2324 bytes/sector const string CDRDAOTrackTypeMode2Form2 = "MODE2_FORM2"; /// Mode 2 mixed forms track, cooked, 2336 bytes/sector const string CDRDAOTrackTypeMode2Mix = "MODE2_FORM_MIX"; /// Mode 2 track, raw, 2352 bytes/sector const string CDRDAOTrackTypeMode2Raw = "MODE2_RAW"; #endregion Internal consts #region Internal variables Filter imageFilter; StreamReader tocStream; Stream imageStream; /// Dictionary, index is track #, value is TrackFile Dictionary offsetmap; List partitions; CDRDAODisc discimage; #endregion #region Parsing regexs const string CommentRegEx = "^\\s*\\/{2}(?.+)$"; const string DiskTypeRegEx = "^\\s*(?(CD_DA|CD_ROM_XA|CD_ROM|CD_I))"; const string MCNRegEx = "^\\s*CATALOG\\s*\"(?[\\d]{13,13})\""; const string TrackRegEx = "^\\s*TRACK\\s*(?(AUDIO|MODE1_RAW|MODE1|MODE2_FORM1|MODE2_FORM2|MODE2_FORM_MIX|MODE2_RAW|MODE2))\\s*(?(RW_RAW|RW))?"; const string CopyRegEx = "^\\s*(?NO)?\\s*COPY"; const string EmphasisRegEx = "^\\s*(?NO)?\\s*PRE_EMPHASIS"; const string StereoRegEx = "^\\s*(?(TWO|FOUR))_CHANNEL_AUDIO"; const string ISRCRegEx = "^\\s*ISRC\\s*\"(?[A-Z0-9]{5,5}[0-9]{7,7})\""; const string IndexRegEx = "^\\s*INDEX\\s*(?
\\d+:\\d+:\\d+)"; const string PregapRegEx = "^\\s*START\\s*(?
\\d+:\\d+:\\d+)?"; const string ZeroPregapRegEx = "^\\s*PREGAP\\s*(?\\d+:\\d+:\\d+)"; const string ZeroDataRegEx = "^\\s*ZERO\\s*(?\\d+:\\d+:\\d+)"; const string ZeroAudioRegEx = "^\\s*SILENCE\\s*(?\\d+:\\d+:\\d+)"; const string AudioFileRegEx = "^\\s*(AUDIO)?FILE\\s*\"(?.+)\"\\s*(#(?\\d+))?\\s*((?[\\d]+:[\\d]+:[\\d]+)|(?\\d+))\\s*(?[\\d]+:[\\d]+:[\\d]+)?"; const string FileRegEx = "^\\s*DATAFILE\\s*\"(?.+)\"\\s*(#(?\\d+))?\\s*(?[\\d]+:[\\d]+:[\\d]+)?"; // CD-Text const string TitleRegEx = "^\\s*TITLE\\s*\"(?.+)\""; const string PerformerRegEx = "^\\s*PERFORMER\\s*\"(?<performer>.+)\""; const string SongwriterRegEx = "^\\s*SONGWRITER\\s*\"(?<songwriter>.+)\""; const string ComposerRegEx = "^\\s*COMPOSER\\s*\"(?<composer>.+)\""; const string ArrangerRegEx = "^\\s*ARRANGER\\s*\"(?<arranger>.+)\""; const string MessageRegEx = "^\\s*MESSAGE\\s*\"(?<message>.+)\""; const string DiscIDRegEx = "^\\s*DISC_ID\\s*\"(?<discid>.+)\""; const string UPCRegEx = "^\\s*UPC_EAN\\s*\"(?<catalog>[\\d]{13,13})\""; // Unused const string CDTextRegEx = "^\\s*CD_TEXT\\s*\\{"; const string LanguageRegEx = "^\\s*LANGUAGE\\s*(?<code>\\d+)\\s*\\{"; const string ClosureRegEx = "^\\s*\\}"; const string LanguageMapRegEx = "^\\s*LANGUAGE_MAP\\s*\\{"; const string LanguageMappingRegEx = "^\\s*(?<code>\\d+)\\s?\\:\\s?(?<language>\\d+|\\w+)"; #endregion #region Public methods public CDRDAO() { Name = "CDRDAO tocfile"; PluginUUID = new Guid("04D7BA12-1BE8-44D4-97A4-1B48A505463E"); ImageInfo = new ImageInfo(); ImageInfo.readableSectorTags = new List<SectorTagType>(); ImageInfo.readableMediaTags = new List<MediaTagType>(); ImageInfo.imageHasPartitions = true; ImageInfo.imageHasSessions = true; ImageInfo.imageVersion = null; ImageInfo.imageApplicationVersion = null; ImageInfo.imageName = null; ImageInfo.imageCreator = null; ImageInfo.mediaManufacturer = null; ImageInfo.mediaModel = null; ImageInfo.mediaPartNumber = null; ImageInfo.mediaSequence = 0; ImageInfo.lastMediaSequence = 0; ImageInfo.driveManufacturer = null; ImageInfo.driveModel = null; ImageInfo.driveSerialNumber = null; ImageInfo.driveFirmwareRevision = null; } #endregion Public methods public override bool IdentifyImage(Filter imageFilter) { try { imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); byte[] testArray = new byte[512]; imageFilter.GetDataForkStream().Read(testArray, 0, 512); imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); // Check for unexpected control characters that shouldn't be present in a text file and can crash this plugin bool twoConsecutiveNulls = false; for(int i = 0; i < 512; i++) { if(i >= imageFilter.GetDataForkStream().Length) break; if(testArray[i] == 0) { if(twoConsecutiveNulls) return false; twoConsecutiveNulls = true; } else twoConsecutiveNulls = false; if(testArray[i] < 0x20 && testArray[i] != 0x0A && testArray[i] != 0x0D && testArray[i] != 0x00) return false; } tocStream = new StreamReader(imageFilter.GetDataForkStream()); string _line; Regex Cr = new Regex(CommentRegEx); Regex Dr = new Regex(DiskTypeRegEx); Match Dm; Match Cm; while(tocStream.Peek() >= 0) { _line = tocStream.ReadLine(); Dm = Dr.Match(_line); Cm = Cr.Match(_line); // Skip comments at start of file if(Cm.Success) continue; return Dm.Success; } return false; } catch(Exception ex) { DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", this.imageFilter.GetFilename()); DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); return false; } } public override bool OpenImage(Filter imageFilter) { if(imageFilter == null) return false; if(imageFilter == null) return false; this.imageFilter = imageFilter; try { imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); tocStream = new StreamReader(imageFilter.GetDataForkStream()); int line = 0; bool intrack = false; // Initialize all RegExs Regex RegexComment = new Regex(CommentRegEx); Regex RegexDiskType = new Regex(DiskTypeRegEx); Regex RegexMCN = new Regex(MCNRegEx); Regex RegexTrack = new Regex(TrackRegEx); Regex RegexCopy = new Regex(CopyRegEx); Regex RegexEmphasis = new Regex(EmphasisRegEx); Regex RegexStereo = new Regex(StereoRegEx); Regex RegexISRC = new Regex(ISRCRegEx); Regex RegexIndex = new Regex(IndexRegEx); Regex RegexPregap = new Regex(PregapRegEx); Regex RegexZeroPregap = new Regex(ZeroPregapRegEx); Regex RegexZeroData = new Regex(ZeroDataRegEx); Regex RegexZeroAudio = new Regex(ZeroAudioRegEx); Regex RegexAudioFile = new Regex(AudioFileRegEx); Regex RegexFile = new Regex(FileRegEx); Regex RegexTitle = new Regex(TitleRegEx); Regex RegexPerformer = new Regex(PerformerRegEx); Regex RegexSongwriter = new Regex(SongwriterRegEx); Regex RegexComposer = new Regex(ComposerRegEx); Regex RegexArranger = new Regex(ArrangerRegEx); Regex RegexMessage = new Regex(MessageRegEx); Regex RegexDiscID = new Regex(DiscIDRegEx); Regex RegexUPC = new Regex(UPCRegEx); Regex RegexCDText = new Regex(CDTextRegEx); Regex RegexLanguage = new Regex(LanguageRegEx); Regex RegexClosure = new Regex(ClosureRegEx); Regex RegexLanguageMap = new Regex(LanguageMapRegEx); Regex RegexLanguageMapping = new Regex(LanguageMappingRegEx); // Initialize all RegEx matches Match MatchComment; Match MatchDiskType; Match MatchMCN; Match MatchTrack; Match MatchCopy; Match MatchEmphasis; Match MatchStereo; Match MatchISRC; Match MatchIndex; Match MatchPregap; Match MatchZeroPregap; Match MatchZeroData; Match MatchZeroAudio; Match MatchAudioFile; Match MatchFile; Match MatchTitle; Match MatchPerformer; Match MatchSongwriter; Match MatchComposer; Match MatchArranger; Match MatchMessage; Match MatchDiscID; Match MatchUPC; Match MatchCDText; Match MatchLanguage; Match MatchClosure; Match MatchLanguageMap; Match MatchLanguageMapping; // Initialize disc discimage = new CDRDAODisc(); discimage.tracks = new List<CDRDAOTrack>(); discimage.comment = ""; CDRDAOTrack currenttrack = new CDRDAOTrack(); uint currentTrackNumber = 0; currenttrack.indexes = new Dictionary<int, ulong>(); currenttrack.pregap = 0; ulong currentSector = 0; int nextindex = 2; StringBuilder commentBuilder = new StringBuilder(); tocStream = new StreamReader(this.imageFilter.GetDataForkStream()); string _line; while(tocStream.Peek() >= 0) { line++; _line = tocStream.ReadLine(); MatchDiskType = RegexDiskType.Match(_line); MatchComment = RegexComment.Match(_line); // Skip comments at start of file if(MatchComment.Success) continue; if(!MatchDiskType.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Not a CDRDAO TOC or TOC type not in line {0}.", line); return false; } break; } tocStream = new StreamReader(this.imageFilter.GetDataForkStream()); FiltersList filtersList = new FiltersList(); line = 0; tocStream.BaseStream.Position = 0; while(tocStream.Peek() >= 0) { line++; _line = tocStream.ReadLine(); MatchComment = RegexComment.Match(_line); MatchDiskType = RegexDiskType.Match(_line); MatchMCN = RegexMCN.Match(_line); MatchTrack = RegexTrack.Match(_line); MatchCopy = RegexCopy.Match(_line); MatchEmphasis = RegexEmphasis.Match(_line); MatchStereo = RegexStereo.Match(_line); MatchISRC = RegexISRC.Match(_line); MatchIndex = RegexIndex.Match(_line); MatchPregap = RegexPregap.Match(_line); MatchZeroPregap = RegexZeroPregap.Match(_line); MatchZeroData = RegexZeroData.Match(_line); MatchZeroAudio = RegexZeroAudio.Match(_line); MatchAudioFile = RegexAudioFile.Match(_line); MatchFile = RegexFile.Match(_line); MatchTitle = RegexTitle.Match(_line); MatchPerformer = RegexPerformer.Match(_line); MatchSongwriter = RegexSongwriter.Match(_line); MatchComposer = RegexComposer.Match(_line); MatchArranger = RegexArranger.Match(_line); MatchMessage = RegexMessage.Match(_line); MatchDiscID = RegexDiscID.Match(_line); MatchUPC = RegexUPC.Match(_line); MatchCDText = RegexCDText.Match(_line); MatchLanguage = RegexLanguage.Match(_line); MatchClosure = RegexClosure.Match(_line); MatchLanguageMap = RegexLanguageMap.Match(_line); MatchLanguageMapping = RegexLanguageMapping.Match(_line); if(MatchComment.Success) { // Ignore "// Track X" comments if(!MatchComment.Groups["comment"].Value.StartsWith(" Track ", StringComparison.Ordinal)) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found comment \"{1}\" at line {0}", line, MatchComment.Groups["comment"].Value.Trim()); commentBuilder.AppendLine(MatchComment.Groups["comment"].Value.Trim()); } } else if(MatchDiskType.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found {1} at line {0}", line, MatchDiskType.Groups["type"].Value); discimage.disktypestr = MatchDiskType.Groups["type"].Value; switch(MatchDiskType.Groups["type"].Value) { case "CD_DA": discimage.disktype = MediaType.CDDA; break; case "CD_ROM": discimage.disktype = MediaType.CDROM; break; case "CD_ROM_XA": discimage.disktype = MediaType.CDROMXA; break; case "CD_I": discimage.disktype = MediaType.CDI; break; default: discimage.disktype = MediaType.CD; break; } } else if(MatchMCN.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found CATALOG \"{1}\" at line {0}", line, MatchMCN.Groups["catalog"].Value); discimage.mcn = MatchMCN.Groups["catalog"].Value; } else if(MatchTrack.Success) { if(MatchTrack.Groups["subchan"].Value == "") DicConsole.DebugWriteLine("CDRDAO plugin", "Found TRACK type \"{1}\" with no subchannel at line {0}", line, MatchTrack.Groups["type"].Value); else DicConsole.DebugWriteLine("CDRDAO plugin", "Found TRACK type \"{1}\" subchannel {2} at line {0}", line, MatchTrack.Groups["type"].Value, MatchTrack.Groups["subchan"].Value); if(intrack) { currentSector += currenttrack.sectors; if(currenttrack.pregap != currenttrack.sectors && !currenttrack.indexes.ContainsKey(1)) currenttrack.indexes.Add(1, currenttrack.startSector + currenttrack.pregap); discimage.tracks.Add(currenttrack); currenttrack = new CDRDAOTrack(); currenttrack.indexes = new Dictionary<int, ulong>(); currenttrack.pregap = 0; nextindex = 2; } currentTrackNumber++; intrack = true; switch(MatchTrack.Groups["type"].Value) { case "AUDIO": case "MODE1_RAW": case "MODE2_RAW": currenttrack.bps = 2352; break; case "MODE1": case "MODE2_FORM1": currenttrack.bps = 2048; break; case "MODE2_FORM2": currenttrack.bps = 2324; break; case "MODE2": case "MODE2_FORM_MIX": currenttrack.bps = 2336; break; default: throw new NotSupportedException(string.Format("Track mode {0} is unsupported", MatchTrack.Groups["type"].Value)); } switch(MatchTrack.Groups["subchan"].Value) { case "": break; case "RW": currenttrack.packedsubchannel = true; goto case "RW_RAW"; case "RW_RAW": currenttrack.bps += 96; currenttrack.subchannel = true; break; default: throw new NotSupportedException(string.Format("Track subchannel mode {0} is unsupported", MatchTrack.Groups["subchan"].Value)); } currenttrack.tracktype = MatchTrack.Groups["type"].Value; currenttrack.sequence = currentTrackNumber; currenttrack.startSector = currentSector; } else if(MatchCopy.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found {1} COPY at line {0}", line, MatchCopy.Groups["no"].Value); currenttrack.flag_dcp |= intrack && MatchCopy.Groups["no"].Value == ""; } else if(MatchEmphasis.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found {1} PRE_EMPHASIS at line {0}", line, MatchEmphasis.Groups["no"].Value); currenttrack.flag_pre |= intrack && MatchCopy.Groups["no"].Value == ""; } else if(MatchStereo.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found {1}_CHANNEL_AUDIO at line {0}", line, MatchStereo.Groups["num"].Value); currenttrack.flag_4ch |= intrack && MatchCopy.Groups["num"].Value == "FOUR"; } else if(MatchISRC.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found ISRC \"{1}\" at line {0}", line, MatchISRC.Groups["isrc"].Value); if(intrack) currenttrack.isrc = MatchISRC.Groups["isrc"].Value; } else if(MatchIndex.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found INDEX \"{1}\" at line {0}", line, MatchIndex.Groups["address"].Value); string[] lengthString = MatchFile.Groups["length"].Value.Split(new char[] { ':' }); ulong nextIndexPos = ulong.Parse(lengthString[0]) * 60 * 75 + ulong.Parse(lengthString[1]) * 75 + ulong.Parse(lengthString[2]); currenttrack.indexes.Add(nextindex, nextIndexPos + currenttrack.pregap + currenttrack.startSector); } else if(MatchPregap.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found START \"{1}\" at line {0}", line, MatchPregap.Groups["address"].Value); currenttrack.indexes.Add(0, currenttrack.startSector); if(MatchPregap.Groups["address"].Value != "") { string[] lengthString = MatchPregap.Groups["address"].Value.Split(new char[] { ':' }); currenttrack.pregap = ulong.Parse(lengthString[0]) * 60 * 75 + ulong.Parse(lengthString[1]) * 75 + ulong.Parse(lengthString[2]); } else currenttrack.pregap = currenttrack.sectors; } else if(MatchZeroPregap.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found PREGAP \"{1}\" at line {0}", line, MatchZeroPregap.Groups["length"].Value); currenttrack.indexes.Add(0, currenttrack.startSector); string[] lengthString = MatchZeroPregap.Groups["length"].Value.Split(new char[] { ':' }); currenttrack.pregap = ulong.Parse(lengthString[0]) * 60 * 75 + ulong.Parse(lengthString[1]) * 75 + ulong.Parse(lengthString[2]); } else if(MatchZeroData.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found ZERO \"{1}\" at line {0}", line, MatchZeroData.Groups["length"].Value); // Seems can be ignored as the data is still in the image } else if(MatchZeroAudio.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found SILENCE \"{1}\" at line {0}", line, MatchZeroAudio.Groups["length"].Value); // Seems can be ignored as the data is still in the image } else if(MatchAudioFile.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found AUDIOFILE \"{1}\" at line {0}", line, MatchAudioFile.Groups["filename"].Value); filtersList = new FiltersList(); currenttrack.trackfile = new CDRDAOTrackFile(); currenttrack.trackfile.datafilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), MatchAudioFile.Groups["filename"].Value)); currenttrack.trackfile.datafile = MatchAudioFile.Groups["filename"].Value; currenttrack.trackfile.offset = MatchAudioFile.Groups["base_offset"].Value != "" ? ulong.Parse(MatchAudioFile.Groups["base_offset"].Value) : 0; currenttrack.trackfile.filetype = "BINARY"; currenttrack.trackfile.sequence = currentTrackNumber; ulong startSectors = 0; if(MatchAudioFile.Groups["start"].Value != "") { string[] startString = MatchAudioFile.Groups["start"].Value.Split(new char[] { ':' }); startSectors = ulong.Parse(startString[0]) * 60 * 75 + ulong.Parse(startString[1]) * 75 + ulong.Parse(startString[2]); } currenttrack.trackfile.offset += (startSectors * currenttrack.bps); if(MatchAudioFile.Groups["length"].Value != "") { string[] lengthString = MatchAudioFile.Groups["length"].Value.Split(new char[] { ':' }); currenttrack.sectors = ulong.Parse(lengthString[0]) * 60 * 75 + ulong.Parse(lengthString[1]) * 75 + ulong.Parse(lengthString[2]); } else currenttrack.sectors = ((ulong)currenttrack.trackfile.datafilter.GetDataForkLength() - currenttrack.trackfile.offset) / currenttrack.bps; } else if(MatchFile.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found DATAFILE \"{1}\" at line {0}", line, MatchFile.Groups["filename"].Value); filtersList = new FiltersList(); currenttrack.trackfile = new CDRDAOTrackFile(); currenttrack.trackfile.datafilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), MatchFile.Groups["filename"].Value)); currenttrack.trackfile.datafile = MatchAudioFile.Groups["filename"].Value; currenttrack.trackfile.offset = MatchFile.Groups["base_offset"].Value != "" ? ulong.Parse(MatchFile.Groups["base_offset"].Value) : 0; currenttrack.trackfile.filetype = "BINARY"; currenttrack.trackfile.sequence = currentTrackNumber; if(MatchFile.Groups["length"].Value != "") { string[] lengthString = MatchFile.Groups["length"].Value.Split(new char[] { ':' }); currenttrack.sectors = ulong.Parse(lengthString[0]) * 60 * 75 + ulong.Parse(lengthString[1]) * 75 + ulong.Parse(lengthString[2]); } else currenttrack.sectors = ((ulong)currenttrack.trackfile.datafilter.GetDataForkLength() - currenttrack.trackfile.offset) / currenttrack.bps; } else if(MatchTitle.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found TITLE \"{1}\" at line {0}", line, MatchTitle.Groups["title"].Value); if(intrack) currenttrack.title = MatchTitle.Groups["title"].Value; else discimage.title = MatchTitle.Groups["title"].Value; } else if(MatchPerformer.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found PERFORMER \"{1}\" at line {0}", line, MatchPerformer.Groups["performer"].Value); if(intrack) currenttrack.performer = MatchPerformer.Groups["performer"].Value; else discimage.performer = MatchPerformer.Groups["performer"].Value; } else if(MatchSongwriter.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found SONGWRITER \"{1}\" at line {0}", line, MatchSongwriter.Groups["songwriter"].Value); if(intrack) currenttrack.songwriter = MatchSongwriter.Groups["songwriter"].Value; else discimage.songwriter = MatchSongwriter.Groups["songwriter"].Value; } else if(MatchComposer.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found COMPOSER \"{1}\" at line {0}", line, MatchComposer.Groups["composer"].Value); if(intrack) currenttrack.composer = MatchComposer.Groups["composer"].Value; else discimage.composer = MatchComposer.Groups["composer"].Value; } else if(MatchArranger.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found ARRANGER \"{1}\" at line {0}", line, MatchArranger.Groups["arranger"].Value); if(intrack) currenttrack.arranger = MatchArranger.Groups["arranger"].Value; else discimage.arranger = MatchArranger.Groups["arranger"].Value; } else if(MatchMessage.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found MESSAGE \"{1}\" at line {0}", line, MatchMessage.Groups["message"].Value); if(intrack) currenttrack.message = MatchMessage.Groups["message"].Value; else discimage.message = MatchMessage.Groups["message"].Value; } else if(MatchDiscID.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found DISC_ID \"{1}\" at line {0}", line, MatchDiscID.Groups["discid"].Value); if(!intrack) discimage.disk_id = MatchDiscID.Groups["discid"].Value; } else if(MatchUPC.Success) { DicConsole.DebugWriteLine("CDRDAO plugin", "Found UPC_EAN \"{1}\" at line {0}", line, MatchUPC.Groups["catalog"].Value); if(!intrack) discimage.barcode = MatchUPC.Groups["catalog"].Value; } // Ignored fields else if(MatchCDText.Success || MatchLanguage.Success || MatchClosure.Success || MatchLanguageMap.Success || MatchLanguageMapping.Success) { } else if(_line == "") // Empty line, ignore it { } // TODO: Regex CD-TEXT SIZE_INFO /* else // Non-empty unknown field { throw new FeatureUnsupportedImageException(string.Format("Found unknown field defined at line {0}: \"{1}\"", line, _line)); } */ } if(currenttrack.sequence != 0) { if(currenttrack.pregap != currenttrack.sectors && !currenttrack.indexes.ContainsKey(1)) currenttrack.indexes.Add(1, currenttrack.startSector + currenttrack.pregap); discimage.tracks.Add(currenttrack); } discimage.comment = commentBuilder.ToString(); // DEBUG information DicConsole.DebugWriteLine("CDRDAO plugin", "Disc image parsing results"); DicConsole.DebugWriteLine("CDRDAO plugin", "Disc CD-TEXT:"); if(discimage.arranger == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tArranger is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tArranger: {0}", discimage.arranger); if(discimage.composer == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tComposer is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tComposer: {0}", discimage.composer); if(discimage.performer == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tPerformer is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tPerformer: {0}", discimage.performer); if(discimage.songwriter == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tSongwriter is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tSongwriter: {0}", discimage.songwriter); if(discimage.title == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tTitle is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tTitle: {0}", discimage.title); DicConsole.DebugWriteLine("CDRDAO plugin", "Disc information:"); DicConsole.DebugWriteLine("CDRDAO plugin", "\tGuessed disk type: {0}", discimage.disktype); if(discimage.barcode == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tBarcode not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tBarcode: {0}", discimage.barcode); if(discimage.disk_id == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tDisc ID not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tDisc ID: {0}", discimage.disk_id); if(discimage.mcn == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\tMCN not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tMCN: {0}", discimage.mcn); if(string.IsNullOrEmpty(discimage.comment)) DicConsole.DebugWriteLine("CDRDAO plugin", "\tComment not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\tComment: \"{0}\"", discimage.comment); DicConsole.DebugWriteLine("CDRDAO plugin", "Track information:"); DicConsole.DebugWriteLine("CDRDAO plugin", "\tDisc contains {0} tracks", discimage.tracks.Count); for(int i = 0; i < discimage.tracks.Count; i++) { DicConsole.DebugWriteLine("CDRDAO plugin", "\tTrack {0} information:", discimage.tracks[i].sequence); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\t{0} bytes per sector", discimage.tracks[i].bps); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tPregap: {0} sectors", discimage.tracks[i].pregap); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tData: {0} sectors starting at sector {1}", discimage.tracks[i].sectors, discimage.tracks[i].startSector); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tPostgap: {0} sectors", discimage.tracks[i].postgap); if(discimage.tracks[i].flag_4ch) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTrack is flagged as quadraphonic"); if(discimage.tracks[i].flag_dcp) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTrack allows digital copy"); if(discimage.tracks[i].flag_pre) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTrack has pre-emphasis applied"); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTrack resides in file {0}, type defined as {1}, starting at byte {2}", discimage.tracks[i].trackfile.datafilter.GetFilename(), discimage.tracks[i].trackfile.filetype, discimage.tracks[i].trackfile.offset); DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tIndexes:"); foreach(KeyValuePair<int, ulong> kvp in discimage.tracks[i].indexes) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\t\tIndex {0} starts at sector {1}", kvp.Key, kvp.Value); if(discimage.tracks[i].isrc == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tISRC is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tISRC: {0}", discimage.tracks[i].isrc); if(discimage.tracks[i].arranger == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tArranger is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tArranger: {0}", discimage.tracks[i].arranger); if(discimage.tracks[i].composer == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tComposer is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tComposer: {0}", discimage.tracks[i].composer); if(discimage.tracks[i].performer == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tPerformer is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tPerformer: {0}", discimage.tracks[i].performer); if(discimage.tracks[i].songwriter == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tSongwriter is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tSongwriter: {0}", discimage.tracks[i].songwriter); if(discimage.tracks[i].title == null) DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTitle is not set."); else DicConsole.DebugWriteLine("CDRDAO plugin", "\t\tTitle: {0}", discimage.tracks[i].title); } DicConsole.DebugWriteLine("CDRDAO plugin", "Building offset map"); partitions = new List<Partition>(); offsetmap = new Dictionary<uint, ulong>(); ulong byte_offset = 0; ulong partitionSequence = 0; for(int i = 0; i < discimage.tracks.Count; i++) { ulong index0_len = 0; if(discimage.tracks[i].sequence == 1 && i != 0) throw new ImageNotSupportedException("Unordered tracks"); Partition partition = new Partition(); // Index 01 partition.Description = string.Format("Track {0}.", discimage.tracks[i].sequence); partition.Name = discimage.tracks[i].title; partition.Start = discimage.tracks[i].startSector; partition.Size = (discimage.tracks[i].sectors - index0_len) * discimage.tracks[i].bps; partition.Length = (discimage.tracks[i].sectors - index0_len); partition.Sequence = partitionSequence; partition.Offset = byte_offset; partition.Type = discimage.tracks[i].tracktype; byte_offset += partition.Size; partitionSequence++; if(!offsetmap.ContainsKey(discimage.tracks[i].sequence)) offsetmap.Add(discimage.tracks[i].sequence, partition.Start); else { ulong old_start; offsetmap.TryGetValue(discimage.tracks[i].sequence, out old_start); if(partition.Start < old_start) { offsetmap.Remove(discimage.tracks[i].sequence); offsetmap.Add(discimage.tracks[i].sequence, partition.Start); } } partitions.Add(partition); partition = new Partition(); } // Print partition map DicConsole.DebugWriteLine("CDRDAO plugin", "printing partition map"); foreach(Partition partition in partitions) { DicConsole.DebugWriteLine("CDRDAO plugin", "Partition sequence: {0}", partition.Sequence); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition name: {0}", partition.Name); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition description: {0}", partition.Description); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition type: {0}", partition.Type); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition starting sector: {0}", partition.Start); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition sectors: {0}", partition.Length); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition starting offset: {0}", partition.Offset); DicConsole.DebugWriteLine("CDRDAO plugin", "\tPartition size in bytes: {0}", partition.Size); } foreach(CDRDAOTrack track in discimage.tracks) { ImageInfo.imageSize += track.bps * track.sectors; ImageInfo.sectors += track.sectors; } if(discimage.disktype == MediaType.CDG || discimage.disktype == MediaType.CDEG || discimage.disktype == MediaType.CDMIDI) ImageInfo.sectorSize = 2448; // CD+G subchannels ARE user data, as CD+G are useless without them else if(discimage.disktype != MediaType.CDROMXA && discimage.disktype != MediaType.CDDA && discimage.disktype != MediaType.CDI && discimage.disktype != MediaType.CDPLUS) ImageInfo.sectorSize = 2048; // Only data tracks else ImageInfo.sectorSize = 2352; // All others if(discimage.mcn != null) ImageInfo.readableMediaTags.Add(MediaTagType.CD_MCN); ImageInfo.imageApplication = "CDRDAO"; ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); ImageInfo.imageComments = discimage.comment; ImageInfo.mediaSerialNumber = discimage.mcn; ImageInfo.mediaBarcode = discimage.barcode; ImageInfo.mediaType = discimage.disktype; ImageInfo.readableSectorTags.Add(SectorTagType.CDTrackFlags); foreach(CDRDAOTrack track in discimage.tracks) { if(track.subchannel) { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubchannel)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubchannel); } switch(track.tracktype) { case CDRDAOTrackTypeAudio: { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDTrackISRC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDTrackISRC); break; } case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); break; } case CDRDAOTrackTypeMode2Raw: { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); break; } case CDRDAOTrackTypeMode1Raw: { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC_P)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC_P); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC_Q)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC_Q); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); break; } } } ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; DicConsole.VerboseWriteLine("CDRDAO image describes a disc of type {0}", ImageInfo.mediaType); if(!string.IsNullOrEmpty(ImageInfo.imageComments)) DicConsole.VerboseWriteLine("CDRDAO comments: {0}", ImageInfo.imageComments); return true; } catch(Exception ex) { DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", imageFilter); DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); return false; } } public override bool ImageHasPartitions() { return ImageInfo.imageHasPartitions; } 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: { if(discimage.mcn != null) { return Encoding.ASCII.GetBytes(discimage.mcn); } throw new FeatureNotPresentImageException("Image does not contain MCN information."); } default: throw new FeatureSupportedButNotImplementedImageException("Feature not supported by image format"); } } 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(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == kvp.Key) { if((sectorAddress - kvp.Value) < cdrdao_track.sectors) 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(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == kvp.Key) { if((sectorAddress - kvp.Value) < cdrdao_track.sectors) 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) { CDRDAOTrack _track = new CDRDAOTrack(); _track.sequence = 0; foreach(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == track) { _track = cdrdao_track; break; } } if(_track.sequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length > _track.sectors) throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than present in track, won't cross tracks"); uint sector_offset; uint sector_size; uint sector_skip; switch(_track.tracktype) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode2Form1: { sector_offset = 0; sector_size = 2048; sector_skip = 0; break; } case CDRDAOTrackTypeMode2Form2: { sector_offset = 0; sector_size = 2324; sector_skip = 0; break; } case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: { sector_offset = 0; sector_size = 2336; sector_skip = 0; break; } case CDRDAOTrackTypeAudio: { sector_offset = 0; sector_size = 2352; sector_skip = 0; break; } case CDRDAOTrackTypeMode1Raw: { sector_offset = 16; sector_size = 2048; sector_skip = 288; break; } case CDRDAOTrackTypeMode2Raw: { sector_offset = 16; sector_size = 2336; sector_skip = 0; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } if(_track.subchannel) sector_skip += 96; byte[] buffer = new byte[sector_size * length]; imageStream = _track.trackfile.datafilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.trackfile.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) { CDRDAOTrack _track = new CDRDAOTrack(); _track.sequence = 0; foreach(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == track) { _track = cdrdao_track; break; } } if(_track.sequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length > _track.sectors) throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than present in track, won't cross tracks"); uint sector_offset; uint sector_size; uint sector_skip = 0; if(!_track.subchannel && tag == SectorTagType.CDSectorSubchannel) throw new ArgumentException("No tags in image for requested track", nameof(tag)); 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]; if(_track.tracktype != CDRDAOTrackTypeAudio) flags[0] += 0x40; if(_track.flag_dcp) flags[0] += 0x20; if(_track.flag_pre) flags[0] += 0x10; if(_track.flag_4ch) flags[0] += 0x80; return flags; } case SectorTagType.CDTrackISRC: return Encoding.UTF8.GetBytes(_track.isrc); default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } switch(_track.tracktype) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode2Form1: if(tag == SectorTagType.CDSectorSubchannel) { sector_offset = 2048; sector_size = 96; break; } throw new ArgumentException("No tags in image for requested track", nameof(tag)); case CDRDAOTrackTypeMode2Form2: case CDRDAOTrackTypeMode2Mix: if(tag == SectorTagType.CDSectorSubchannel) { sector_offset = 2336; sector_size = 96; break; } throw new ArgumentException("No tags in image for requested track", nameof(tag)); case CDRDAOTrackTypeAudio: if(tag == SectorTagType.CDSectorSubchannel) { sector_offset = 2352; sector_size = 96; break; } throw new ArgumentException("No tags in image for requested track", nameof(tag)); case CDRDAOTrackTypeMode1Raw: { 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: { sector_offset = 2352; sector_size = 96; 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; 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; } case CDRDAOTrackTypeMode2Raw: // Requires reading sector if(tag == SectorTagType.CDSectorSubchannel) { sector_offset = 2352; sector_size = 96; break; } throw new FeatureSupportedButNotImplementedImageException("Feature not yet implemented"); default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } byte[] buffer = new byte[sector_size * length]; imageStream = _track.trackfile.datafilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.trackfile.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(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == kvp.Key) { if((sectorAddress - kvp.Value) < cdrdao_track.sectors) return ReadSectorsLong((sectorAddress - kvp.Value), length, kvp.Key); } } } } throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); } public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) { CDRDAOTrack _track = new CDRDAOTrack(); _track.sequence = 0; foreach(CDRDAOTrack cdrdao_track in discimage.tracks) { if(cdrdao_track.sequence == track) { _track = cdrdao_track; break; } } if(_track.sequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length > _track.sectors) throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than present in track, won't cross tracks"); uint sector_offset; uint sector_size; uint sector_skip; switch(_track.tracktype) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode2Form1: { sector_offset = 0; sector_size = 2048; sector_skip = 0; break; } case CDRDAOTrackTypeMode2Form2: { sector_offset = 0; sector_size = 2324; sector_skip = 0; break; } case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: { sector_offset = 0; sector_size = 2336; sector_skip = 0; break; } case CDRDAOTrackTypeMode1Raw: case CDRDAOTrackTypeMode2Raw: case CDRDAOTrackTypeAudio: { sector_offset = 0; sector_size = 2352; sector_skip = 0; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } if(_track.subchannel) sector_skip += 96; byte[] buffer = new byte[sector_size * length]; imageStream = _track.trackfile.datafilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.trackfile.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 "CDRDAO tocfile"; } 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 GetImageComments() { return ImageInfo.imageComments; } public override string GetMediaSerialNumber() { return ImageInfo.mediaSerialNumber; } public override string GetMediaBarcode() { return ImageInfo.mediaBarcode; } public override MediaType GetMediaType() { return ImageInfo.mediaType; } public override List<Partition> GetPartitions() { return partitions; } public override List<Track> GetTracks() { List<Track> tracks = new List<Track>(); foreach(CDRDAOTrack cdr_track in discimage.tracks) { Track _track = new Track(); _track.Indexes = cdr_track.indexes; _track.TrackDescription = cdr_track.title; if(!cdr_track.indexes.TryGetValue(0, out _track.TrackStartSector)) cdr_track.indexes.TryGetValue(1, out _track.TrackStartSector); _track.TrackStartSector = cdr_track.startSector; _track.TrackEndSector = _track.TrackStartSector + cdr_track.sectors - 1; _track.TrackPregap = cdr_track.pregap; _track.TrackSession = 1; _track.TrackSequence = cdr_track.sequence; _track.TrackType = CDRDAOTrackTypeToTrackType(cdr_track.tracktype); _track.TrackFilter = cdr_track.trackfile.datafilter; _track.TrackFile = cdr_track.trackfile.datafilter.GetFilename(); _track.TrackFileOffset = cdr_track.trackfile.offset; _track.TrackFileType = cdr_track.trackfile.filetype; _track.TrackRawBytesPerSector = cdr_track.bps; _track.TrackBytesPerSector = CDRDAOTrackTypeToCookedBytesPerSector(cdr_track.tracktype); if(cdr_track.subchannel) { _track.TrackSubchannelType = cdr_track.packedsubchannel ? TrackSubchannelType.PackedInterleaved : TrackSubchannelType.RawInterleaved; _track.TrackSubchannelFilter = cdr_track.trackfile.datafilter; _track.TrackSubchannelFile = cdr_track.trackfile.datafilter.GetFilename(); _track.TrackSubchannelOffset = cdr_track.trackfile.offset; } else _track.TrackSubchannelType = TrackSubchannelType.None; tracks.Add(_track); } return tracks; } public override List<Track> GetSessionTracks(Session session) { return GetSessionTracks(session.SessionSequence); } public override List<Track> GetSessionTracks(ushort session) { if(session == 1) return GetTracks(); throw new ImageNotSupportedException("Session does not exist in disc image"); } 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; } #region Not implemented methods public override List<Session> GetSessions() { // TODO throw new NotImplementedException(); } #endregion #region Private methods static ushort CDRDAOTrackTypeToBytesPerSector(string trackType) { switch(trackType) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode2Form1: return 2048; case CDRDAOTrackTypeMode2Form2: return 2324; case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: return 2336; case CDRDAOTrackTypeAudio: case CDRDAOTrackTypeMode1Raw: case CDRDAOTrackTypeMode2Raw: return 2352; default: return 0; } } static ushort CDRDAOTrackTypeToCookedBytesPerSector(string trackType) { switch(trackType) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode2Form1: case CDRDAOTrackTypeMode1Raw: return 2048; case CDRDAOTrackTypeMode2Form2: return 2324; case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: case CDRDAOTrackTypeMode2Raw: return 2336; case CDRDAOTrackTypeAudio: return 2352; default: return 0; } } static TrackType CDRDAOTrackTypeToTrackType(string trackType) { switch(trackType) { case CDRDAOTrackTypeMode1: case CDRDAOTrackTypeMode1Raw: return TrackType.CDMode1; case CDRDAOTrackTypeMode2Form1: return TrackType.CDMode2Form1; case CDRDAOTrackTypeMode2Form2: return TrackType.CDMode2Form2; case CDRDAOTrackTypeMode2: case CDRDAOTrackTypeMode2Mix: case CDRDAOTrackTypeMode2Raw: return TrackType.CDMode2Formless; case CDRDAOTrackTypeAudio: return TrackType.Audio; default: return TrackType.Data; } } #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; } #endregion } }