// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : BlindWrite5.cs // Author(s) : Natalia Portillo // // Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // // Manages BlindWrite 5 disc images. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2017 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using DiscImageChef.CommonTypes; using DiscImageChef.Console; using DiscImageChef.Decoders.SCSI.MMC; using System.Text; using System.Globalization; using DiscImageChef.Filters; namespace DiscImageChef.ImagePlugins { public class BlindWrite5 : ImagePlugin { #region Internal Constants /// "BWT5 STREAM SIGN" readonly byte[] BW5_Signature = { 0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x53, 0x49, 0x47, 0x4E }; /// "BWT5 STREAM FOOT" readonly byte[] BW5_Footer = { 0x42, 0x57, 0x54, 0x35, 0x20, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4D, 0x20, 0x46, 0x4F, 0x4F, 0x54 }; #endregion Internal Constants #region Internal enumerations enum BW5_TrackType : byte { NotData = 0, Audio = 1, Mode1 = 2, Mode2 = 3, Mode2F1 = 4, Mode2F2 = 5, DVD = 6 } enum BW5_TrackSubchannel : byte { None = 0, Q16 = 2, Linear = 4, } #endregion Internal enumerations #region Internal Structures [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BW5_Header { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] signature; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public uint[] unknown1; public ProfileNumber profile; public ushort sessions; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public uint[] unknown2; [MarshalAs(UnmanagedType.U1, SizeConst = 3)] public bool mcnIsValid; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] public byte[] mcn; public ushort unknown3; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public uint[] unknown4; public ushort pmaLen; public ushort atipLen; public ushort cdtLen; public ushort cdInfoLen; public uint bcaLen; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public uint[] unknown5; public uint dvdStrLen; public uint dvdInfoLen; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] unknown6; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] manufacturer; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] product; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] revision; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] vendor; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] volumeId; public uint mode2ALen; public uint unkBlkLen; public uint dataLen; public uint sessionsLen; public uint dpmLen; } struct BW5_DataFile { public uint type; public uint length; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public uint[] unknown1; public uint offset; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] unknown2; public int startLba; public int sectors; public uint filenameLen; public byte[] filenameBytes; public uint unknown3; public string filename; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BW5_TrackDescriptor { public BW5_TrackType type; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] unknown1; public uint unknown2; public BW5_TrackSubchannel subchannel; public byte unknown3; public byte ctl; public byte adr; public byte point; public byte unknown4; public byte min; public byte sec; public byte frame; public byte zero; public byte pmin; public byte psec; public byte pframe; public byte unknown5; public uint pregap; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public uint[] unknown6; public int startLba; public int sectors; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] unknown7; public uint session; public ushort unknown8; // Seems to be only on non DVD track descriptors [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] unknown9; } struct BW5_SessionDescriptor { public ushort sequence; public byte entries; public byte unknown; public int start; public int end; public ushort firstTrack; public ushort lastTrack; public BW5_TrackDescriptor[] tracks; } struct DataFileCharacteristics { public Filter fileFilter; public string filePath; public TrackSubchannelType subchannel; public long sectorSize; public int startLba; public int sectors; } #endregion Internal Structures #region Internal variables BW5_Header header; byte[] mode2A; byte[] unkBlock; byte[] pma; byte[] atip; byte[] cdtext; byte[] bca; byte[] dmi; byte[] pfi; byte[] discInformation; string dataPath; List dataFiles; List bwSessions; byte[] dpm; List sessions; List tracks; List partitions; List filePaths; byte[] fullToc; Dictionary offsetmap; Dictionary trackFlags; Stream imageStream; #endregion Internal variables #region Public Methods public BlindWrite5() { Name = "BlindWrite 5"; PluginUUID = new Guid("9CB7A381-0509-4F9F-B801-3F65434BC3EE"); 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) { Stream stream = imageFilter.GetDataForkStream(); stream.Seek(0, SeekOrigin.Begin); if(stream.Length < 276) return false; byte[] signature = new byte[16]; stream.Read(signature, 0, 16); byte[] footer = new byte[16]; stream.Seek(-16, SeekOrigin.End); stream.Read(footer, 0, 16); return BW5_Signature.SequenceEqual(signature) && BW5_Footer.SequenceEqual(footer); } public override bool OpenImage(Filter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); stream.Seek(0, SeekOrigin.Begin); if(stream.Length < 276) return false; byte[] hdr = new byte[260]; stream.Read(hdr, 0, 260); header = new BW5_Header(); IntPtr hdrPtr = Marshal.AllocHGlobal(260); Marshal.Copy(hdr, 0, hdrPtr, 260); header = (BW5_Header)Marshal.PtrToStructure(hdrPtr, typeof(BW5_Header)); Marshal.FreeHGlobal(hdrPtr); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.signature = {0}", StringHandlers.CToString(header.signature)); for(int i = 0; i < header.unknown1.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown1[{1}] = 0x{0:X8}", header.unknown1[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.profile = {0}", header.profile); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.sessions = {0}", header.sessions); for(int i = 0; i < header.unknown2.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown2[{1}] = 0x{0:X8}", header.unknown2[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.mcnIsValid = {0}", header.mcnIsValid); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.mcn = {0}", StringHandlers.CToString(header.mcn)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown3 = 0x{0:X4}", header.unknown3); for(int i = 0; i < header.unknown4.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown4[{1}] = 0x{0:X8}", header.unknown4[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.pmaLen = {0}", header.pmaLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.atipLen = {0}", header.atipLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.cdtLen = {0}", header.cdtLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.cdInfoLen = {0}", header.cdInfoLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.bcaLen = {0}", header.bcaLen); for(int i = 0; i < header.unknown5.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown5[{1}] = 0x{0:X8}", header.unknown5[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.dvdStrLen = {0}", header.dvdStrLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.dvdInfoLen = {0}", header.dvdInfoLen); for(int i = 0; i < header.unknown6.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unknown6[{1}] = 0x{0:X2}", header.unknown6[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.manufacturer = {0}", StringHandlers.CToString(header.manufacturer)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.product = {0}", StringHandlers.CToString(header.product)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.revision = {0}", StringHandlers.CToString(header.revision)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.vendor = {0}", StringHandlers.CToString(header.vendor)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.volumeId = {0}", StringHandlers.CToString(header.volumeId)); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.mode2ALen = {0}", header.mode2ALen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.unkBlkLen = {0}", header.unkBlkLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.dataLen = {0}", header.dataLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.sessionsLen = {0}", header.sessionsLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "header.dpmLen = {0}", header.dpmLen); mode2A = new byte[header.mode2ALen]; if(mode2A.Length > 0) { stream.Read(mode2A, 0, mode2A.Length); mode2A[1] -= 2; Decoders.SCSI.Modes.ModePage_2A? decoded2A = Decoders.SCSI.Modes.DecodeModePage_2A(mode2A); if(decoded2A.HasValue) DicConsole.DebugWriteLine("BlindWrite5 plugin", "mode page 2A: {0}", Decoders.SCSI.Modes.PrettifyModePage_2A(decoded2A)); else mode2A = null; } unkBlock = new byte[header.unkBlkLen]; if(unkBlock.Length > 0) stream.Read(unkBlock, 0, unkBlock.Length); byte[] temp = new byte[header.pmaLen]; if(temp.Length > 0) { byte[] tushort = BitConverter.GetBytes((ushort)(temp.Length + 2)); stream.Read(temp, 0, temp.Length); pma = new byte[temp.Length + 4]; pma[0] = tushort[1]; pma[1] = tushort[0]; Array.Copy(temp, 0, pma, 4, temp.Length); Decoders.CD.PMA.CDPMA? decodedPma = Decoders.CD.PMA.Decode(pma); if(decodedPma.HasValue) DicConsole.DebugWriteLine("BlindWrite5 plugin", "PMA: {0}", Decoders.CD.PMA.Prettify(decodedPma)); else pma = null; } temp = new byte[header.atipLen]; if(temp.Length > 0) { byte[] tushort = BitConverter.GetBytes((ushort)(temp.Length + 2)); stream.Read(temp, 0, temp.Length); atip = new byte[temp.Length + 4]; atip[0] = tushort[1]; atip[1] = tushort[0]; Array.Copy(temp, 0, atip, 4, temp.Length); Decoders.CD.ATIP.CDATIP? decodedAtip = Decoders.CD.ATIP.Decode(atip); if(decodedAtip.HasValue) DicConsole.DebugWriteLine("BlindWrite5 plugin", "ATIP: {0}", Decoders.CD.ATIP.Prettify(decodedAtip)); else atip = null; } temp = new byte[header.cdtLen]; if(temp.Length > 0) { byte[] tushort = BitConverter.GetBytes((ushort)(temp.Length + 2)); stream.Read(temp, 0, temp.Length); cdtext = new byte[temp.Length + 4]; cdtext[0] = tushort[1]; cdtext[1] = tushort[0]; Array.Copy(temp, 0, cdtext, 4, temp.Length); Decoders.CD.CDTextOnLeadIn.CDText? decodedCdText = Decoders.CD.CDTextOnLeadIn.Decode(cdtext); if(decodedCdText.HasValue) DicConsole.DebugWriteLine("BlindWrite5 plugin", "CD-Text: {0}", Decoders.CD.CDTextOnLeadIn.Prettify(decodedCdText)); else cdtext = null; } bca = new byte[header.bcaLen]; if(bca.Length > 0) stream.Read(bca, 0, bca.Length); else bca = null; temp = new byte[header.dvdStrLen]; if(temp.Length > 0) { stream.Read(temp, 0, temp.Length); dmi = new byte[2052]; pfi = new byte[2052]; Array.Copy(temp, 0, dmi, 0, 2050); Array.Copy(temp, 0x802, pfi, 4, 2048); pfi[0] = 0x08; pfi[1] = 0x02; dmi[0] = 0x08; dmi[1] = 0x02; Decoders.DVD.PFI.PhysicalFormatInformation? decodedPfi = Decoders.DVD.PFI.Decode(pfi); if(decodedPfi.HasValue) DicConsole.DebugWriteLine("BlindWrite5 plugin", "PFI: {0}", Decoders.DVD.PFI.Prettify(decodedPfi)); else { pfi = null; dmi = null; } } switch(header.profile) { case ProfileNumber.CDR: case ProfileNumber.CDROM: case ProfileNumber.CDRW: case ProfileNumber.DDCDR: case ProfileNumber.DDCDROM: case ProfileNumber.DDCDRW: case ProfileNumber.HDBURNROM: case ProfileNumber.HDBURNR: case ProfileNumber.HDBURNRW: discInformation = new byte[header.cdInfoLen]; break; default: discInformation = new byte[header.dvdInfoLen]; break; } if(discInformation.Length > 0) { stream.Read(discInformation, 0, discInformation.Length); DicConsole.DebugWriteLine("BlindWrite5 plugin", "Disc information: {0}", PrintHex.ByteArrayToHexArrayString(discInformation, 40)); } else discInformation = null; // How many data blocks byte[] tmpArray = new byte[4]; stream.Read(tmpArray, 0, tmpArray.Length); uint dataBlockCount = BitConverter.ToUInt32(tmpArray, 0); stream.Read(tmpArray, 0, tmpArray.Length); uint dataPathLen = BitConverter.ToUInt32(tmpArray, 0); byte[] dataPathBytes = new byte[dataPathLen]; stream.Read(dataPathBytes, 0, dataPathBytes.Length); dataPath = Encoding.Unicode.GetString(dataPathBytes); DicConsole.DebugWriteLine("BlindWrite5 plugin", "Data path: {0}", dataPath); dataFiles = new List(); for(int cD = 0; cD < dataBlockCount; cD++) { tmpArray = new byte[52]; BW5_DataFile dataFile = new BW5_DataFile(); dataFile.unknown1 = new uint[4]; dataFile.unknown2 = new uint[3]; stream.Read(tmpArray, 0, tmpArray.Length); dataFile.type = BitConverter.ToUInt32(tmpArray, 0); dataFile.length = BitConverter.ToUInt32(tmpArray, 4); dataFile.unknown1[0] = BitConverter.ToUInt32(tmpArray, 8); dataFile.unknown1[1] = BitConverter.ToUInt32(tmpArray, 12); dataFile.unknown1[2] = BitConverter.ToUInt32(tmpArray, 16); dataFile.unknown1[3] = BitConverter.ToUInt32(tmpArray, 20); dataFile.offset = BitConverter.ToUInt32(tmpArray, 24); dataFile.unknown2[0] = BitConverter.ToUInt32(tmpArray, 28); dataFile.unknown2[1] = BitConverter.ToUInt32(tmpArray, 32); dataFile.unknown2[2] = BitConverter.ToUInt32(tmpArray, 36); dataFile.startLba = BitConverter.ToInt32(tmpArray, 40); dataFile.sectors = BitConverter.ToInt32(tmpArray, 44); dataFile.filenameLen = BitConverter.ToUInt32(tmpArray, 48); dataFile.filenameBytes = new byte[dataFile.filenameLen]; tmpArray = new byte[dataFile.filenameLen]; stream.Read(tmpArray, 0, tmpArray.Length); dataFile.filenameBytes = tmpArray; tmpArray = new byte[4]; stream.Read(tmpArray, 0, tmpArray.Length); dataFile.unknown3 = BitConverter.ToUInt32(tmpArray, 0); dataFile.filename = Encoding.Unicode.GetString(dataFile.filenameBytes); dataFiles.Add(dataFile); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.type = 0x{0:X8}", dataFile.type); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.length = {0}", dataFile.length); for(int i = 0; i < dataFile.unknown1.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.unknown1[{1}] = {0}", dataFile.unknown1[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.offset = {0}", dataFile.offset); for(int i = 0; i < dataFile.unknown2.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.unknown2[{1}] = {0}", dataFile.unknown2[i], i); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.startLba = {0}", dataFile.startLba); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.sectors = {0}", dataFile.sectors); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.filenameLen = {0}", dataFile.filenameLen); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.filename = {0}", dataFile.filename); DicConsole.DebugWriteLine("BlindWrite5 plugin", "dataFile.unknown3 = {0}", dataFile.unknown3); } bwSessions = new List(); for(int ses = 0; ses < header.sessions; ses++) { BW5_SessionDescriptor session = new BW5_SessionDescriptor(); tmpArray = new byte[16]; stream.Read(tmpArray, 0, tmpArray.Length); session.sequence = BitConverter.ToUInt16(tmpArray, 0); session.entries = tmpArray[2]; session.unknown = tmpArray[3]; session.start = BitConverter.ToInt32(tmpArray, 4); session.end = BitConverter.ToInt32(tmpArray, 8); session.firstTrack = BitConverter.ToUInt16(tmpArray, 12); session.lastTrack = BitConverter.ToUInt16(tmpArray, 14); session.tracks = new BW5_TrackDescriptor[session.entries]; DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].filename = {1}", ses, session.sequence); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].entries = {1}", ses, session.entries); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].unknown = {1}", ses, session.unknown); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].start = {1}", ses, session.start); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].end = {1}", ses, session.end); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].firstTrack = {1}", ses, session.firstTrack); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].lastTrack = {1}", ses, session.lastTrack); for(int tSeq = 0; tSeq < session.entries; tSeq++) { byte[] trk = new byte[72]; stream.Read(trk, 0, 72); session.tracks[tSeq] = new BW5_TrackDescriptor(); IntPtr trkPtr = Marshal.AllocHGlobal(72); Marshal.Copy(trk, 0, trkPtr, 72); session.tracks[tSeq] = (BW5_TrackDescriptor)Marshal.PtrToStructure(trkPtr, typeof(BW5_TrackDescriptor)); Marshal.FreeHGlobal(trkPtr); if(session.tracks[tSeq].type == BW5_TrackType.DVD || session.tracks[tSeq].type == BW5_TrackType.NotData) { session.tracks[tSeq].unknown9[0] = 0; session.tracks[tSeq].unknown9[1] = 0; stream.Seek(-8, SeekOrigin.Current); } DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].type = {2}", ses, tSeq, session.tracks[tSeq].type); for(int i = 0; i < session.tracks[tSeq].unknown1.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown1[{2}] = 0x{3:X2}", ses, tSeq, i, session.tracks[tSeq].unknown1[i]); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown2 = 0x{2:X8}", ses, tSeq, session.tracks[tSeq].unknown2); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].subchannel = {2}", ses, tSeq, session.tracks[tSeq].subchannel); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown3 = 0x{2:X2}", ses, tSeq, session.tracks[tSeq].unknown3); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].ctl = {2}", ses, tSeq, session.tracks[tSeq].ctl); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].adr = {2}", ses, tSeq, session.tracks[tSeq].adr); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].point = {2}", ses, tSeq, session.tracks[tSeq].point); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown4 = 0x{2:X2}", ses, tSeq, session.tracks[tSeq].unknown4); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].min = {2}", ses, tSeq, session.tracks[tSeq].min); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].sec = {2}", ses, tSeq, session.tracks[tSeq].sec); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].frame = {2}", ses, tSeq, session.tracks[tSeq].frame); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].zero = {2}", ses, tSeq, session.tracks[tSeq].zero); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].pmin = {2}", ses, tSeq, session.tracks[tSeq].pmin); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].psec = {2}", ses, tSeq, session.tracks[tSeq].psec); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].pframe = {2}", ses, tSeq, session.tracks[tSeq].pframe); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown5 = 0x{2:X2}", ses, tSeq, session.tracks[tSeq].unknown5); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].pregap = {2}", ses, tSeq, session.tracks[tSeq].pregap); for(int i = 0; i < session.tracks[tSeq].unknown6.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown6[{2}] = 0x{3:X8}", ses, tSeq, i, session.tracks[tSeq].unknown6[i]); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].startLba = {2}", ses, tSeq, session.tracks[tSeq].startLba); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].sectors = {2}", ses, tSeq, session.tracks[tSeq].sectors); for(int i = 0; i < session.tracks[tSeq].unknown7.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown7[{2}] = 0x{3:X8}", ses, tSeq, i, session.tracks[tSeq].unknown7[i]); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].session = {2}", ses, tSeq, session.tracks[tSeq].session); DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown8 = 0x{2:X4}", ses, tSeq, session.tracks[tSeq].unknown8); if(session.tracks[tSeq].type != BW5_TrackType.DVD && session.tracks[tSeq].type != BW5_TrackType.NotData) { for(int i = 0; i < session.tracks[tSeq].unknown9.Length; i++) DicConsole.DebugWriteLine("BlindWrite5 plugin", "session[{0}].track[{1}].unknown9[{2}] = 0x{3:X8}", ses, tSeq, i, session.tracks[tSeq].unknown9[i]); } } bwSessions.Add(session); } dpm = new byte[header.dpmLen]; stream.Read(dpm, 0, dpm.Length); // Unused tmpArray = new byte[4]; stream.Read(tmpArray, 0, tmpArray.Length); byte[] footer = new byte[16]; stream.Read(footer, 0, footer.Length); if(BW5_Footer.SequenceEqual(footer)) DicConsole.DebugWriteLine("BlindWrite5 plugin", "Correctly arrived end of image"); else DicConsole.ErrorWriteLine("BlindWrite5 image ends after expected position. Probably new version with different data. Errors may occur."); FiltersList filtersList; filePaths = new List(); foreach(BW5_DataFile dataFile in dataFiles) { DataFileCharacteristics chars = new DataFileCharacteristics(); string path = Path.Combine(dataPath, dataFile.filename); filtersList = new FiltersList(); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)) != null) { chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)); chars.filePath = path; } else { path = Path.Combine(dataPath, dataFile.filename.ToLower(CultureInfo.CurrentCulture)); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)) != null) { chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)); chars.filePath = path; } else { path = Path.Combine(dataPath, dataFile.filename.ToUpper(CultureInfo.CurrentCulture)); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)) != null) { chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)); chars.filePath = path; } else { path = Path.Combine(dataPath.ToLower(CultureInfo.CurrentCulture), dataFile.filename); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)) != null) { chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)); chars.filePath = path; } else { path = Path.Combine(dataPath.ToUpper(CultureInfo.CurrentCulture), dataFile.filename); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)) != null) { chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path)); chars.filePath = path; } else { path = Path.Combine(dataPath, dataFile.filename); if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path.ToLower(CultureInfo.CurrentCulture))) != null) { chars.filePath = path.ToLower(CultureInfo.CurrentCulture); chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path.ToLower(CultureInfo.CurrentCulture))); } else if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path.ToUpper(CultureInfo.CurrentCulture))) != null) { chars.filePath = path.ToUpper(CultureInfo.CurrentCulture); chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), path.ToUpper(CultureInfo.CurrentCulture))); } else if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename.ToLower(CultureInfo.CurrentCulture))) != null) { chars.filePath = dataFile.filename.ToLower(CultureInfo.CurrentCulture); chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename.ToLower(CultureInfo.CurrentCulture))); } else if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename.ToUpper(CultureInfo.CurrentCulture))) != null) { chars.filePath = dataFile.filename.ToUpper(CultureInfo.CurrentCulture); chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename.ToUpper(CultureInfo.CurrentCulture))); } else if(filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename)) != null) { chars.filePath = dataFile.filename; chars.fileFilter = filtersList.GetFilter(Path.Combine(imageFilter.GetParentFolder(), dataFile.filename)); } else { DicConsole.ErrorWriteLine("Cannot find data file {0}", dataFile.filename); return false; } } } } } } long sectorSize = dataFile.length / dataFile.sectors; if(sectorSize > 2352) { if((sectorSize - 2352) == 16) chars.subchannel = TrackSubchannelType.Q16Interleaved; else if((sectorSize - 2352) == 96) chars.subchannel = TrackSubchannelType.PackedInterleaved; else { DicConsole.ErrorWriteLine("BlindWrite5 found unknown subchannel size: {0}", sectorSize - 2352); return false; } } else chars.subchannel = TrackSubchannelType.None; chars.sectorSize = sectorSize; chars.startLba = dataFile.startLba; chars.sectors = dataFile.sectors; filePaths.Add(chars); } sessions = new List(); tracks = new List(); partitions = new List(); MemoryStream fullTocStream = new MemoryStream(); fullTocStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); ulong offsetBytes = 0; offsetmap = new Dictionary(); bool isDvd = false; byte firstSession = byte.MaxValue; byte lastSession = 0; trackFlags = new Dictionary(); ImageInfo.sectors = 0; DicConsole.DebugWriteLine("BlindWrite5 plugin", "Building maps"); foreach(BW5_SessionDescriptor ses in bwSessions) { Session session = new Session(); session.SessionSequence = ses.sequence; if(ses.start < 0) session.StartSector = 0; else session.StartSector = (ulong)ses.start; session.EndSector = (ulong)ses.end; session.StartTrack = ses.firstTrack; session.EndTrack = ses.lastTrack; if(ses.sequence < firstSession) firstSession = (byte)ses.sequence; if(ses.sequence > lastSession) lastSession = (byte)ses.sequence; foreach(BW5_TrackDescriptor trk in ses.tracks) { byte adrCtl = (byte)((trk.adr << 4) + trk.ctl); fullTocStream.WriteByte((byte)trk.session); fullTocStream.WriteByte(adrCtl); fullTocStream.WriteByte(0x00); fullTocStream.WriteByte(trk.point); fullTocStream.WriteByte(trk.min); fullTocStream.WriteByte(trk.sec); fullTocStream.WriteByte(trk.frame); fullTocStream.WriteByte(trk.zero); fullTocStream.WriteByte(trk.pmin); fullTocStream.WriteByte(trk.psec); fullTocStream.WriteByte(trk.pframe); if(trk.point < 0xA0) { Track track = new Track(); Partition partition = new Partition(); trackFlags.Add(trk.point, trk.ctl); switch(trk.type) { case BW5_TrackType.Audio: track.TrackBytesPerSector = 2352; track.TrackRawBytesPerSector = 2352; if(ImageInfo.sectorSize < 2352) ImageInfo.sectorSize = 2352; break; case BW5_TrackType.Mode1: case BW5_TrackType.Mode2F1: if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorECC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorECC); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.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); track.TrackBytesPerSector = 2048; track.TrackRawBytesPerSector = 2352; if(ImageInfo.sectorSize < 2048) ImageInfo.sectorSize = 2048; break; case BW5_TrackType.Mode2: if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); track.TrackBytesPerSector = 2336; track.TrackRawBytesPerSector = 2352; if(ImageInfo.sectorSize < 2336) ImageInfo.sectorSize = 2336; break; case BW5_TrackType.Mode2F2: if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubHeader)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubHeader); if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorEDC)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorEDC); track.TrackBytesPerSector = 2336; track.TrackRawBytesPerSector = 2352; if(ImageInfo.sectorSize < 2324) ImageInfo.sectorSize = 2324; break; case BW5_TrackType.DVD: track.TrackBytesPerSector = 2048; track.TrackRawBytesPerSector = 2048; if(ImageInfo.sectorSize < 2048) ImageInfo.sectorSize = 2048; isDvd = true; break; } track.TrackDescription = string.Format("Track {0}", trk.point); track.TrackStartSector = (ulong)(trk.startLba + trk.pregap); track.TrackEndSector = (ulong)(trk.sectors + trk.startLba); foreach(DataFileCharacteristics chars in filePaths) { if(trk.startLba >= chars.startLba && (trk.startLba + trk.sectors) <= (chars.startLba + chars.sectors)) { track.TrackFilter = chars.fileFilter; track.TrackFile = chars.fileFilter.GetFilename(); if(trk.startLba >= 0) track.TrackFileOffset = (ulong)((trk.startLba - chars.startLba) * chars.sectorSize); else track.TrackFileOffset = (ulong)((trk.startLba * -1) * chars.sectorSize); track.TrackFileType = "BINARY"; if(chars.subchannel != TrackSubchannelType.None) { track.TrackSubchannelFilter = track.TrackFilter; track.TrackSubchannelFile = track.TrackFile; track.TrackSubchannelType = chars.subchannel; track.TrackSubchannelOffset = track.TrackFileOffset; if(chars.subchannel == TrackSubchannelType.PackedInterleaved) { if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubchannel)) ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubchannel); } } break; } } track.TrackPregap = trk.pregap; track.TrackSequence = trk.point; track.TrackType = BlindWriteTrackTypeToTrackType(trk.type); track.Indexes = new Dictionary(); track.Indexes.Add(1, track.TrackStartSector); partition.Description = track.TrackDescription; partition.Size = (track.TrackEndSector - track.TrackStartSector) * (ulong)track.TrackRawBytesPerSector; partition.Length = (track.TrackEndSector - track.TrackStartSector); partition.Sequence = track.TrackSequence; partition.Offset = offsetBytes; partition.Start = track.TrackStartSector; partition.Type = track.TrackType.ToString(); offsetBytes += partition.Size; tracks.Add(track); partitions.Add(partition); offsetmap.Add(track.TrackSequence, track.TrackStartSector); ImageInfo.sectors += partition.Length; } } } DicConsole.DebugWriteLine("BlindWrite5 plugin", "printing track map"); foreach(Track track in tracks) { DicConsole.DebugWriteLine("BlindWrite5 plugin", "Partition sequence: {0}", track.TrackSequence); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition description: {0}", track.TrackDescription); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition type: {0}", track.TrackType); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition starting sector: {0}", track.TrackStartSector); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition ending sector: {0}", track.TrackEndSector); } DicConsole.DebugWriteLine("BlindWrite5 plugin", "printing partition map"); foreach(Partition partition in partitions) { DicConsole.DebugWriteLine("BlindWrite5 plugin", "Partition sequence: {0}", partition.Sequence); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition name: {0}", partition.Name); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition description: {0}", partition.Description); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition type: {0}", partition.Type); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition starting sector: {0}", partition.Start); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition sectors: {0}", partition.Length); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition starting offset: {0}", partition.Offset); DicConsole.DebugWriteLine("BlindWrite5 plugin", "\tPartition size in bytes: {0}", partition.Size); } if(!isDvd) { DicConsole.DebugWriteLine("BlindWrite5 plugin", "Rebuilding TOC"); fullToc = fullTocStream.ToArray(); DicConsole.DebugWriteLine("BlindWrite5 plugin", "TOC len {0}", fullToc.Length); byte[] fullTocSize = BitConverter.GetBytes((short)(fullToc.Length - 2)); fullToc[0] = fullTocSize[1]; fullToc[1] = fullTocSize[0]; fullToc[2] = firstSession; fullToc[3] = lastSession; Decoders.CD.FullTOC.CDFullTOC? decodedFullToc = Decoders.CD.FullTOC.Decode(fullToc); if(!decodedFullToc.HasValue) { DicConsole.DebugWriteLine("BlindWrite5 plugin", "TOC not correctly rebuilt"); fullToc = null; } else { DicConsole.DebugWriteLine("BlindWrite5 plugin", "TOC correctly rebuilt"); } ImageInfo.readableSectorTags.Add(SectorTagType.CDTrackFlags); } ImageInfo.mediaType = BlindWriteProfileToMediaType(header.profile); if(dmi != null && pfi != null) { Decoders.DVD.PFI.PhysicalFormatInformation? pfi0 = Decoders.DVD.PFI.Decode(pfi); // All discs I tested the disk category and part version (as well as the start PSN for DVD-RAM) where modified by Alcohol // So much for archival value if(pfi0.HasValue) { switch(pfi0.Value.DiskCategory) { case Decoders.DVD.DiskCategory.DVDPR: ImageInfo.mediaType = MediaType.DVDPR; break; case Decoders.DVD.DiskCategory.DVDPRDL: ImageInfo.mediaType = MediaType.DVDPRDL; break; case Decoders.DVD.DiskCategory.DVDPRW: ImageInfo.mediaType = MediaType.DVDPRW; break; case Decoders.DVD.DiskCategory.DVDPRWDL: ImageInfo.mediaType = MediaType.DVDPRWDL; break; case Decoders.DVD.DiskCategory.DVDR: if(pfi0.Value.PartVersion == 6) ImageInfo.mediaType = MediaType.DVDRDL; else ImageInfo.mediaType = MediaType.DVDR; break; case Decoders.DVD.DiskCategory.DVDRAM: ImageInfo.mediaType = MediaType.DVDRAM; break; default: ImageInfo.mediaType = MediaType.DVDROM; break; case Decoders.DVD.DiskCategory.DVDRW: if(pfi0.Value.PartVersion == 3) ImageInfo.mediaType = MediaType.DVDRWDL; else ImageInfo.mediaType = MediaType.DVDRW; break; case Decoders.DVD.DiskCategory.HDDVDR: ImageInfo.mediaType = MediaType.HDDVDR; break; case Decoders.DVD.DiskCategory.HDDVDRAM: ImageInfo.mediaType = MediaType.HDDVDRAM; break; case Decoders.DVD.DiskCategory.HDDVDROM: ImageInfo.mediaType = MediaType.HDDVDROM; break; case Decoders.DVD.DiskCategory.HDDVDRW: ImageInfo.mediaType = MediaType.HDDVDRW; break; case Decoders.DVD.DiskCategory.Nintendo: if(pfi0.Value.DiscSize == Decoders.DVD.DVDSize.Eighty) ImageInfo.mediaType = MediaType.GOD; else ImageInfo.mediaType = MediaType.WOD; break; case Decoders.DVD.DiskCategory.UMD: ImageInfo.mediaType = MediaType.UMD; break; } if(Decoders.Xbox.DMI.IsXbox(dmi)) ImageInfo.mediaType = MediaType.XGD; else if(Decoders.Xbox.DMI.IsXbox360(dmi)) ImageInfo.mediaType = MediaType.XGD2; } } else if(ImageInfo.mediaType == MediaType.CD || ImageInfo.mediaType == MediaType.CDROM) { bool data = false; bool mode2 = false; bool firstaudio = false; bool firstdata = false; bool audio = false; foreach(Track _track in tracks) { // First track is audio firstaudio |= _track.TrackSequence == 1 && _track.TrackType == TrackType.Audio; // First track is data firstdata |= _track.TrackSequence == 1 && _track.TrackType != TrackType.Audio; // Any non first track is data data |= _track.TrackSequence != 1 && _track.TrackType != TrackType.Audio; // Any non first track is audio audio |= _track.TrackSequence != 1 && _track.TrackType == TrackType.Audio; switch(_track.TrackType) { case TrackType.CDMode2Formless: case TrackType.CDMode2Form1: case TrackType.CDMode2Form2: mode2 = true; break; } } if(!data && !firstdata) ImageInfo.mediaType = MediaType.CDDA; else if(firstaudio && data && sessions.Count > 1 && mode2) ImageInfo.mediaType = MediaType.CDPLUS; else if((firstdata && audio) || mode2) ImageInfo.mediaType = MediaType.CDROMXA; else if(!audio) ImageInfo.mediaType = MediaType.CDROM; else ImageInfo.mediaType = MediaType.CD; } ImageInfo.driveManufacturer = StringHandlers.CToString(header.manufacturer); ImageInfo.driveModel = StringHandlers.CToString(header.product); ImageInfo.driveFirmwareRevision = StringHandlers.CToString(header.revision); ImageInfo.imageApplication = "BlindWrite"; if(string.Compare(Path.GetExtension(imageFilter.GetFilename()), "B5T", StringComparison.OrdinalIgnoreCase) == 0) ImageInfo.imageApplicationVersion = "5"; else if(string.Compare(Path.GetExtension(imageFilter.GetFilename()), "B6T", StringComparison.OrdinalIgnoreCase) == 0) ImageInfo.imageApplicationVersion = "6"; ImageInfo.imageVersion = "5"; ImageInfo.imageSize = (ulong)imageFilter.GetDataForkLength(); ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; if(pma != null) { Decoders.CD.PMA.CDPMA pma0 = Decoders.CD.PMA.Decode(pma).Value; foreach(Decoders.CD.PMA.CDPMADescriptors descriptor in pma0.PMADescriptors) { if(descriptor.ADR == 2) { uint id = (uint)((descriptor.Min << 16) + (descriptor.Sec << 8) + descriptor.Frame); ImageInfo.mediaSerialNumber = string.Format("{0:X6}", id & 0x00FFFFFF); } } } if(atip != null) { Decoders.CD.ATIP.CDATIP atip0 = Decoders.CD.ATIP.Decode(atip).Value; if(atip0.DiscType) ImageInfo.mediaType = MediaType.CDRW; else ImageInfo.mediaType = MediaType.CDR; if(atip0.LeadInStartMin == 97) { int type = atip0.LeadInStartFrame % 10; int frm = atip0.LeadInStartFrame - type; ImageInfo.mediaManufacturer = Decoders.CD.ATIP.ManufacturerFromATIP(atip0.LeadInStartSec, frm); } } bool isBD = false; if(ImageInfo.mediaType == MediaType.BDR || ImageInfo.mediaType == MediaType.BDRE || ImageInfo.mediaType == MediaType.BDROM) { isDvd = false; isBD = true; } if(isBD && ImageInfo.sectors > 24438784) { if(ImageInfo.mediaType == MediaType.BDR) ImageInfo.mediaType = MediaType.BDRXL; if(ImageInfo.mediaType == MediaType.BDRE) ImageInfo.mediaType = MediaType.BDREXL; } DicConsole.DebugWriteLine("BlindWrite5 plugin", "ImageInfo.mediaType = {0}", ImageInfo.mediaType); if(mode2A != null) ImageInfo.readableMediaTags.Add(MediaTagType.SCSI_MODEPAGE_2A); if(pma != null) ImageInfo.readableMediaTags.Add(MediaTagType.CD_PMA); if(atip != null) ImageInfo.readableMediaTags.Add(MediaTagType.CD_ATIP); if(cdtext != null) ImageInfo.readableMediaTags.Add(MediaTagType.CD_TEXT); if(bca != null) { if(isDvd) ImageInfo.readableMediaTags.Add(MediaTagType.DVD_BCA); else if(isBD) ImageInfo.readableMediaTags.Add(MediaTagType.BD_BCA); } if(dmi != null) ImageInfo.readableMediaTags.Add(MediaTagType.DVD_DMI); if(pfi != null) ImageInfo.readableMediaTags.Add(MediaTagType.DVD_PFI); if(fullToc != null) ImageInfo.readableMediaTags.Add(MediaTagType.CD_FullTOC); if(ImageInfo.mediaType == MediaType.XGD2) { // All XGD3 all have the same number of blocks if(ImageInfo.sectors == 25063 || // Locked (or non compatible drive) ImageInfo.sectors == 4229664 || // Xtreme unlock ImageInfo.sectors == 4246304) // Wxripper unlock ImageInfo.mediaType = MediaType.XGD3; } DicConsole.VerboseWriteLine("BlindWrite image describes a disc of type {0}", ImageInfo.mediaType); return true; } 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.SCSI_MODEPAGE_2A: { if(mode2A != null) { return (byte[])mode2A.Clone(); } throw new FeatureNotPresentImageException("Image does not contain SCSI MODE PAGE 2Ah."); } case MediaTagType.CD_PMA: { if(pma != null) { return (byte[])pma.Clone(); } throw new FeatureNotPresentImageException("Image does not contain PMA information."); } case MediaTagType.CD_ATIP: { if(atip != null) { return (byte[])atip.Clone(); } throw new FeatureNotPresentImageException("Image does not contain ATIP information."); } case MediaTagType.CD_TEXT: { if(cdtext != null) { return (byte[])cdtext.Clone(); } throw new FeatureNotPresentImageException("Image does not contain CD-Text information."); } case MediaTagType.DVD_BCA: case MediaTagType.BD_BCA: { if(bca != null) { return (byte[])bca.Clone(); } throw new FeatureNotPresentImageException("Image does not contain BCA information."); } case MediaTagType.DVD_PFI: { if(pfi != null) { return (byte[])pfi.Clone(); } throw new FeatureNotPresentImageException("Image does not contain PFI."); } case MediaTagType.DVD_DMI: { if(dmi != null) { return (byte[])dmi.Clone(); } throw new FeatureNotPresentImageException("Image does not contain DMI."); } case MediaTagType.CD_FullTOC: { if(fullToc != null) { return (byte[])fullToc.Clone(); } throw new FeatureNotPresentImageException("Image does not contain TOC 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 - kvp.Value) < (track.TrackEndSector - track.TrackStartSector)) return ReadSectors((sectorAddress - kvp.Value), length, kvp.Key); } } } } throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); } 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 - kvp.Value) < (track.TrackEndSector - track.TrackStartSector)) return ReadSectorsTag((sectorAddress - kvp.Value), length, kvp.Key, tag); } } } } throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); } public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) { // TODO: Cross data files Track _track = new Track(); DataFileCharacteristics chars = new DataFileCharacteristics(); _track.TrackSequence = 0; foreach(Track bwTrack in tracks) { if(bwTrack.TrackSequence == track) { _track = bwTrack; 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)); foreach(DataFileCharacteristics _chars in filePaths) { if((long)sectorAddress >= _chars.startLba && length < ((ulong)_chars.sectors - sectorAddress)) { chars = _chars; break; } } if(string.IsNullOrEmpty(chars.filePath) || chars.fileFilter == null) throw new ArgumentOutOfRangeException(nameof(chars.fileFilter), "Track does not exist in disc image"); uint sector_offset; uint sector_size; uint sector_skip; switch(_track.TrackType) { 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; } case TrackType.Audio: { sector_offset = 0; sector_size = 2352; sector_skip = 0; break; } case TrackType.Data: { sector_offset = 0; sector_size = 2048; sector_skip = 0; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } switch(chars.subchannel) { case TrackSubchannelType.None: sector_skip += 0; break; case TrackSubchannelType.Q16Interleaved: sector_skip += 16; break; case TrackSubchannelType.PackedInterleaved: sector_skip += 96; break; default: throw new FeatureSupportedButNotImplementedImageException("Unsupported subchannel type"); } byte[] buffer = new byte[sector_size * length]; imageStream = chars.fileFilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.TrackFileOffset + (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) { // TODO: Cross data files Track _track = new Track(); DataFileCharacteristics chars = new DataFileCharacteristics(); _track.TrackSequence = 0; foreach(Track bwTrack in tracks) { if(bwTrack.TrackSequence == track) { _track = bwTrack; 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)); foreach(DataFileCharacteristics _chars in filePaths) { if((long)sectorAddress >= _chars.startLba && length < ((ulong)_chars.sectors - sectorAddress)) { chars = _chars; break; } } if(string.IsNullOrEmpty(chars.filePath) || chars.fileFilter == null) throw new ArgumentOutOfRangeException(nameof(chars.fileFilter), "Track does not exist in disc image"); if(_track.TrackType == TrackType.Data) throw new ArgumentException("Unsupported tag requested", 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 flag; if(trackFlags.TryGetValue(track, out flag)) return new byte[] { flag }; throw new ArgumentException("Unsupported tag requested", nameof(tag)); 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; } case SectorTagType.CDSectorSubchannel: throw new NotImplementedException("Packed subchannel not yet supported"); 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; } case SectorTagType.CDSectorSubchannel: throw new NotImplementedException("Packed subchannel not yet supported"); 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; } case SectorTagType.CDSectorSubchannel: throw new NotImplementedException("Packed subchannel not yet supported"); 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; } case SectorTagType.CDSectorSubchannel: throw new NotImplementedException("Packed subchannel not yet supported"); default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } break; case TrackType.Audio: { switch(tag) { case SectorTagType.CDSectorSubchannel: throw new NotImplementedException("Packed subchannel not yet supported"); default: throw new ArgumentException("Unsupported tag requested", nameof(tag)); } } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } switch(chars.subchannel) { case TrackSubchannelType.None: sector_skip += 0; break; case TrackSubchannelType.Q16Interleaved: sector_skip += 16; break; case TrackSubchannelType.PackedInterleaved: sector_skip += 96; break; default: throw new FeatureSupportedButNotImplementedImageException("Unsupported subchannel type"); } byte[] buffer = new byte[sector_size * length]; imageStream = _track.TrackFilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.TrackFileOffset + (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 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), "Sector address not found"); } public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) { // TODO: Cross data files Track _track = new Track(); DataFileCharacteristics chars = new DataFileCharacteristics(); _track.TrackSequence = 0; foreach(Track bwTrack in tracks) { if(bwTrack.TrackSequence == track) { _track = bwTrack; 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)); foreach(DataFileCharacteristics _chars in filePaths) { if((long)sectorAddress >= _chars.startLba && length < ((ulong)_chars.sectors - sectorAddress)) { chars = _chars; break; } } if(string.IsNullOrEmpty(chars.filePath) || chars.fileFilter == null) throw new ArgumentOutOfRangeException(nameof(chars.fileFilter), "Track does not exist in disc image"); uint sector_offset; uint sector_size; uint sector_skip; switch(_track.TrackType) { case TrackType.CDMode1: case TrackType.CDMode2Formless: case TrackType.CDMode2Form1: case TrackType.CDMode2Form2: case TrackType.Audio: { sector_offset = 0; sector_size = 2352; sector_skip = 0; break; } case TrackType.Data: { sector_offset = 0; sector_size = 2048; sector_skip = 0; break; } default: throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); } switch(chars.subchannel) { case TrackSubchannelType.None: sector_skip += 0; break; case TrackSubchannelType.Q16Interleaved: sector_skip += 16; break; case TrackSubchannelType.PackedInterleaved: sector_skip += 96; break; default: throw new FeatureSupportedButNotImplementedImageException("Unsupported subchannel type"); } byte[] buffer = new byte[sector_size * length]; imageStream = _track.TrackFilter.GetDataForkStream(); BinaryReader br = new BinaryReader(imageStream); br.BaseStream.Seek((long)_track.TrackFileOffset + (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 "BlindWrite 5 TOC file"; } public override string GetImageVersion() { return ImageInfo.imageVersion; } public override string GetImageApplication() { return ImageInfo.imageApplication; } public override MediaType GetMediaType() { return ImageInfo.mediaType; } public override List GetPartitions() { return partitions; } public override List GetTracks() { return tracks; } public override List GetSessionTracks(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; } #endregion Public Methods #region Private methods static TrackType BlindWriteTrackTypeToTrackType(BW5_TrackType trackType) { switch(trackType) { case BW5_TrackType.Mode1: return TrackType.CDMode1; case BW5_TrackType.Mode2F1: return TrackType.CDMode2Form1; case BW5_TrackType.Mode2F2: return TrackType.CDMode2Form2; case BW5_TrackType.Mode2: return TrackType.CDMode2Formless; case BW5_TrackType.Audio: return TrackType.Audio; default: return TrackType.Data; } } static MediaType BlindWriteProfileToMediaType(ProfileNumber profile) { switch(profile) { case ProfileNumber.BDRE: return MediaType.BDRE; case ProfileNumber.BDROM: return MediaType.BDROM; case ProfileNumber.BDRRdm: case ProfileNumber.BDRSeq: return MediaType.BDR; case ProfileNumber.CDR: case ProfileNumber.HDBURNR: return MediaType.CDR; case ProfileNumber.CDROM: case ProfileNumber.HDBURNROM: return MediaType.CDROM; case ProfileNumber.CDRW: case ProfileNumber.HDBURNRW: return MediaType.CDRW; case ProfileNumber.DDCDR: return MediaType.DDCDR; case ProfileNumber.DDCDROM: return MediaType.DDCD; case ProfileNumber.DDCDRW: return MediaType.DDCDRW; case ProfileNumber.DVDDownload: return MediaType.DVDDownload; case ProfileNumber.DVDRAM: return MediaType.DVDRAM; case ProfileNumber.DVDRDLJump: case ProfileNumber.DVDRDLSeq: return MediaType.DVDRDL; case ProfileNumber.DVDRDLPlus: return MediaType.DVDPRDL; case ProfileNumber.DVDROM: return MediaType.DVDROM; case ProfileNumber.DVDRPlus: return MediaType.DVDPR; case ProfileNumber.DVDRSeq: return MediaType.DVDR; case ProfileNumber.DVDRWDL: return MediaType.DVDRWDL; case ProfileNumber.DVDRWDLPlus: return MediaType.DVDPRWDL; case ProfileNumber.DVDRWPlus: return MediaType.DVDPRW; case ProfileNumber.DVDRWRes: case ProfileNumber.DVDRWSeq: return MediaType.DVDRW; case ProfileNumber.HDDVDR: return MediaType.HDDVDR; case ProfileNumber.HDDVDRAM: return MediaType.HDDVDRAM; case ProfileNumber.HDDVDRDL: return MediaType.HDDVDRDL; case ProfileNumber.HDDVDROM: return MediaType.HDDVDROM; case ProfileNumber.HDDVDRW: return MediaType.HDDVDRW; case ProfileNumber.HDDVDRWDL: return MediaType.HDDVDRWDL; case ProfileNumber.ASMO: case ProfileNumber.MOErasable: return MediaType.UnknownMO; case ProfileNumber.NonRemovable: return MediaType.GENERIC_HDD; default: return MediaType.CD; } } #endregion Private methods #region Unsupported features 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 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 Unsupported features } }