// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : CloneCD.cs // Author(s) : Natalia Portillo // // Component : Component // // --[ Description ] ---------------------------------------------------------- // // Description // // --[ 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-2017 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Decoders.CD; using DiscImageChef.Filters; using DiscImageChef.ImagePlugins; namespace DiscImageChef.DiscImages { public class CloneCD : ImagePlugin { #region Parsing regexs const string CCD_Identifier = "^\\s*\\[CloneCD\\]"; const string Disc_Identifier = "^\\s*\\[Disc\\]"; const string Session_Identifier = "^\\s*\\[Session\\s*(?\\d+)\\]"; const string Entry_Identifier = "^\\s*\\[Entry\\s*(?\\d+)\\]"; const string Track_Identifier = "^\\s*\\[TRACK\\s*(?\\d+)\\]"; const string CDText_Identifier = "^\\s*\\[CDText\\]"; const string CCD_Version = "^\\s*Version\\s*=\\s*(?\\d+)"; const string Disc_Entries = "^\\s*TocEntries\\s*=\\s*(?\\d+)"; const string Disc_Sessions = "^\\s*Sessions\\s*=\\s*(?\\d+)"; const string Disc_Scrambled = "^\\s*DataTracksScrambled\\s*=\\s*(?\\d+)"; const string CDText_Length = "^\\s*CDTextLength\\s*=\\s*(?\\d+)"; const string Disc_Catalog = "^\\s*CATALOG\\s*=\\s*(?\\w+)"; const string Session_Pregap = "^\\s*PreGapMode\\s*=\\s*(?\\d+)"; const string Session_Subchannel = "^\\s*PreGapSubC\\s*=\\s*(?\\d+)"; const string Entry_Session = "^\\s*Session\\s*=\\s*(?\\d+)"; const string Entry_Point = "^\\s*Point\\s*=\\s*(?[\\w+]+)"; const string Entry_ADR = "^\\s*ADR\\s*=\\s*(?\\w+)"; const string Entry_Control = "^\\s*Control\\s*=\\s*(?\\w+)"; const string Entry_TrackNo = "^\\s*TrackNo\\s*=\\s*(?\\d+)"; const string Entry_AMin = "^\\s*AMin\\s*=\\s*(?\\d+)"; const string Entry_ASec = "^\\s*ASec\\s*=\\s*(?\\d+)"; const string Entry_AFrame = "^\\s*AFrame\\s*=\\s*(?\\d+)"; const string Entry_ALBA = "^\\s*ALBA\\s*=\\s*(?-?\\d+)"; const string Entry_Zero = "^\\s*Zero\\s*=\\s*(?\\d+)"; const string Entry_PMin = "^\\s*PMin\\s*=\\s*(?\\d+)"; const string Entry_PSec = "^\\s*PSec\\s*=\\s*(?\\d+)"; const string Entry_PFrame = "^\\s*PFrame\\s*=\\s*(?\\d+)"; const string Entry_PLBA ="^\\s*PLBA\\s*=\\s*(?\\d+)"; const string CDText_Entries = "^\\s*Entries\\s*=\\s*(?\\d+)"; const string CDText_Entry = "^\\s*Entry\\s*(?\\d+)\\s*=\\s*(?([0-9a-fA-F]+\\s*)+)"; #endregion Filter imageFilter; Filter dataFilter; Filter subFilter; StreamReader cueStream; byte[] fulltoc; bool scrambled; string catalog; List sessions; List partitions; List tracks; Stream dataStream; Stream subStream; Dictionary offsetmap; byte[] cdtext; public CloneCD() { Name = "CloneCD"; PluginUUID = new Guid("EE9C2975-2E79-427A-8EE9-F86F19165784"); ImageInfo = new ImageInfo(); ImageInfo.readableSectorTags = new List(); ImageInfo.readableMediaTags = new List(); 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; } public override bool IdentifyImage(Filter imageFilter) { this.imageFilter = 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 foreach(byte b in testArray) { if(b < 0x20 && b != 0x0A && b != 0x0D && b != 0x00) return false; } cueStream = new StreamReader(this.imageFilter.GetDataForkStream()); string _line = cueStream.ReadLine(); Regex Hdr = new Regex(CCD_Identifier); Match Hdm; Hdm = Hdr.Match(_line); return Hdm.Success; } catch(Exception ex) { DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", this.imageFilter); 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; this.imageFilter = imageFilter; try { imageFilter.GetDataForkStream().Seek(0, SeekOrigin.Begin); cueStream = new StreamReader(imageFilter.GetDataForkStream()); int line = 0; Regex CCD_IdRegex = new Regex(CCD_Identifier); Regex Disc_IdRegex = new Regex(Disc_Identifier); Regex Sess_IdRegex = new Regex(Session_Identifier); Regex Entry_IdRegex = new Regex(Entry_Identifier); Regex Track_IdRegex = new Regex(Track_Identifier); Regex CDT_IdRegex = new Regex(CDText_Identifier); Regex CCD_VerRegex = new Regex(CCD_Version); Regex Disc_EntRegex = new Regex(Disc_Entries); Regex Disc_SessRegex = new Regex(Disc_Sessions); Regex Disc_ScrRegex = new Regex(Disc_Scrambled); Regex CDT_LenRegex = new Regex(CDText_Length); Regex Disc_CatRegex = new Regex(Disc_Catalog); Regex Sess_PregRegex = new Regex(Session_Pregap); Regex Sess_SubcRegex = new Regex(Session_Subchannel); Regex Ent_SessRegex = new Regex(Entry_Session); Regex Ent_PointRegex = new Regex(Entry_Point); Regex Ent_ADRRegex = new Regex(Entry_ADR); Regex Ent_CtrlRegex = new Regex(Entry_Control); Regex Ent_TNORegex = new Regex(Entry_TrackNo); Regex Ent_AMinRegex = new Regex(Entry_AMin); Regex Ent_ASecRegex = new Regex(Entry_ASec); Regex Ent_AFrameRegex = new Regex(Entry_AFrame); Regex Ent_ALBARegex = new Regex(Entry_ALBA); Regex Ent_ZeroRegex = new Regex(Entry_Zero); Regex Ent_PMinRegex = new Regex(Entry_PMin); Regex Ent_PSecRegex = new Regex(Entry_PSec); Regex Ent_PFrameRegex = new Regex(Entry_PFrame); Regex Ent_PLBARegex = new Regex(Entry_PLBA); Regex CDT_EntsRegex = new Regex(CDText_Entries); Regex CDT_EntRegex = new Regex(CDText_Entry); Match CCD_IdMatch; Match Disc_IdMatch; Match Sess_IdMatch; Match Entry_IdMatch; Match Track_IdMatch; Match CDT_IdMatch; Match CCD_VerMatch; Match Disc_EntMatch; Match Disc_SessMatch; Match Disc_ScrMatch; Match CDT_LenMatch; Match Disc_CatMatch; Match Sess_PregMatch; Match Sess_SubcMatch; Match Ent_SessMatch; Match Ent_PointMatch; Match Ent_ADRMatch; Match Ent_CtrlMatch; Match Ent_TNOMatch; Match Ent_AMinMatch; Match Ent_ASecMatch; Match Ent_AFrameMatch; Match Ent_ALBAMatch; Match Ent_ZeroMatch; Match Ent_PMinMatch; Match Ent_PSecMatch; Match Ent_PFrameMatch; Match Ent_PLBAMatch; Match CDT_EntsMatch; Match CDT_EntMatch; bool inCcd = false; bool inDisk = false; bool inSession = false; bool inEntry = false; bool inTrack = false; bool inCDText = false; MemoryStream cdtMs = new MemoryStream(); int minSession = int.MaxValue; int maxSession = int.MinValue; FullTOC.TrackDataDescriptor currentEntry = new FullTOC.TrackDataDescriptor(); List entries = new List(); scrambled = false; catalog = null; while(cueStream.Peek() >= 0) { line++; string _line = cueStream.ReadLine(); CCD_IdMatch = CCD_IdRegex.Match(_line); Disc_IdMatch = Disc_IdRegex.Match(_line); Sess_IdMatch = Sess_IdRegex.Match(_line); Entry_IdMatch = Entry_IdRegex.Match(_line); Track_IdMatch = Track_IdRegex.Match(_line); CDT_IdMatch = CDT_IdRegex.Match(_line); // [CloneCD] if(CCD_IdMatch.Success) { if(inDisk || inSession || inEntry || inTrack || inCDText) throw new FeatureUnsupportedImageException(string.Format("Found [CloneCD] out of order in line {0}", line)); inCcd = true; inDisk = false; inSession = false; inEntry = false; inTrack = false; inCDText = false; } else if(Disc_IdMatch.Success || Sess_IdMatch.Success || Entry_IdMatch.Success || Track_IdMatch.Success || CDT_IdMatch.Success) { if(inEntry) { entries.Add(currentEntry); currentEntry = new FullTOC.TrackDataDescriptor(); } inCcd = false; inDisk = Disc_IdMatch.Success; inSession = Sess_IdMatch.Success; inEntry = Entry_IdMatch.Success; inTrack = Track_IdMatch.Success; inCDText = CDT_IdMatch.Success; } else { if(inCcd) { CCD_VerMatch = CCD_VerRegex.Match(_line); if(CCD_VerMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Version at line {0}", line); ImageInfo.imageVersion = CCD_VerMatch.Groups["value"].Value; if(ImageInfo.imageVersion != "2") DicConsole.ErrorWriteLine("(CloneCD plugin): Warning! Unknown CCD image version {0}, may not work!", ImageInfo.imageVersion); } } else if(inDisk) { Disc_EntMatch = Disc_EntRegex.Match(_line); Disc_SessMatch = Disc_SessRegex.Match(_line); Disc_ScrMatch = Disc_ScrRegex.Match(_line); CDT_LenMatch = CDT_LenRegex.Match(_line); Disc_CatMatch = Disc_CatRegex.Match(_line); if(Disc_EntMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found TocEntries at line {0}", line); } else if(Disc_SessMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Sessions at line {0}", line); } else if(Disc_ScrMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found DataTracksScrambled at line {0}", line); scrambled |= Disc_ScrMatch.Groups["value"].Value == "1"; } else if(CDT_LenMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found CDTextLength at line {0}", line); } else if(Disc_CatMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Catalog at line {0}", line); catalog = Disc_CatMatch.Groups["value"].Value; } } // TODO: Do not suppose here entries come sorted else if(inCDText) { CDT_EntsMatch = CDT_EntsRegex.Match(_line); CDT_EntMatch = CDT_EntRegex.Match(_line); if(CDT_EntsMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found CD-Text Entries at line {0}", line); } else if(CDT_EntMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found CD-Text Entry at line {0}", line); string[] bytes = CDT_EntMatch.Groups["value"].Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach(string byt in bytes) cdtMs.WriteByte(Convert.ToByte(byt, 16)); } } // Is this useful? else if(inSession) { Sess_PregMatch = Sess_PregRegex.Match(_line); Sess_SubcMatch = Sess_SubcRegex.Match(_line); if(Sess_PregMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PreGapMode at line {0}", line); } else if(Sess_SubcMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PreGapSubC at line {0}", line); } } else if(inEntry) { Ent_SessMatch = Ent_SessRegex.Match(_line); Ent_PointMatch = Ent_PointRegex.Match(_line); Ent_ADRMatch = Ent_ADRRegex.Match(_line); Ent_CtrlMatch = Ent_CtrlRegex.Match(_line); Ent_TNOMatch = Ent_TNORegex.Match(_line); Ent_AMinMatch = Ent_AMinRegex.Match(_line); Ent_ASecMatch = Ent_ASecRegex.Match(_line); Ent_AFrameMatch = Ent_AFrameRegex.Match(_line); Ent_ALBAMatch = Ent_ALBARegex.Match(_line); Ent_ZeroMatch = Ent_ZeroRegex.Match(_line); Ent_PMinMatch = Ent_PMinRegex.Match(_line); Ent_PSecMatch = Ent_PSecRegex.Match(_line); Ent_PFrameMatch = Ent_PFrameRegex.Match(_line); Ent_PLBAMatch = Ent_PLBARegex.Match(_line); if(Ent_SessMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Session at line {0}", line); currentEntry.SessionNumber = Convert.ToByte(Ent_SessMatch.Groups["value"].Value, 10); if(currentEntry.SessionNumber < minSession) minSession = currentEntry.SessionNumber; if(currentEntry.SessionNumber > maxSession) maxSession = currentEntry.SessionNumber; } else if(Ent_PointMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Point at line {0}", line); currentEntry.POINT = Convert.ToByte(Ent_PointMatch.Groups["value"].Value, 16); } else if(Ent_ADRMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found ADR at line {0}", line); currentEntry.ADR = Convert.ToByte(Ent_ADRMatch.Groups["value"].Value, 16); } else if(Ent_CtrlMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Control at line {0}", line); currentEntry.CONTROL = Convert.ToByte(Ent_CtrlMatch.Groups["value"].Value, 16); } else if(Ent_TNOMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found TrackNo at line {0}", line); currentEntry.TNO = Convert.ToByte(Ent_TNOMatch.Groups["value"].Value, 10); } else if(Ent_AMinMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found AMin at line {0}", line); currentEntry.Min = Convert.ToByte(Ent_AMinMatch.Groups["value"].Value, 10); } else if(Ent_ASecMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found ASec at line {0}", line); currentEntry.Sec = Convert.ToByte(Ent_ASecMatch.Groups["value"].Value, 10); } else if(Ent_AFrameMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found AFrame at line {0}", line); currentEntry.Frame = Convert.ToByte(Ent_AFrameMatch.Groups["value"].Value, 10); } else if(Ent_ALBAMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found ALBA at line {0}", line); } else if(Ent_ZeroMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found Zero at line {0}", line); currentEntry.Zero = Convert.ToByte(Ent_ZeroMatch.Groups["value"].Value, 10); currentEntry.HOUR = (byte)((currentEntry.Zero & 0xF0) >> 4); currentEntry.PHOUR = (byte)(currentEntry.Zero & 0x0F); } else if(Ent_PMinMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PMin at line {0}", line); currentEntry.PMIN = Convert.ToByte(Ent_PMinMatch.Groups["value"].Value, 10); } else if(Ent_PSecMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PSec at line {0}", line); currentEntry.PSEC = Convert.ToByte(Ent_PSecMatch.Groups["value"].Value, 10); } else if(Ent_PFrameMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PFrame at line {0}", line); currentEntry.PFRAME = Convert.ToByte(Ent_PFrameMatch.Groups["value"].Value, 10); } else if(Ent_PLBAMatch.Success) { DicConsole.DebugWriteLine("CloneCD plugin", "Found PLBA at line {0}", line); } } } } if(inEntry) entries.Add(currentEntry); if(entries.Count == 0) throw new FeatureUnsupportedImageException("Did not find any track."); FullTOC.CDFullTOC toc; toc.TrackDescriptors = entries.ToArray(); toc.LastCompleteSession = (byte)maxSession; toc.FirstCompleteSession = (byte)minSession; toc.DataLength = (ushort)(entries.Count * 11 + 2); MemoryStream tocMs = new MemoryStream(); tocMs.Write(BigEndianBitConverter.GetBytes(toc.DataLength), 0, 2); tocMs.WriteByte(toc.FirstCompleteSession); tocMs.WriteByte(toc.LastCompleteSession); foreach(FullTOC.TrackDataDescriptor descriptor in toc.TrackDescriptors) { tocMs.WriteByte(descriptor.SessionNumber); tocMs.WriteByte((byte)((descriptor.ADR << 4) + descriptor.CONTROL)); tocMs.WriteByte(descriptor.TNO); tocMs.WriteByte(descriptor.POINT); tocMs.WriteByte(descriptor.Min); tocMs.WriteByte(descriptor.Sec); tocMs.WriteByte(descriptor.Frame); tocMs.WriteByte(descriptor.Zero); tocMs.WriteByte(descriptor.PMIN); tocMs.WriteByte(descriptor.PSEC); tocMs.WriteByte(descriptor.PFRAME); } fulltoc = tocMs.ToArray(); ImageInfo.readableMediaTags.Add(MediaTagType.CD_FullTOC); DicConsole.DebugWriteLine("CloneCD plugin", "{0}", FullTOC.Prettify(toc)); string dataFile = Path.GetFileNameWithoutExtension(imageFilter.GetBasePath()) + ".img"; string subFile = Path.GetFileNameWithoutExtension(imageFilter.GetBasePath()) + ".sub"; FiltersList filtersList = new FiltersList(); dataFilter = filtersList.GetFilter(dataFile); if(dataFilter == null) throw new Exception("Cannot open data file"); filtersList = new FiltersList(); subFilter = filtersList.GetFilter(subFile); int curSessionNo = 0; Track currentTrack = new Track(); bool firstTrackInSession = true; tracks = new List(); byte discType; ulong LeadOutStart = 0; dataStream = dataFilter.GetDataForkStream(); if(subFilter != null) subStream = subFilter.GetDataForkStream(); foreach(FullTOC.TrackDataDescriptor descriptor in entries) { if(descriptor.SessionNumber > curSessionNo) { curSessionNo = descriptor.SessionNumber; if(!firstTrackInSession) { currentTrack.TrackEndSector = LeadOutStart - 1; tracks.Add(currentTrack); } firstTrackInSession = true; } switch(descriptor.ADR) { case 1: case 4: switch(descriptor.POINT) { case 0xA0: discType = descriptor.PSEC; DicConsole.DebugWriteLine("CloneCD plugin", "Disc Type: {0}", discType); break; case 0xA2: LeadOutStart = GetLBA(descriptor.PHOUR, descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME); break; default: if(descriptor.POINT >= 0x01 && descriptor.POINT <= 0x63) { if(!firstTrackInSession) { currentTrack.TrackEndSector = GetLBA(descriptor.PHOUR, descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME) - 1; tracks.Add(currentTrack); } else firstTrackInSession = false; currentTrack = new Track(); currentTrack.TrackBytesPerSector = 2352; currentTrack.TrackFile = dataFilter.GetFilename(); currentTrack.TrackFileType = scrambled ? "SCRAMBLED" : "BINARY"; currentTrack.TrackFilter = dataFilter; currentTrack.TrackRawBytesPerSector = 2352; currentTrack.TrackSequence = descriptor.POINT; currentTrack.TrackStartSector = GetLBA(descriptor.PHOUR, descriptor.PMIN, descriptor.PSEC, descriptor.PFRAME); currentTrack.TrackFileOffset = currentTrack.TrackStartSector * 2352; currentTrack.TrackSession = descriptor.SessionNumber; // Need to check exact data type later if((TOC_CONTROL)(descriptor.CONTROL & 0x0D) == TOC_CONTROL.DataTrack || (TOC_CONTROL)(descriptor.CONTROL & 0x0D) == TOC_CONTROL.DataTrackIncremental) currentTrack.TrackType = TrackType.Data; else currentTrack.TrackType = TrackType.Audio; if(subFilter != null) { currentTrack.TrackSubchannelFile = subFilter.GetFilename(); currentTrack.TrackSubchannelFilter = subFilter; currentTrack.TrackSubchannelOffset = currentTrack.TrackStartSector * 96; currentTrack.TrackSubchannelType = TrackSubchannelType.Raw; } else currentTrack.TrackSubchannelType = TrackSubchannelType.None; if(currentTrack.TrackType == TrackType.Data) { byte[] syncTest = new byte[12]; byte[] sectTest = new byte[2352]; dataStream.Seek((long)currentTrack.TrackFileOffset, SeekOrigin.Begin); dataStream.Read(sectTest, 0, 2352); Array.Copy(sectTest, 0, syncTest, 0, 12); if(Sector.SyncMark.SequenceEqual(syncTest)) { if(scrambled) sectTest = Sector.Scramble(sectTest); if(sectTest[15] == 1) { currentTrack.TrackBytesPerSector = 2048; currentTrack.TrackType = TrackType.CDMode1; 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.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); if(ImageInfo.sectorSize < 2048) ImageInfo.sectorSize = 2048; } else if(sectTest[15] == 2) { byte[] subHdr1 = new byte[4]; byte[] subHdr2 = new byte[4]; byte[] empHdr = new byte[4]; Array.Copy(sectTest, 16, subHdr1, 0, 4); Array.Copy(sectTest, 20, subHdr2, 0, 4); if(subHdr1.SequenceEqual(subHdr2) && !empHdr.SequenceEqual(subHdr1)) { if((subHdr1[2] & 0x20) == 0x20) { currentTrack.TrackBytesPerSector = 2324; currentTrack.TrackType = TrackType.CDMode2Form2; 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); if(ImageInfo.sectorSize < 2324) ImageInfo.sectorSize = 2324; } else { currentTrack.TrackBytesPerSector = 2048; currentTrack.TrackType = TrackType.CDMode2Form1; 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); if(ImageInfo.sectorSize < 2048) ImageInfo.sectorSize = 2048; } } else { currentTrack.TrackBytesPerSector = 2336; currentTrack.TrackType = TrackType.CDMode2Formless; if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); if(ImageInfo.sectorSize < 2336) ImageInfo.sectorSize = 2336; } } } } else { if(ImageInfo.sectorSize < 2352) ImageInfo.sectorSize = 2352; } } break; } break; case 5: switch(descriptor.POINT) { case 0xC0: if(descriptor.PMIN == 97) { int type = descriptor.PFRAME % 10; int frm = descriptor.PFRAME - type; ImageInfo.mediaManufacturer = ATIP.ManufacturerFromATIP(descriptor.PSEC, frm); if(ImageInfo.mediaManufacturer != "") DicConsole.DebugWriteLine("CloneCD plugin", "Disc manufactured by: {0}", ImageInfo.mediaManufacturer); } break; } break; case 6: { uint id = (uint)((descriptor.Min << 16) + (descriptor.Sec << 8) + descriptor.Frame); DicConsole.DebugWriteLine("CloneCD plugin", "Disc ID: {0:X6}", id & 0x00FFFFFF); ImageInfo.mediaSerialNumber = string.Format("{0:X6}", id & 0x00FFFFFF); break; } } } if(!firstTrackInSession) { currentTrack.TrackEndSector = LeadOutStart - 1; tracks.Add(currentTrack); } if(subFilter != null && !ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubchannel)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubchannel); sessions = new List(); ImagePlugins.Session currentSession = new ImagePlugins.Session(); currentSession.EndTrack = uint.MinValue; currentSession.StartTrack = uint.MaxValue; currentSession.SessionSequence = 1; partitions = new List(); offsetmap = new Dictionary(); foreach(Track track in tracks) { if(track.TrackSession == currentSession.SessionSequence) { if(track.TrackSequence > currentSession.EndTrack) { currentSession.EndSector = track.TrackEndSector; currentSession.EndTrack = track.TrackSequence; } if(track.TrackSequence < currentSession.StartTrack) { currentSession.StartSector = track.TrackStartSector; currentSession.StartTrack = track.TrackSequence; } } else { sessions.Add(currentSession); currentSession = new ImagePlugins.Session(); currentSession.EndTrack = uint.MinValue; currentSession.StartTrack = uint.MaxValue; currentSession.SessionSequence = track.TrackSession; } Partition partition = new Partition(); partition.PartitionDescription = track.TrackDescription; partition.PartitionLength = (track.TrackEndSector - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector; partition.PartitionSectors = (track.TrackEndSector - track.TrackStartSector); ImageInfo.sectors += partition.PartitionSectors; partition.PartitionSequence = track.TrackSequence; partition.PartitionStart = track.TrackFileOffset; partition.PartitionStartSector = track.TrackStartSector; partition.PartitionType = track.TrackType.ToString(); partitions.Add(partition); offsetmap.Add(track.TrackSequence, track.TrackStartSector); } bool data = false; bool mode2 = false; bool firstaudio = false; bool firstdata = false; bool audio = false; for(int i = 0; i < tracks.Count; i++) { // First track is audio firstaudio |= i == 0 && tracks[i].TrackType == TrackType.Audio; // First track is data firstdata |= i == 0 && tracks[i].TrackType != TrackType.Audio; // Any non first track is data data |= i != 0 && tracks[i].TrackType != TrackType.Audio; // Any non first track is audio audio |= i != 0 && tracks[i].TrackType == TrackType.Audio; switch(tracks[i].TrackType) { case TrackType.CDMode2Form1: case TrackType.CDMode2Form2: case TrackType.CDMode2Formless: mode2 = true; break; } } // TODO: Check format cdtext = cdtMs.ToArray(); if(!data && !firstdata) ImageInfo.mediaType = MediaType.CDDA; else if(firstaudio && data && sessions.Count > 1 && mode2) ImageInfo.mediaType = MediaType.CDPLUS; else if((firstdata && audio) || mode2) ImageInfo.mediaType = MediaType.CDROMXA; else if(!audio) ImageInfo.mediaType = MediaType.CDROM; else ImageInfo.mediaType = MediaType.CD; ImageInfo.imageApplication = "CloneCD"; ImageInfo.imageSize = (ulong)imageFilter.GetDataForkLength(); ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; return true; } catch(Exception ex) { DicConsole.ErrorWriteLine("Exception trying to identify image file {0}", imageFilter.GetFilename()); DicConsole.ErrorWriteLine("Exception: {0}", ex.Message); DicConsole.ErrorWriteLine("Stack trace: {0}", ex.StackTrace); return false; } } static ulong GetLBA(int hour, int minute, int second, int frame) { return (ulong)((hour * 60 * 60 * 75) + (minute * 60 * 75) + (second * 75) + frame - 150); } 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_FullTOC: { return fulltoc; } case MediaTagType.CD_TEXT: { if(cdtext != null && cdtext.Length > 0) return cdtext; throw new FeatureNotPresentImageException("Image does not contain CD-TEXT 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 kvp in offsetmap) { if(sectorAddress >= kvp.Value) { foreach(Track _track in tracks) { if(_track.TrackSequence == kvp.Key) { if(sectorAddress < _track.TrackEndSector) 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 kvp in offsetmap) { if(sectorAddress >= kvp.Value) { foreach(Track _track in tracks) { if(_track.TrackSequence == kvp.Key) { if(sectorAddress < _track.TrackEndSector) 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) { Track _track = new Track(); _track.TrackSequence = 0; foreach(Track __track in tracks) { if(__track.TrackSequence == track) { _track = __track; break; } } if(_track.TrackSequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length + sectorAddress > (_track.TrackEndSector)) throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length + sectorAddress, _track.TrackEndSector)); uint sector_offset; uint sector_size; uint sector_skip; switch(_track.TrackType) { case TrackType.Audio: { sector_offset = 0; sector_size = 2352; sector_skip = 0; break; } case TrackType.CDMode1: { sector_offset = 16; sector_size = 2048; sector_skip = 288; break; } case TrackType.CDMode2Formless: { sector_offset = 16; sector_size = 2336; sector_skip = 0; break; } case TrackType.CDMode2Form1: { sector_offset = 24; sector_size = 2048; sector_skip = 280; break; } case TrackType.CDMode2Form2: { sector_offset = 24; sector_size = 2324; sector_skip = 4; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } byte[] buffer = new byte[sector_size * length]; dataStream.Seek((long)(_track.TrackFileOffset + sectorAddress * 2352), SeekOrigin.Begin); if(sector_offset == 0 && sector_skip == 0) dataStream.Read(buffer, 0, buffer.Length); else { for(int i = 0; i < length; i++) { byte[] sector = new byte[sector_size]; dataStream.Seek(sector_offset, SeekOrigin.Current); dataStream.Read(sector, 0, sector.Length); dataStream.Seek(sector_skip, SeekOrigin.Current); Array.Copy(sector, 0, buffer, i * sector_size, sector_size); } } return buffer; } // TODO: Flags public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) { Track _track = new Track(); _track.TrackSequence = 0; foreach(Track __track in tracks) { if(__track.TrackSequence == track) { _track = __track; break; } } if(_track.TrackSequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length + sectorAddress > (_track.TrackEndSector)) throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length + sectorAddress, _track.TrackEndSector)); if(_track.TrackType == TrackType.Data) throw new ArgumentException("Unsupported tag requested", nameof(tag)); byte[] buffer; switch(tag) { case SectorTagType.CDSectorECC: case SectorTagType.CDSectorECC_P: case SectorTagType.CDSectorECC_Q: case SectorTagType.CDSectorEDC: case SectorTagType.CDSectorHeader: case SectorTagType.CDSectorSubHeader: case SectorTagType.CDSectorSync: break; case SectorTagType.CDSectorSubchannel: buffer = new byte[96 * length]; subStream.Seek((long)(_track.TrackSubchannelOffset + sectorAddress * 96), SeekOrigin.Begin); subStream.Read(buffer, 0, buffer.Length); break; default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } uint sector_offset; uint sector_size; uint sector_skip; switch(_track.TrackType) { case TrackType.CDMode1: 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.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 TrackType.CDMode2Formless: { switch(tag) { case SectorTagType.CDSectorSync: case SectorTagType.CDSectorHeader: 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 TrackType.CDMode2Form1: 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.CDSectorSubHeader: { sector_offset = 16; sector_size = 8; sector_skip = 2328; break; } 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 = 2072; sector_size = 4; sector_skip = 276; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } break; case TrackType.CDMode2Form2: 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.CDSectorSubHeader: { sector_offset = 16; sector_size = 8; sector_skip = 2328; break; } case SectorTagType.CDSectorEDC: { sector_offset = 2348; sector_size = 4; sector_skip = 0; break; } default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } break; case TrackType.Audio: { throw new ArgumentException("Unsupported tag requested", nameof(tag)); } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } buffer = new byte[sector_size * length]; dataStream.Seek((long)(_track.TrackFileOffset + sectorAddress * 2352), SeekOrigin.Begin); if(sector_offset == 0 && sector_skip == 0) dataStream.Read(buffer, 0, buffer.Length); else { for(int i = 0; i < length; i++) { byte[] sector = new byte[sector_size]; dataStream.Seek(sector_offset, SeekOrigin.Current); dataStream.Read(sector, 0, sector.Length); dataStream.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 kvp in offsetmap) { if(sectorAddress >= kvp.Value) { foreach(Track track in tracks) { 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) { Track _track = new Track(); _track.TrackSequence = 0; foreach(Track __track in tracks) { if(__track.TrackSequence == track) { _track = __track; break; } } if(_track.TrackSequence == 0) throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); if(length + sectorAddress > (_track.TrackEndSector)) throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than present in track ({1}), won't cross tracks", length + sectorAddress, _track.TrackEndSector)); byte[] buffer = new byte[2352 * length]; dataStream.Seek((long)(_track.TrackFileOffset + sectorAddress * 2352), SeekOrigin.Begin); dataStream.Read(buffer, 0, buffer.Length); return buffer; } public override string GetImageFormat() { return "CloneCD"; } public override string GetImageVersion() { return ImageInfo.imageVersion; } public override string GetImageApplication() { return ImageInfo.imageApplication; } public override string GetImageApplicationVersion() { return ImageInfo.imageApplicationVersion; } public override string GetImageCreator() { return ImageInfo.imageCreator; } public override DateTime GetImageCreationTime() { return ImageInfo.imageCreationTime; } public override DateTime GetImageLastModificationTime() { return ImageInfo.imageLastModificationTime; } public override string GetImageName() { return ImageInfo.imageName; } public override string GetImageComments() { return ImageInfo.imageComments; } public override string GetMediaManufacturer() { return ImageInfo.mediaManufacturer; } public override string GetMediaModel() { return ImageInfo.mediaModel; } public override string GetMediaSerialNumber() { return ImageInfo.driveSerialNumber; } public override string GetMediaBarcode() { return ImageInfo.mediaBarcode; } public override string GetMediaPartNumber() { return ImageInfo.mediaPartNumber; } public override MediaType GetMediaType() { return ImageInfo.mediaType; } 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 List GetPartitions() { return partitions; } public override List GetTracks() { return tracks; } public override List GetSessionTracks(ImagePlugins.Session session) { if(sessions.Contains(session)) { return GetSessionTracks(session.SessionSequence); } throw new ImageNotSupportedException("Session does not exist in disc image"); } public override List GetSessionTracks(ushort session) { List _tracks = new List(); foreach(Track _track in tracks) { if(_track.TrackSession == session) _tracks.Add(_track); } return _tracks; } public override List GetSessions() { return sessions; } 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 FailingLBAs, out List UnknownLBAs) { byte[] buffer = ReadSectorsLong(sectorAddress, length); int bps = (int)(buffer.Length / length); byte[] sector = new byte[bps]; FailingLBAs = new List(); UnknownLBAs = new List(); 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 FailingLBAs, out List UnknownLBAs) { byte[] buffer = ReadSectorsLong(sectorAddress, length, track); int bps = (int)(buffer.Length / length); byte[] sector = new byte[bps]; FailingLBAs = new List(); UnknownLBAs = new List(); 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; } } }