diff --git a/DiscImageChef.Core/Sidecar/BlockMedia.cs b/DiscImageChef.Core/Sidecar/BlockMedia.cs index 5d478bd08..9bd882096 100644 --- a/DiscImageChef.Core/Sidecar/BlockMedia.cs +++ b/DiscImageChef.Core/Sidecar/BlockMedia.cs @@ -36,13 +36,18 @@ // ****************************************************************************/ // //$Id$ -using System.Collections.Generic; -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using DiscImageChef.CommonTypes; +using DiscImageChef.Console; using DiscImageChef.Decoders.PCMCIA; using DiscImageChef.Filesystems; +using DiscImageChef.Filters; using DiscImageChef.ImagePlugins; using Schemas; +using Tuple = DiscImageChef.Decoders.PCMCIA.Tuple; namespace DiscImageChef.Core { @@ -413,6 +418,200 @@ namespace DiscImageChef.Core } } + // TODO: This is more of a hack, redo it planned for >4.0 + #region SuperCardPro + string scpFilePath = Path.Combine(Path.GetDirectoryName(imagePath), + Path.GetFileNameWithoutExtension(imagePath) + ".scp"); + ImagePlugins.SuperCardPro scpImage = new SuperCardPro(); + Filters.ZZZNoFilter scpFilter = new ZZZNoFilter(); + scpFilter.Open(scpFilePath); + + string scpFormat = null; + + switch(image.ImageInfo.mediaType) + { + case MediaType.Apple32SS: + case MediaType.Apple32DS: + scpFormat = "Apple GCR (DOS 3.2)"; + break; + case MediaType.Apple33SS: + case MediaType.Apple33DS: + scpFormat = "Apple GCR (DOS 3.3)"; + break; + case MediaType.AppleSonySS: + case MediaType.AppleSonyDS: + scpFormat = "Apple GCR (Sony)"; + break; + case MediaType.AppleFileWare: + scpFormat = "Apple GCR (Twiggy)"; + break; + case MediaType.DOS_525_SS_DD_9: + case MediaType.DOS_525_DS_DD_8: + case MediaType.DOS_525_DS_DD_9: + case MediaType.DOS_525_HD: + case MediaType.DOS_35_SS_DD_8: + case MediaType.DOS_35_SS_DD_9: + case MediaType.DOS_35_DS_DD_8: + case MediaType.DOS_35_DS_DD_9: + case MediaType.DOS_35_HD: + case MediaType.DOS_35_ED: + case MediaType.DMF: + case MediaType.DMF_82: + case MediaType.XDF_525: + case MediaType.XDF_35: + case MediaType.IBM53FD_256: + case MediaType.IBM53FD_512: + case MediaType.IBM53FD_1024: + case MediaType.RX02: + case MediaType.RX03: + case MediaType.RX50: + case MediaType.ACORN_525_SS_DD_40: + case MediaType.ACORN_525_SS_DD_80: + case MediaType.ACORN_525_DS_DD: + case MediaType.ACORN_35_DS_DD: + case MediaType.ACORN_35_DS_HD: + case MediaType.ATARI_525_ED: + case MediaType.ATARI_525_DD: + case MediaType.ATARI_35_SS_DD: + case MediaType.ATARI_35_DS_DD: + case MediaType.ATARI_35_SS_DD_11: + case MediaType.ATARI_35_DS_DD_11: + case MediaType.DOS_525_SS_DD_8: + case MediaType.NEC_8_DD: + case MediaType.NEC_525_SS: + case MediaType.NEC_525_DS: + case MediaType.NEC_525_HD: + case MediaType.NEC_35_HD_8: + case MediaType.NEC_35_HD_15: + case MediaType.NEC_35_TD: + case MediaType.FDFORMAT_525_DD: + case MediaType.FDFORMAT_525_HD: + case MediaType.FDFORMAT_35_DD: + case MediaType.FDFORMAT_35_HD: + case MediaType.Apricot_35: + case MediaType.CompactFloppy: + scpFormat = "IBM MFM"; + break; + case MediaType.ATARI_525_SD: + case MediaType.NEC_8_SD: + case MediaType.ACORN_525_SS_SD_40: + case MediaType.ACORN_525_SS_SD_80: + case MediaType.RX01: + case MediaType.IBM23FD: + case MediaType.IBM33FD_128: + case MediaType.IBM33FD_256: + case MediaType.IBM33FD_512: + case MediaType.IBM43FD_128: + case MediaType.IBM43FD_256: + scpFormat = "IBM FM"; + break; + case MediaType.CBM_35_DD: + scpFormat = "Commodore MFM"; + break; + case MediaType.CBM_AMIGA_35_HD: + case MediaType.CBM_AMIGA_35_DD: + scpFormat = "Amiga MFM"; + break; + case MediaType.CBM_1540: + case MediaType.CBM_1540_Ext: + case MediaType.CBM_1571: + scpFormat = "Commodore GCR"; + break; + case MediaType.SHARP_525: + case MediaType.SHARP_525_9: + case MediaType.SHARP_35: + break; + case MediaType.SHARP_35_9: + break; + case MediaType.ECMA_99_15: + case MediaType.ECMA_99_26: + case MediaType.ECMA_100: + case MediaType.ECMA_125: + case MediaType.ECMA_147: + case MediaType.ECMA_99_8: + scpFormat = "ISO MFM"; + break; + case MediaType.ECMA_54: + case MediaType.ECMA_59: + case MediaType.ECMA_66: + case MediaType.ECMA_69_8: + case MediaType.ECMA_69_15: + case MediaType.ECMA_69_26: + case MediaType.ECMA_70: + case MediaType.ECMA_78: + case MediaType.ECMA_78_2: + scpFormat = "ISO FM"; + break; + default: + scpFormat = "Unknown"; + break; + } + + if(image.ImageInfo.heads <= 2 && File.Exists(scpFilePath) && scpImage.IdentifyImage(scpFilter)) + { + try + { + scpImage.OpenImage(scpFilter); + } + catch(NotImplementedException) + { + } + + if((image.ImageInfo.heads == 2 && scpImage.header.heads == 0) || + (image.ImageInfo.heads == 1 && (scpImage.header.heads == 1 || scpImage.header.heads == 2))) + { + if(scpImage.header.end + 1 >= image.ImageInfo.cylinders) + { + ImageType scpImageType = new ImageType(); + scpImageType.format = "SuperCardPro"; + scpImageType.Value = Path.GetFileName(scpFilePath); + List scpBlockTrackTypes = new List(); + long currentSector = 0; + Stream scpStream = scpFilter.GetDataForkStream(); + + for(byte t = scpImage.header.start; t <= scpImage.header.end; t++) + { + BlockTrackType scpBlockTrackType = new BlockTrackType(); + scpBlockTrackType.Cylinder = t / image.ImageInfo.heads; + scpBlockTrackType.Head = t % image.ImageInfo.heads; + scpBlockTrackType.Image = scpImageType; + scpBlockTrackType.Image.offset = scpImage.header.offsets[t]; + + if(scpBlockTrackType.Cylinder < image.ImageInfo.cylinders) + { + scpBlockTrackType.StartSector = currentSector; + currentSector += image.ImageInfo.sectorsPerTrack; + scpBlockTrackType.EndSector = currentSector - 1; + scpBlockTrackType.Sectors = image.ImageInfo.sectorsPerTrack; + scpBlockTrackType.BytesPerSector = (int)image.ImageInfo.sectorSize; + scpBlockTrackType.Format = scpFormat; + } + + if(scpImage.tracks.TryGetValue(t, out SuperCardPro.TrackHeader scpTrack)) + { + byte[] trackContents = + new byte[(scpTrack.entries.Last().dataOffset + + scpTrack.entries.Last().trackLength) - scpImage.header.offsets[t] + 1]; + scpStream.Position = scpImage.header.offsets[t]; + scpStream.Read(trackContents, 0, trackContents.Length); + scpBlockTrackType.Size = trackContents.Length; + scpBlockTrackType.Checksums = Checksum.GetChecksums(trackContents).ToArray(); + } + + scpBlockTrackTypes.Add(scpBlockTrackType); + } + + sidecar.BlockMedia[0].Track = + scpBlockTrackTypes.OrderBy(t => t.Cylinder).ThenBy(t => t.Head).ToArray(); + } + else + DicConsole.ErrorWriteLine("SuperCardPro image do not contain same number of tracks ({0}) than disk image ({1}), ignoring...", scpImage.header.end + 1, image.ImageInfo.cylinders); + } + else + DicConsole.ErrorWriteLine("SuperCardPro image do not contain same number of heads ({0}) than disk image ({1}), ignoring...", 2, image.ImageInfo.heads); + } + #endregion + // TODO: Implement support for getting CHS from SCSI mode pages } } diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index 513ed0f91..464c1c21d 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -1,4 +1,4 @@ - + Debug @@ -58,6 +58,7 @@ + diff --git a/DiscImageChef.DiscImages/SuperCardPro.cs b/DiscImageChef.DiscImages/SuperCardPro.cs new file mode 100644 index 000000000..fd7c10734 --- /dev/null +++ b/DiscImageChef.DiscImages/SuperCardPro.cs @@ -0,0 +1,516 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : SuperCardPro.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages SuperCardPro disk 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.Filters; + +namespace DiscImageChef.ImagePlugins +{ + public class SuperCardPro : ImagePlugin + { + #region Internal Structures + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ScpHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] signature; + public byte version; + public ScpDiskType type; + public byte revolutions; + public byte start; + public byte end; + public ScpFlags flags; + public byte bitCellEncoding; + public byte heads; + public byte reserved; + public uint checksum; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 168)] + public uint[] offsets; + } + + public struct TrackHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] signature; + public byte trackNumber; + public TrackEntry[] entries; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TrackEntry + { + public uint indexTime; + public uint trackLength; + public uint dataOffset; + } + #endregion Internal Structures + + #region Internal Constants + /// + /// SuperCardPro header signature: "SCP" + /// + readonly byte[] ScpSignature = { 0x53, 0x43, 0x50 }; + /// + /// SuperCardPro track header signature: "TRK" + /// + readonly byte[] TrkSignature = { 0x54, 0x52, 0x4B }; + + public enum ScpDiskType : byte + { + Commodore64 = 0x00, + CommodoreAmiga = 0x04, + AtariFMSS = 0x10, + AtariFMDS = 0x11, + AtariFSEx = 0x12, + AtariSTSS = 0x14, + AtariSTDS = 0x15, + AppleII = 0x20, + AppleIIPro = 0x21, + Apple400K = 0x24, + Apple800K = 0x25, + Apple144 = 0x26, + PC360K = 0x30, + PC720K = 0x31, + PC12M = 0x32, + PC144M = 0x33, + TandySSSD = 0x40, + TandySSDD = 0x41, + TandyDSSD = 0x42, + TandyDSDD = 0x43, + Ti994A = 0x50, + RolandD20 = 0x60 + } + + [Flags] + public enum ScpFlags : byte + { + /// + /// If set flux starts at index pulse + /// + Index = 0x00, + /// + /// If set drive is 96tpi + /// + Tpi = 0x02, + /// + /// If set drive is 360rpm + /// + Rpm = 0x04, + /// + /// If set image contains normalized data + /// + Normalized = 0x08, + /// + /// If set image is read/write capable + /// + Writable = 0x10 + } + #endregion Internal Constants + + #region Internal variables + // TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0 + public ScpHeader header; + public Dictionary tracks; + #endregion Internal variables + + public SuperCardPro() + { + Name = "SuperCardPro"; + PluginUUID = new Guid("C5D3182E-1D45-4767-A205-E6E5C83444DC"); + ImageInfo = new ImageInfo() + { + readableSectorTags = new List(), + readableMediaTags = new List(), + imageHasPartitions = false, + imageHasSessions = false, + imageVersion = null, + imageApplication = null, + imageApplicationVersion = null, + imageCreator = null, + imageComments = null, + mediaManufacturer = null, + mediaModel = null, + mediaSerialNumber = null, + mediaBarcode = null, + mediaPartNumber = null, + mediaSequence = 0, + lastMediaSequence = 0, + driveManufacturer = null, + driveModel = null, + driveSerialNumber = null, + driveFirmwareRevision = null + }; + } + + #region Public methods + public override bool IdentifyImage(Filter imageFilter) + { + header = new ScpHeader(); + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < Marshal.SizeOf(header)) + return false; + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + stream.Read(hdr, 0, Marshal.SizeOf(header)); + + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(header)); + header = (ScpHeader)Marshal.PtrToStructure(hdrPtr, typeof(ScpHeader)); + Marshal.FreeHGlobal(hdrPtr); + + return ScpSignature.SequenceEqual(header.signature); + } + + public override bool OpenImage(Filter imageFilter) + { + header = new ScpHeader(); + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < Marshal.SizeOf(header)) + return false; + + byte[] hdr = new byte[Marshal.SizeOf(header)]; + stream.Read(hdr, 0, Marshal.SizeOf(header)); + + IntPtr hdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(header)); + Marshal.Copy(hdr, 0, hdrPtr, Marshal.SizeOf(header)); + header = (ScpHeader)Marshal.PtrToStructure(hdrPtr, typeof(ScpHeader)); + Marshal.FreeHGlobal(hdrPtr); + + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.signature = \"{0}\"", StringHandlers.CToString(header.signature)); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.version = {0}.{1}", (header.version & 0xF0) >> 4, header.version & 0xF); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.type = {0}", header.type); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.revolutions = {0}", header.revolutions); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.start = {0}", header.start); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.end = {0}", header.end); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.flags = {0}", header.flags); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.bitCellEncoding = {0}", header.bitCellEncoding); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.heads = {0}", header.heads); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.reserved = {0}", header.reserved); + DicConsole.DebugWriteLine("SuperCardPro plugin", "header.checksum = 0x{0:X8}", header.checksum); + + if(!ScpSignature.SequenceEqual(header.signature)) + return false; + + tracks = new Dictionary(); + + for(byte t = header.start; t <= header.end; t++) + { + if(t >= header.offsets.Length) + break; + + stream.Position = header.offsets[t]; + TrackHeader trk = new TrackHeader(); + trk.signature = new byte[3]; + trk.entries = new TrackEntry[header.revolutions]; + stream.Read(trk.signature, 0, trk.signature.Length); + trk.trackNumber = (byte)stream.ReadByte(); + + if(!trk.signature.SequenceEqual(TrkSignature)) + { + DicConsole.DebugWriteLine("SuperCardPro plugin", "Track header at {0} contains incorrect signature.", header.offsets[t]); + continue; + } + + if(trk.trackNumber != t) + { + DicConsole.DebugWriteLine("SuperCardPro plugin", "Track number at {0} should be {1} but is {2}.", header.offsets[t], t, trk.trackNumber); + continue; + } + + DicConsole.DebugWriteLine("SuperCardPro plugin", "Found track {0} at {1}.", t, header.offsets[t]); + + for(byte r = 0; r < header.revolutions; r++) + { + byte[] rev = new byte[Marshal.SizeOf(typeof(TrackEntry))]; + stream.Read(rev, 0, Marshal.SizeOf(typeof(TrackEntry))); + + IntPtr revPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TrackEntry))); + Marshal.Copy(rev, 0, revPtr, Marshal.SizeOf(typeof(TrackEntry))); + trk.entries[r] = (TrackEntry)Marshal.PtrToStructure(revPtr, typeof(TrackEntry)); + Marshal.FreeHGlobal(revPtr); + // De-relative offsets + trk.entries[r].dataOffset += header.offsets[t]; + } + + tracks.Add(t, trk); + } + + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + 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) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override byte[] ReadSectorLong(ulong sectorAddress) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override string GetImageFormat() + { + return "SuperCardPro"; + } + + 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; + } + + // TODO: Check if it exists. If so, read it. + 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.mediaSerialNumber; + } + + 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 bool? VerifySector(ulong sectorAddress) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + throw new NotImplementedException("Flux decoding is not yet implemented."); + } + + public override bool? VerifyMediaImage() + { + throw new NotImplementedException(); + } + #endregion Public methods + + #region Unsupported features + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetPartitions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetTracks() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + #endregion Unsupported features + } +} \ No newline at end of file diff --git a/README.md b/README.md index 999fcb922..311804648 100644 --- a/README.md +++ b/README.md @@ -210,4 +210,9 @@ Supported filters * GZip * LZip * MacBinary I, II, III -* XZ \ No newline at end of file +* XZ + +Partially supported disk image formats +====================================== +This disk image formats cannot be read, but their contents can be checksummed on sidecar creation +* SuperCardPro \ No newline at end of file