From 841453e1faf842ad54966ac1114ec9f3de2951a6 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 10 Oct 2016 02:06:52 +0100 Subject: [PATCH] Adds support for DiscJuggler images, closes #56 --- DiscImageChef.DiscImages/ChangeLog | 6 + .../DiscImageChef.DiscImages.csproj | 1 + DiscImageChef.DiscImages/DiscJuggler.cs | 1347 ++++++++++++++++- README.md | 1 + TODO | 2 - 5 files changed, 1349 insertions(+), 8 deletions(-) diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index 9ea82e335..74e010e81 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,9 @@ +2016-10-10 Natalia Portillo + + * DiscJuggler.cs: + * DiscImageChef.DiscImages.csproj: Adds support for + DiscJuggler images, closes #56 + 2016-10-10 Natalia Portillo * CloneCD.cs: Correct subchannel reading. diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index cf740051a..375447691 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -71,6 +71,7 @@ + diff --git a/DiscImageChef.DiscImages/DiscJuggler.cs b/DiscImageChef.DiscImages/DiscJuggler.cs index ea686ba85..04e794904 100644 --- a/DiscImageChef.DiscImages/DiscJuggler.cs +++ b/DiscImageChef.DiscImages/DiscJuggler.cs @@ -29,13 +29,1348 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; +using DiscImageChef.Filters; +using DiscImageChef.ImagePlugins; + namespace DiscImageChef.DiscImages { - public class DiscJuggler - { - public DiscJuggler() - { - } - } + // Support separate data files? Never seen a DiscJuggler image using them anyways... + public class DiscJuggler : ImagePlugin + { + Stream imageStream; + List sessions; + List tracks; + byte[] cdtext; + Dictionary offsetmap; + List partitions; + Dictionary trackFlags; + + public DiscJuggler() + { + Name = "DiscJuggler"; + PluginUUID = new Guid("2444DBC6-CD35-424C-A227-39B0C4DB01B2"); + 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) + { + imageStream = imageFilter.GetDataForkStream(); + + imageStream.Seek(-4, SeekOrigin.End); + byte[] dscLen_b = new byte[4]; + imageStream.Read(dscLen_b, 0, 4); + int dscLen = BitConverter.ToInt32(dscLen_b, 0); + + DicConsole.DebugWriteLine("DiscJuggler plugin", "dscLen = {0}", dscLen); + + if(dscLen >= imageStream.Length) + return false; + + byte[] descriptor = new byte[dscLen]; + imageStream.Seek(-dscLen, SeekOrigin.End); + imageStream.Read(descriptor, 0, dscLen); + + // Sessions + if(descriptor[0] > 99 || descriptor[0] == 0) + return false; + + // Seems all sessions start with this data + if(descriptor[1] != 0x00 || descriptor[3] != 0x00 || descriptor[4] != 0x00 || + descriptor[5] != 0x00 || descriptor[6] != 0x00 || descriptor[7] != 0x00 || + descriptor[8] != 0x00 || descriptor[9] != 0x00 || descriptor[10] != 0x01 || + descriptor[11] != 0x00 || descriptor[12] != 0x00 || descriptor[13] != 0x00 || + descriptor[14] != 0xFF || descriptor[15] != 0xFF) + return false; + + // Too many tracks + if(descriptor[2] > 99) + return false; + + return true; + } + + public override bool OpenImage(Filter imageFilter) + { + imageStream = imageFilter.GetDataForkStream(); + + imageStream.Seek(-4, SeekOrigin.End); + byte[] dscLen_b = new byte[4]; + imageStream.Read(dscLen_b, 0, 4); + int dscLen = BitConverter.ToInt32(dscLen_b, 0); + + if(dscLen >= imageStream.Length) + return false; + + byte[] descriptor = new byte[dscLen]; + imageStream.Seek(-dscLen, SeekOrigin.End); + imageStream.Read(descriptor, 0, dscLen); + + // Sessions + if(descriptor[0] > 99 || descriptor[0] == 0) + return false; + + int position = 1; + + ushort sessionSequence = 0; + sessions = new List(); + tracks = new List(); + partitions = new List(); + offsetmap = new Dictionary(); + trackFlags = new Dictionary(); + ushort mediumType; + byte maxS = descriptor[0]; + + DicConsole.DebugWriteLine("DiscJuggler plugin", "maxS = {0}", maxS); + uint lastSessionTrack = 0; + ulong currentOffset = 0; + + // Read sessions + for(byte s = 0; s <= maxS; s++) + { + DicConsole.DebugWriteLine("DiscJuggler plugin", "s = {0}", s); + + // Seems all sessions start with this data + if(descriptor[position + 0] != 0x00 || descriptor[position + 2] != 0x00 || descriptor[position + 3] != 0x00 || + descriptor[position + 4] != 0x00 || descriptor[position + 5] != 0x00 || descriptor[position + 6] != 0x00 || + descriptor[position + 7] != 0x00 || descriptor[position + 8] != 0x00 || descriptor[position + 9] != 0x01 || + descriptor[position + 10] != 0x00 || descriptor[position + 11] != 0x00 || descriptor[position + 12] != 0x00 || + descriptor[position + 13] != 0xFF || descriptor[position + 14] != 0xFF) + return false; + + // Too many tracks + if(descriptor[position + 1] > 99) + return false; + + byte maxT = descriptor[position + 1]; + DicConsole.DebugWriteLine("DiscJuggler plugin", "maxT = {0}", maxT); + + sessionSequence++; + Session session = new Session(); + session.SessionSequence = sessionSequence; + session.EndTrack = uint.MinValue; + session.StartTrack = uint.MaxValue; + + position += 15; + bool addedATrack = false; + + // Read track + for(byte t = 0; t < maxT; t++) + { + addedATrack = false; + DicConsole.DebugWriteLine("DiscJuggler plugin", "t = {0}", t); + Track track = new Track(); + + // Skip unknown + position += 16; + + byte[] trackFilename_b = new byte[descriptor[position]]; + position++; + Array.Copy(descriptor, position, trackFilename_b, 0, trackFilename_b.Length); + position += trackFilename_b.Length; + track.TrackFile = Path.GetFileName(Encoding.Default.GetString(trackFilename_b)); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tfilename = {0}", track.TrackFile); + + // Skip unknown + position += 29; + + mediumType = BitConverter.ToUInt16(descriptor, position); + position += 2; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tmediumType = {0}", mediumType); + + // Read indices + track.Indexes = new Dictionary(); + ushort maxI = BitConverter.ToUInt16(descriptor, position); + position += 2; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tmaxI = {0}", maxI); + for(ushort i = 0; i < maxI; i++) + { + uint index = BitConverter.ToUInt32(descriptor, position); + track.Indexes.Add(i, index); + position += 4; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tindex[{1}] = {0}", index, i); + } + + // Read CD-Text + uint maxC = BitConverter.ToUInt32(descriptor, position); + position += 4; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tmaxC = {0}", maxC); + for(uint c = 0; c < maxC; c++) + { + for(int cb = 0; cb < 18; cb++) + { + int bLen = descriptor[position]; + position++; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tc[{1}][{2}].Length = {0}", bLen, c, cb); + if(bLen > 0) + { + byte[] textBlk = new byte[bLen]; + Array.Copy(descriptor, position, textBlk, 0, bLen); + position += bLen; + // Track title + if(cb == 10) + { + track.TrackDescription = Encoding.Default.GetString(textBlk, 0, bLen); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tTrack title = {0}", track.TrackDescription); + } + } + } + } + + position += 2; + uint trackMode = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrackMode = {0}", trackMode); + position += 4; + + // Skip unknown + position += 4; + + session.SessionSequence = (ushort)(BitConverter.ToUInt32(descriptor, position) + 1); + track.TrackSession = (ushort)(session.SessionSequence + 1); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tsession = {0}", session.SessionSequence); + position += 4; + track.TrackSequence = BitConverter.ToUInt32(descriptor, position) + lastSessionTrack + 1; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrack = {1} + {2} + 1 = {0}", track.TrackSequence, BitConverter.ToUInt32(descriptor, position), lastSessionTrack); + position += 4; + track.TrackStartSector = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrackStart = {0}", track.TrackStartSector); + position += 4; + uint trackLen = BitConverter.ToUInt32(descriptor, position); + track.TrackEndSector = track.TrackStartSector + trackLen - 1; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrackEnd = {0}", track.TrackEndSector); + position += 4; + + if(track.TrackSequence > session.EndTrack) + { + session.EndTrack = track.TrackSequence; + session.EndSector = track.TrackEndSector; + } + if(track.TrackSequence < session.StartTrack) + { + session.StartTrack = track.TrackSequence; + session.StartSector = track.TrackStartSector; + } + + // Skip unknown + position += 16; + + uint readMode = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\treadMode = {0}", readMode); + position += 4; + uint trackCtl = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrackCtl = {0}", trackCtl); + position += 4; + + // Skip unknown + position += 9; + + byte[] isrc = new byte[12]; + Array.Copy(descriptor, position, isrc, 0, 12); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tisrc = {0}", StringHandlers.CToString(isrc)); + position += 12; + uint isrc_valid = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tisrc_valid = {0}", isrc_valid); + position += 4; + + // Skip unknown + position += 87; + + byte sessionType = descriptor[position]; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tsessionType = {0}", sessionType); + position++; + + // Skip unknown + position += 5; + + byte trackFollows = descriptor[position]; + DicConsole.DebugWriteLine("DiscJuggler plugin", "\ttrackFollows = {0}", trackFollows); + position += 2; + + uint endAddress = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "\tendAddress = {0}", endAddress); + position += 4; + + // As to skip the lead-in + bool firstTrack = currentOffset == 0; + + track.TrackSubchannelType = TrackSubchannelType.None; + + switch(trackMode) + { + // Audio + case 0: + if(ImageInfo.sectorSize < 2352) + ImageInfo.sectorSize = 2352; + track.TrackType = TrackType.Audio; + track.TrackBytesPerSector = 2352; + track.TrackRawBytesPerSector = 2352; + switch(readMode) + { + case 2: + if(firstTrack) + currentOffset += 150 * (ulong)track.TrackRawBytesPerSector; + track.TrackFileOffset = currentOffset; + currentOffset += trackLen * (ulong)track.TrackRawBytesPerSector; + break; + case 3: + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 16); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.Q16Interleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 16); + break; + case 4: + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 96); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 96); + break; + default: + throw new ImageNotSupportedException(string.Format("Unknown read mode {0}", readMode)); + } + break; + // Mode 1 or DVD + case 1: + if(ImageInfo.sectorSize < 2048) + ImageInfo.sectorSize = 2048; + track.TrackType = TrackType.CDMode1; + track.TrackBytesPerSector = 2048; + switch(readMode) + { + case 0: + track.TrackRawBytesPerSector = 2048; + if(firstTrack) + currentOffset += 150 * (ulong)track.TrackRawBytesPerSector; + track.TrackFileOffset = currentOffset; + currentOffset += trackLen * (ulong)track.TrackRawBytesPerSector; + break; + case 1: + throw new ImageNotSupportedException(string.Format("Invalid read mode {0} for this track", readMode)); + case 2: + track.TrackRawBytesPerSector = 2352; + currentOffset += trackLen * (ulong)track.TrackRawBytesPerSector; + 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); + break; + case 3: + track.TrackRawBytesPerSector = 2352; + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 16); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.Q16Interleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 16); + 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); + break; + case 4: + track.TrackRawBytesPerSector = 2352; + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 96); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 96); + 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); + break; + default: + throw new ImageNotSupportedException(string.Format("Unknown read mode {0}", readMode)); + } + break; + // Mode 2 + case 2: + if(ImageInfo.sectorSize < 2336) + ImageInfo.sectorSize = 2336; + track.TrackType = TrackType.CDMode2Formless; + track.TrackBytesPerSector = 2336; + switch(readMode) + { + case 0: + throw new ImageNotSupportedException(string.Format("Invalid read mode {0} for this track", readMode)); + case 1: + track.TrackRawBytesPerSector = 2336; + if(firstTrack) + currentOffset += 150 * (ulong)track.TrackRawBytesPerSector; + track.TrackFileOffset = currentOffset; + currentOffset += trackLen * (ulong)track.TrackRawBytesPerSector; + break; + case 2: + track.TrackRawBytesPerSector = 2352; + currentOffset += trackLen * (ulong)track.TrackRawBytesPerSector; + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + break; + case 3: + track.TrackRawBytesPerSector = 2352; + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 16); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.Q16Interleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 16); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + break; + case 4: + track.TrackRawBytesPerSector = 2352; + if(firstTrack) + currentOffset += 150 * (ulong)(track.TrackRawBytesPerSector + 96); + track.TrackFileOffset = currentOffset; + track.TrackSubchannelFile = track.TrackFile; + track.TrackSubchannelOffset = currentOffset; + track.TrackSubchannelType = TrackSubchannelType.RawInterleaved; + currentOffset += trackLen * (ulong)(track.TrackRawBytesPerSector + 96); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSync)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSync); + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorHeader)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorHeader); + break; + default: + throw new ImageNotSupportedException(string.Format("Unknown read mode {0}", readMode)); + } + break; + default: + throw new ImageNotSupportedException(string.Format("Unknown track mode {0}", trackMode)); + } + + track.TrackFile = imageFilter.GetFilename(); + track.TrackFilter = imageFilter; + if(track.TrackSubchannelType != TrackSubchannelType.None) + { + track.TrackSubchannelFile = imageFilter.GetFilename(); + track.TrackSubchannelFilter = imageFilter; + if(!ImageInfo.readableSectorTags.Contains(SectorTagType.CDSectorSubchannel)) + ImageInfo.readableSectorTags.Add(SectorTagType.CDSectorSubchannel); + } + + Partition partition = new Partition(); + partition.PartitionDescription = track.TrackDescription; + partition.PartitionLength = (ulong)(trackLen * track.TrackBytesPerSector); + partition.PartitionSectors = trackLen; + 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); + tracks.Add(track); + trackFlags.Add(track.TrackSequence, (byte)(trackCtl & 0xFF)); + addedATrack = true; + } + + if(addedATrack) + { + lastSessionTrack = session.EndTrack; + sessions.Add(session); + DicConsole.DebugWriteLine("DiscJuggler plugin", "session.StartTrack = {0}", session.StartTrack); + DicConsole.DebugWriteLine("DiscJuggler plugin", "session.StartSector = {0}", session.StartSector); + DicConsole.DebugWriteLine("DiscJuggler plugin", "session.EndTrack = {0}", session.EndTrack); + DicConsole.DebugWriteLine("DiscJuggler plugin", "session.EndSector = {0}", session.EndSector); + DicConsole.DebugWriteLine("DiscJuggler plugin", "session.SessionSequence = {0}", session.SessionSequence); + } + } + + // Skip unknown + position += 16; + + DicConsole.DebugWriteLine("DiscJuggler plugin", "Current position = {0}", position); + byte[] filename_b = new byte[descriptor[position]]; + position++; + Array.Copy(descriptor, position, filename_b, 0, filename_b.Length); + position += filename_b.Length; + string filename = Path.GetFileName(Encoding.Default.GetString(filename_b)); + DicConsole.DebugWriteLine("DiscJuggler plugin", "filename = {0}", filename); + + // Skip unknown + position += 29; + + mediumType = BitConverter.ToUInt16(descriptor, position); + position += 2; + DicConsole.DebugWriteLine("DiscJuggler plugin", "mediumType = {0}", mediumType); + + uint discSize = BitConverter.ToUInt32(descriptor, position); + position += 4; + DicConsole.DebugWriteLine("DiscJuggler plugin", "discSize = {0}", discSize); + + byte[] volid_b = new byte[descriptor[position]]; + position++; + Array.Copy(descriptor, position, volid_b, 0, volid_b.Length); + position += volid_b.Length; + string volid = Path.GetFileName(Encoding.Default.GetString(volid_b)); + DicConsole.DebugWriteLine("DiscJuggler plugin", "volid = {0}", volid); + + // Skip unknown + position += 9; + + byte[] mcn = new byte[13]; + Array.Copy(descriptor, position, mcn, 0, 13); + DicConsole.DebugWriteLine("DiscJuggler plugin", "mcn = {0}", StringHandlers.CToString(mcn)); + position += 13; + uint mcn_valid = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "mcn_valid = {0}", mcn_valid); + position += 4; + + uint cdtextLen = BitConverter.ToUInt32(descriptor, position); + DicConsole.DebugWriteLine("DiscJuggler plugin", "cdtextLen = {0}", cdtextLen); + position += 4; + if(cdtextLen > 0) + { + cdtext = new byte[cdtextLen]; + Array.Copy(descriptor, position, cdtext, 0, cdtextLen); + position += (int)cdtextLen; + ImageInfo.readableMediaTags.Add(MediaTagType.CD_TEXT); + } + + // Skip unknown + position += 12; + + DicConsole.DebugWriteLine("DiscJuggler plugin", "End position = {0}", position); + + if(ImageInfo.mediaType == MediaType.CDROM) + { + 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; + } + } + + 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 = "DiscJuggler"; + ImageInfo.imageSize = (ulong)imageFilter.GetDataForkLength(); + ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); + ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); + ImageInfo.xmlMediaType = XmlMediaType.OpticalDisc; + + return true; + } + + static MediaType DecodeCDIMediumType(ushort type) + { + switch(type) + { + case 56: + return MediaType.DVDROM; + case 152: + return MediaType.CDROM; + default: + return MediaType.Unknown; + } + } + + 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_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: + if(_track.TrackRawBytesPerSector == 2352) + { + sector_offset = 16; + sector_size = 2048; + sector_skip = 288; + } + else + { + sector_offset = 0; + sector_size = 2048; + sector_skip = 0; + } + break; + case TrackType.CDMode2Formless: + if(_track.TrackRawBytesPerSector == 2352) + { + sector_offset = 16; + sector_size = 2336; + sector_skip = 0; + } + else + { + sector_offset = 0; + sector_size = 2336; + sector_skip = 0; + } + break; + default: + throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); + } + + switch(_track.TrackSubchannelType) + { + 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.Seek((long)(_track.TrackFileOffset + sectorAddress * (ulong)_track.TrackRawBytesPerSector), SeekOrigin.Begin); + if(sector_offset == 0 && sector_skip == 0) + imageStream.Read(buffer, 0, buffer.Length); + else + { + for(int i = 0; i < length; i++) + { + byte[] sector = new byte[sector_size]; + imageStream.Seek(sector_offset, SeekOrigin.Current); + imageStream.Read(sector, 0, sector.Length); + imageStream.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) + { + 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.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: + if(_track.TrackRawBytesPerSector != 2352) + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + 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: + if(_track.TrackSubchannelType == TrackSubchannelType.None) + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + if(_track.TrackSubchannelType == TrackSubchannelType.Q16Interleaved) + throw new ArgumentException("Q16 subchannel not yet supported"); + + sector_offset = 2352; + sector_size = 96; + sector_skip = 0; + break; + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + break; + case TrackType.CDMode2Formless: + if(_track.TrackRawBytesPerSector != 2352) + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + { + 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: + if(_track.TrackSubchannelType == TrackSubchannelType.None) + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + if(_track.TrackSubchannelType == TrackSubchannelType.Q16Interleaved) + throw new ArgumentException("Q16 subchannel not yet supported"); + + sector_offset = 2352; + sector_size = 96; + sector_skip = 0; + break; + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + break; + } + case TrackType.Audio: + { + switch(tag) + { + case SectorTagType.CDSectorSubchannel: + if(_track.TrackSubchannelType == TrackSubchannelType.None) + throw new ArgumentException("Unsupported tag requested for this track", nameof(tag)); + if(_track.TrackSubchannelType == TrackSubchannelType.Q16Interleaved) + throw new ArgumentException("Q16 subchannel not yet supported"); + + sector_offset = 2352; + sector_size = 96; + sector_skip = 0; + break; + default: + throw new ArgumentException("Unsupported tag requested", nameof(tag)); + } + break; + } + default: + throw new FeatureSupportedButNotImplementedImageException("Unsupported track type"); + } + + switch(_track.TrackSubchannelType) + { + 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"); + } + + buffer = new byte[sector_size * length]; + + imageStream.Seek((long)(_track.TrackFileOffset + sectorAddress * (ulong)_track.TrackRawBytesPerSector), SeekOrigin.Begin); + if(sector_offset == 0 && sector_skip == 0) + imageStream.Read(buffer, 0, buffer.Length); + else + { + for(int i = 0; i < length; i++) + { + byte[] sector = new byte[sector_size]; + imageStream.Seek(sector_offset, SeekOrigin.Current); + imageStream.Read(sector, 0, sector.Length); + imageStream.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)); + + uint sector_offset = 0; + uint sector_size = (uint)_track.TrackRawBytesPerSector; + uint sector_skip = 0; + + switch(_track.TrackSubchannelType) + { + 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.Seek((long)(_track.TrackFileOffset + sectorAddress * (ulong)_track.TrackRawBytesPerSector), SeekOrigin.Begin); + if(sector_offset == 0 && sector_skip == 0) + imageStream.Read(buffer, 0, buffer.Length); + else + { + for(int i = 0; i < length; i++) + { + byte[] sector = new byte[sector_size]; + imageStream.Seek(sector_offset, SeekOrigin.Current); + imageStream.Read(sector, 0, sector.Length); + imageStream.Seek(sector_skip, SeekOrigin.Current); + Array.Copy(sector, 0, buffer, i * sector_size, sector_size); + } + } + + return buffer; + } + + public override string GetImageFormat() + { + return "DiscJuggler"; + } + + 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(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; + } } } diff --git a/README.md b/README.md index 08876fcf5..028c84c4c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Supported disk image formats * Apple Disk Archival/Retrieval Tool (DART) * MAME Compressed Hunks of Data (CHD) * Apple II nibble images (NIB) +* DiscJuggler images Supported partitioning schemes ============================== diff --git a/TODO b/TODO index 82e4846a1..07df4b583 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,4 @@ Disc image plugins: ---- Add support for CloneCD images ---- Add support for DiscJuggler images --- Add support for dump(8) images --- Add support for IMD images --- Add support for Kryoflux images