diff --git a/DiscImageChef.DiscImages/DiscImageChef.cs b/DiscImageChef.DiscImages/DiscImageChef.cs index 9da94dd2..f277ca2e 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.cs +++ b/DiscImageChef.DiscImages/DiscImageChef.cs @@ -113,6 +113,8 @@ namespace DiscImageChef.DiscImages byte shift; byte[] structureBytes; IntPtr structurePointer; + Dictionary trackFlags; + Dictionary trackIsrcs; ulong[] userDataDdt; public DiscImageChef() @@ -146,9 +148,9 @@ namespace DiscImageChef.DiscImages public string Name => "DiscImageChef format"; public Guid Id => new Guid("49360069-1784-4A2F-B723-0C844D610B0A"); public string Format => "DiscImageChef"; - public List Partitions { get; } + public List Partitions { get; private set; } public List Tracks { get; private set; } - public List Sessions { get; } + public List Sessions { get; private set; } public bool Identify(IFilter imageFilter) { @@ -568,6 +570,59 @@ namespace DiscImageChef.DiscImages imageInfo.DriveFirmwareRevision); } + break; + case BlockType.TracksBlock: + TracksHeader tracksHeader = new TracksHeader(); + structureBytes = new byte[Marshal.SizeOf(tracksHeader)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(tracksHeader)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(tracksHeader)); + tracksHeader = (TracksHeader)Marshal.PtrToStructure(structurePointer, typeof(TracksHeader)); + Marshal.FreeHGlobal(structurePointer); + if(tracksHeader.identifier != BlockType.TracksBlock) + { + DicConsole.DebugWriteLine("DiscImageChef format plugin", + "Incorrect identifier for tracks block at position {0}", + entry.offset); + break; + } + + // TODO: Check CRC64 + + Tracks = new List(); + trackFlags = new Dictionary(); + trackIsrcs = new Dictionary(); + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Found {0} tracks at position {0}", + tracksHeader.entries, entry.offset); + + for(ushort i = 0; i < tracksHeader.entries; i++) + { + TrackEntry trackEntry = new TrackEntry(); + structureBytes = new byte[Marshal.SizeOf(trackEntry)]; + imageStream.Read(structureBytes, 0, structureBytes.Length); + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trackEntry)); + Marshal.Copy(structureBytes, 0, structurePointer, Marshal.SizeOf(trackEntry)); + trackEntry = (TrackEntry)Marshal.PtrToStructure(structurePointer, typeof(TrackEntry)); + Marshal.FreeHGlobal(structurePointer); + + Tracks.Add(new Track + { + TrackSequence = trackEntry.sequence, + TrackType = trackEntry.type, + TrackStartSector = (ulong)trackEntry.start, + TrackEndSector = (ulong)trackEntry.end, + TrackPregap = (ulong)trackEntry.pregap, + TrackSession = trackEntry.session, + TrackFile = imageFilter.GetFilename(), + TrackFileType = "BINARY", + TrackFilter = imageFilter + }); + + trackFlags.Add(trackEntry.sequence, trackEntry.flags); + trackIsrcs.Add(trackEntry.sequence, trackEntry.isrc); + } + break; } } @@ -596,6 +651,79 @@ namespace DiscImageChef.DiscImages currentCacheSize = 0; if(!inMemoryDdt) ddtEntryCache = new Dictionary(); + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc) + { + if(Tracks == null || Tracks.Count == 0) + { + Tracks = new List + { + new Track + { + Indexes = new Dictionary(), + TrackBytesPerSector = (int)imageInfo.SectorSize, + TrackEndSector = imageInfo.Sectors - 1, + TrackFile = imageFilter.GetFilename(), + TrackFileType = "BINARY", + TrackFilter = imageFilter, + TrackRawBytesPerSector = (int)imageInfo.SectorSize, + TrackSession = 1, + TrackSequence = 1, + TrackType = TrackType.Data + } + }; + + trackFlags = new Dictionary {{1, (byte)CdFlags.DataTrack}}; + trackIsrcs = new Dictionary(); + } + + Sessions = new List(); + for(int i = 1; i <= Tracks.Max(t => t.TrackSession); i++) + Sessions.Add(new Session + { + SessionSequence = (ushort)i, + StartTrack = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackSequence), + EndTrack = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackSequence), + StartSector = Tracks.Where(t => t.TrackSession == i).Min(t => t.TrackStartSector), + EndSector = Tracks.Where(t => t.TrackSession == i).Max(t => t.TrackEndSector) + }); + + ulong currentTrackOffset = 0; + Partitions = new List(); + foreach(Track track in Tracks.OrderBy(t => t.TrackStartSector)) + { + Partitions.Add(new Partition + { + Sequence = track.TrackSequence, + Type = track.TrackType.ToString(), + Name = $"Track {track.TrackSequence}", + Offset = currentTrackOffset, + Start = track.TrackStartSector, + Size = (track.TrackEndSector - track.TrackStartSector + 1) * + (ulong)track.TrackBytesPerSector, + Length = track.TrackEndSector - track.TrackStartSector + 1, + Scheme = "Optical disc track" + }); + currentTrackOffset += (track.TrackEndSector - track.TrackStartSector + 1) * + (ulong)track.TrackBytesPerSector; + } + + Track[] tracks = Tracks.ToArray(); + for(int i = 0; i < tracks.Length; i++) + { + byte[] sector = ReadSector(tracks[i].TrackStartSector); + tracks[i].TrackBytesPerSector = sector.Length; + tracks[i].TrackRawBytesPerSector = sector.Length; + } + + Tracks = tracks.ToList(); + } + else + { + Tracks = null; + Sessions = null; + Partitions = null; + } + return true; } @@ -686,12 +814,26 @@ namespace DiscImageChef.DiscImages public byte[] ReadSector(ulong sectorAddress, uint track) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSector(trk.TrackStartSector + sectorAddress); } public byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSectorTag(trk.TrackStartSector + sectorAddress, tag); } public byte[] ReadSectors(ulong sectorAddress, uint length) @@ -721,12 +863,34 @@ namespace DiscImageChef.DiscImages public byte[] ReadSectors(ulong sectorAddress, uint length, uint track) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectors(trk.TrackStartSector + sectorAddress, length); } public byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectorsTag(trk.TrackStartSector + sectorAddress, length, tag); } public byte[] ReadSectorLong(ulong sectorAddress) @@ -736,7 +900,14 @@ namespace DiscImageChef.DiscImages public byte[] ReadSectorLong(ulong sectorAddress, uint track) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return ReadSectorLong(trk.TrackStartSector + sectorAddress); } public byte[] ReadSectorsLong(ulong sectorAddress, uint length) @@ -746,7 +917,18 @@ namespace DiscImageChef.DiscImages public byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return ReadSectorsLong(trk.TrackStartSector + sectorAddress, length); } public List GetSessionTracks(Session session) @@ -766,7 +948,14 @@ namespace DiscImageChef.DiscImages public bool? VerifySector(ulong sectorAddress, uint track) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + return VerifySector(trk.TrackStartSector + sectorAddress); } public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, @@ -778,7 +967,18 @@ namespace DiscImageChef.DiscImages public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, out List unknownLbas) { - throw new NotImplementedException(); + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + throw new FeatureNotPresentImageException("Feature not present in image"); + + Track trk = Tracks.FirstOrDefault(t => t.TrackSequence == track); + if(trk.TrackSequence != track) + throw new ArgumentOutOfRangeException(nameof(track), "Track does not exist in disc image"); + + if(trk.TrackStartSector + sectorAddress + length > trk.TrackEndSector + 1) + throw new ArgumentOutOfRangeException(nameof(length), + $"Requested more sectors ({length + sectorAddress}) than present in track ({trk.TrackEndSector - trk.TrackStartSector + 1}), won't cross tracks"); + + return VerifySectors(trk.TrackStartSector + sectorAddress, length, out failingLbas, out unknownLbas); } public bool? VerifyMediaImage() @@ -837,7 +1037,13 @@ namespace DiscImageChef.DiscImages DicConsole.DebugWriteLine("DiscImageChef format plugin", "Got a shift of {0} for {1} sectors per block", shift, oldSectorsPerBlock); - imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + imageInfo = new ImageInfo + { + MediaType = mediaType, + SectorSize = sectorSize, + Sectors = sectors, + XmlMediaType = GetXmlMediaType(mediaType) + }; try { imageStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } catch(IOException e) @@ -906,6 +1112,8 @@ namespace DiscImageChef.DiscImages mediaTags = new Dictionary(); checksumProvider = SHA256.Create(); deduplicationTable = new Dictionary(); + trackIsrcs = new Dictionary(); + trackFlags = new Dictionary(); IsWriting = true; ErrorMessage = null; @@ -970,7 +1178,7 @@ namespace DiscImageChef.DiscImages dataType = DataType.UserData, offset = (ulong)imageStream.Position }); - + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(currentBlockHeader)); structureBytes = new byte[Marshal.SizeOf(currentBlockHeader)]; Marshal.StructureToPtr(currentBlockHeader, structurePointer, true); @@ -1055,6 +1263,12 @@ namespace DiscImageChef.DiscImages public bool SetTracks(List tracks) { + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + ErrorMessage = "Unsupported feature"; + return false; + } + if(!IsWriting) { ErrorMessage = "Tried to write on a non-writable image"; @@ -1245,6 +1459,72 @@ namespace DiscImageChef.DiscImages index.Add(idxEntry); } + if(imageInfo.XmlMediaType == XmlMediaType.OpticalDisc && Tracks != null && Tracks.Count > 0) + { + List trackEntries = new List(); + foreach(Track track in Tracks) + { + trackFlags.TryGetValue((byte)track.TrackSequence, out byte flags); + trackIsrcs.TryGetValue((byte)track.TrackSequence, out string isrc); + + if((flags & (int)CdFlags.DataTrack) == 0 && track.TrackType != TrackType.Audio) + flags += (byte)CdFlags.DataTrack; + + trackEntries.Add(new TrackEntry + { + sequence = (byte)track.TrackSequence, + type = track.TrackType, + start = (long)track.TrackStartSector, + end = (long)track.TrackEndSector, + pregap = (long)track.TrackPregap, + session = (byte)track.TrackSession, + isrc = isrc, + flags = flags + }); + } + + if(trackEntries.Count > 0) + { + blockStream = new MemoryStream(); + + foreach(TrackEntry entry in trackEntries) + { + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(entry)); + structureBytes = new byte[Marshal.SizeOf(entry)]; + Marshal.StructureToPtr(entry, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + blockStream.Write(structureBytes, 0, structureBytes.Length); + } + + Crc64Context.Data(blockStream.ToArray(), out byte[] trksCrc); + TracksHeader trkHeader = new TracksHeader + { + identifier = BlockType.TracksBlock, + entries = (ushort)trackEntries.Count, + crc64 = BitConverter.ToUInt64(trksCrc, 0) + }; + + DicConsole.DebugWriteLine("DiscImageChef format plugin", "Writing tracks to position {0}", + imageStream.Position); + + index.Add(new IndexEntry + { + blockType = BlockType.TracksBlock, + dataType = DataType.NoData, + offset = (ulong)imageStream.Position + }); + + structurePointer = Marshal.AllocHGlobal(Marshal.SizeOf(trkHeader)); + structureBytes = new byte[Marshal.SizeOf(trkHeader)]; + Marshal.StructureToPtr(trkHeader, structurePointer, true); + Marshal.Copy(structurePointer, structureBytes, 0, structureBytes.Length); + Marshal.FreeHGlobal(structurePointer); + imageStream.Write(structureBytes, 0, structureBytes.Length); + imageStream.Write(blockStream.ToArray(), 0, (int)blockStream.Length); + } + } + MetadataBlock metadataBlock = new MetadataBlock(); blockStream = new MemoryStream(); blockStream.Write(new byte[Marshal.SizeOf(metadataBlock)], 0, Marshal.SizeOf(metadataBlock)); @@ -1489,18 +1769,85 @@ namespace DiscImageChef.DiscImages return true; } - // TODO: Implement public bool WriteSectorTag(byte[] data, ulong sectorAddress, SectorTagType tag) { - ErrorMessage = "Writing sectors with tags is not yet implemented."; - return false; + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + Track track = new Track(); + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: + if(imageInfo.XmlMediaType != XmlMediaType.OpticalDisc) + { + ErrorMessage = "Incorrect tag for disk type"; + return false; + } + + track = Tracks.FirstOrDefault(trk => sectorAddress >= trk.TrackStartSector && + sectorAddress <= trk.TrackEndSector); + if(track.TrackSequence == 0) + { + ErrorMessage = $"Can't found track containing {sectorAddress}"; + return false; + } + + break; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + { + if(data.Length != 1) + { + ErrorMessage = "Incorrect data size for track flags"; + return false; + } + + trackFlags.Add((byte)track.TrackSequence, data[0]); + + return true; + } + case SectorTagType.CdTrackIsrc: + { + if(data != null) trackIsrcs.Add((byte)track.TrackSequence, Encoding.UTF8.GetString(data)); + return true; + } + default: throw new NotImplementedException(); + } } - // TODO: Implement public bool WriteSectorsTag(byte[] data, ulong sectorAddress, uint length, SectorTagType tag) { - ErrorMessage = "Writing sectors with tags is not yet implemented."; - return false; + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + switch(tag) + { + case SectorTagType.CdTrackFlags: + case SectorTagType.CdTrackIsrc: return WriteSectorTag(data, sectorAddress, tag); + default: throw new NotImplementedException(); + } } static XmlMediaType GetXmlMediaType(MediaType type) @@ -1860,7 +2207,8 @@ namespace DiscImageChef.DiscImages DeDuplicationTable = 0x48544444, Index = 0x48584449, GeometryBlock = 0x4D4F4547, - MetadataBlock = 0x5444545D + MetadataBlock = 0x5444545D, + TracksBlock = 0x534B5254 } /// Header, at start of file @@ -1975,6 +2323,7 @@ namespace DiscImageChef.DiscImages public uint sectorsPerTrack; } + /// Metadata block, contains metadata [StructLayout(LayoutKind.Sequential, Pack = 1)] struct MetadataBlock { @@ -2035,5 +2384,30 @@ namespace DiscImageChef.DiscImages /// Length in bytes of the null-terminated UTF-16LE creator string public uint driveFirmwareRevisionLength; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TracksHeader + { + /// Identifier, + public BlockType identifier; + /// How many entries follow this header + public ushort entries; + /// CRC64-ECMA of the block + public ulong crc64; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + struct TrackEntry + { + public byte sequence; + public TrackType type; + public long start; + public long end; + public long pregap; + public byte session; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string isrc; + public byte flags; + } } } \ No newline at end of file diff --git a/DiscImageChef.DiscImages/Enums.cs b/DiscImageChef.DiscImages/Enums.cs index 3eaa33db..5a8826b5 100644 --- a/DiscImageChef.DiscImages/Enums.cs +++ b/DiscImageChef.DiscImages/Enums.cs @@ -37,20 +37,20 @@ namespace DiscImageChef.DiscImages /// /// Track (as partitioning element) types. /// - public enum TrackType + public enum TrackType : byte { /// Audio track - Audio, + Audio = 0, /// Data track (not any of the below defined ones) - Data, + Data = 1, /// Data track, compact disc mode 1 - CdMode1, + CdMode1 = 2, /// Data track, compact disc mode 2, formless - CdMode2Formless, + CdMode2Formless = 3, /// Data track, compact disc mode 2, form 1 - CdMode2Form1, + CdMode2Form1 = 4, /// Data track, compact disc mode 2, form 2 - CdMode2Form2 + CdMode2Form2 = 5 } ///