diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj
index 002f38b28..448a6ab0f 100644
--- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj
+++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj
@@ -77,6 +77,7 @@
+
@@ -125,7 +126,7 @@
-
+
diff --git a/DiscImageChef.DiscImages/SaveDskF.cs b/DiscImageChef.DiscImages/SaveDskF.cs
index cccf38ae0..a9ef5f0c7 100644
--- a/DiscImageChef.DiscImages/SaveDskF.cs
+++ b/DiscImageChef.DiscImages/SaveDskF.cs
@@ -5,11 +5,11 @@
// Filename : SaveDskF.cs
// Author(s) : Natalia Portillo
//
-// Component : Component
+// Component : Disc image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
-// Description
+// Manages IBM SaveDskF disk images.
//
// --[ License ] --------------------------------------------------------------
//
@@ -29,13 +29,550 @@
// ----------------------------------------------------------------------------
// Copyright © 2011-2017 Natalia Portillo
// ****************************************************************************/
+
using System;
-namespace DiscImageChef.DiscImages
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using DiscImageChef.CommonTypes;
+using DiscImageChef.Console;
+using DiscImageChef.Filters;
+
+namespace DiscImageChef.ImagePlugins
{
- public class SaveDskF
+ public class SaveDskF : ImagePlugin
{
+ #region Internal Structures
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SaveDskFHeader
+ {
+ /// 0x00 magic number
+ public ushort magic;
+ /// 0x02 media type from FAT
+ public ushort mediaType;
+ /// 0x04 bytes per sector
+ public ushort sectorSize;
+ /// 0x06 sectors per cluster - 1
+ public byte clusterMask;
+ /// 0x07 log2(cluster / sector)
+ public byte clusterShift;
+ /// 0x08 reserved sectors
+ public ushort reservedSectors;
+ /// 0x0A copies of FAT
+ public byte fatCopies;
+ /// 0x0B entries in root directory
+ public ushort rootEntries;
+ /// 0x0D first cluster
+ public ushort firstCluster;
+ /// 0x0F clusters present in image
+ public ushort clustersCopied;
+ /// 0x11 sectors per FAT
+ public byte sectorsPerFat;
+ /// 0x12 sector number of root directory
+ public ushort rootDirectorySector;
+ /// 0x14 sum of all image bytes
+ public uint checksum;
+ /// 0x18 cylinders
+ public ushort cylinders;
+ /// 0x1A heads
+ public ushort heads;
+ /// 0x1C sectors per track
+ public ushort sectorsPerTrack;
+ /// 0x1E always zero
+ public uint padding;
+ /// 0x22 sectors present in image
+ public ushort sectorsCopied;
+ /// 0x24 offset to comment
+ public ushort commentOffset;
+ /// 0x26 offset to data
+ public ushort dataOffset;
+ }
+ #endregion Internal Structures
+
+ #region Internal Constants
+ const ushort SDF_MAGIC_OLD = 0x58AA;
+ const ushort SDF_MAGIC = 0x59AA;
+ const ushort SDF_MAGIC_COMPRESSED = 0x5AAA;
+ #endregion Internal Constants
+
+ #region Internal variables
+ SaveDskFHeader header;
+ long sectors;
+ uint calculatedChk;
+ byte[] decodedDisk;
+ #endregion Internal variables
+
public SaveDskF()
{
+ Name = "IBM SaveDskF";
+ PluginUUID = new Guid("288CE058-1A51-4034-8C45-5A256CAE1461");
+ 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)
+ {
+ Stream stream = imageFilter.GetDataForkStream();
+ stream.Seek(0, SeekOrigin.Begin);
+ if(stream.Length < 41)
+ return false;
+
+ byte[] hdr = new byte[40];
+ stream.Read(hdr, 0, 40);
+
+ header = new SaveDskFHeader();
+ IntPtr hdrPtr = Marshal.AllocHGlobal(40);
+ Marshal.Copy(hdr, 0, hdrPtr, 40);
+ header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader));
+ Marshal.FreeHGlobal(hdrPtr);
+
+ return (header.magic == SDF_MAGIC || header.magic == SDF_MAGIC_COMPRESSED || header.magic == SDF_MAGIC_OLD) &&
+ header.fatCopies <= 2 && header.padding == 0 && header.commentOffset < stream.Length && header.dataOffset < stream.Length;
+ }
+
+ public override bool OpenImage(Filter imageFilter)
+ {
+ Stream stream = imageFilter.GetDataForkStream();
+ stream.Seek(0, SeekOrigin.Begin);
+
+ byte[] hdr = new byte[40];
+ byte[] cmt;
+
+ stream.Read(hdr, 0, 40);
+ header = new SaveDskFHeader();
+ IntPtr hdrPtr = Marshal.AllocHGlobal(40);
+ Marshal.Copy(hdr, 0, hdrPtr, 40);
+ header = (SaveDskFHeader)Marshal.PtrToStructure(hdrPtr, typeof(SaveDskFHeader));
+ Marshal.FreeHGlobal(hdrPtr);
+
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.magic = 0x{0:X4}", header.magic);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.mediaType = 0x{0:X2}", header.mediaType);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorSize = {0}", header.sectorSize);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterMask = {0}", header.clusterMask);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.clusterShift = {0}", header.clusterShift);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.reservedSectors = {0}", header.reservedSectors);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.fatCopies = {0}", header.fatCopies);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.rootEntries = {0}", header.rootEntries);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.firstCluster = {0}", header.firstCluster);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.clustersCopied = {0}", header.clustersCopied);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.checksum = 0x{0:X8}", header.checksum);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.cylinders = {0}", header.cylinders);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.heads = {0}", header.heads);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.padding = {0}", header.padding);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.sectorsCopied = {0}", header.sectorsCopied);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.commentOffset = {0}", header.commentOffset);
+ DicConsole.DebugWriteLine("SaveDskF plugin", "header.dataOffset = {0}", header.dataOffset);
+
+ if(header.dataOffset == 0 && header.magic == SDF_MAGIC_OLD)
+ header.dataOffset = 512;
+
+ cmt = new byte[header.dataOffset - header.commentOffset];
+ stream.Seek(header.commentOffset, SeekOrigin.Begin);
+ stream.Read(cmt, 0, cmt.Length);
+ if(cmt.Length > 1)
+ ImageInfo.imageComments = StringHandlers.CToString(cmt, Encoding.GetEncoding("ibm437"));
+
+ calculatedChk = 0;
+ stream.Seek(0, SeekOrigin.Begin);
+
+ int b = 0;
+ do
+ {
+ b = stream.ReadByte();
+ if(b >= 0)
+ calculatedChk += (uint)b;
+ }
+ while(b >= 0);
+
+ // In case there is omitted data
+ sectors = (header.sectorsPerTrack) * header.heads * header.cylinders;
+
+ DicConsole.DebugWriteLine("SaveDskF plugin", "Calculated checksum = 0x{0:X8}, {1}", calculatedChk, calculatedChk == header.checksum);
+
+ ImageInfo.imageApplication = "SaveDskF";
+ ImageInfo.imageCreationTime = imageFilter.GetCreationTime();
+ ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime();
+ ImageInfo.imageName = imageFilter.GetFilename();
+ ImageInfo.imageSize = (ulong)(stream.Length - header.dataOffset);
+ ImageInfo.sectors = (ulong)(header.sectorsPerTrack * header.heads * header.cylinders);
+ ImageInfo.sectorSize = header.sectorSize;
+
+ ImageInfo.mediaType = MediaType.Unknown;
+ switch(header.cylinders)
+ {
+ case 40:
+ switch(header.heads)
+ {
+ case 1:
+ switch(header.sectorsPerTrack)
+ {
+ case 8:
+ ImageInfo.mediaType = MediaType.DOS_525_SS_DD_8;
+ break;
+ case 9:
+ ImageInfo.mediaType = MediaType.DOS_525_SS_DD_9;
+ break;
+ }
+ break;
+ case 2:
+ switch(header.sectorsPerTrack)
+ {
+ case 8:
+ ImageInfo.mediaType = MediaType.DOS_525_DS_DD_8;
+ break;
+ case 9:
+ ImageInfo.mediaType = MediaType.DOS_525_DS_DD_9;
+ break;
+ }
+ break;
+ }
+ break;
+ case 80:
+ switch(header.heads)
+ {
+ case 1:
+ switch(header.sectorsPerTrack)
+ {
+ case 8:
+ ImageInfo.mediaType = MediaType.DOS_35_SS_DD_8;
+ break;
+ case 9:
+ ImageInfo.mediaType = MediaType.DOS_35_SS_DD_9;
+ break;
+ }
+ break;
+ case 2:
+ switch(header.sectorsPerTrack)
+ {
+ case 8:
+ ImageInfo.mediaType = MediaType.DOS_35_DS_DD_8;
+ break;
+ case 9:
+ ImageInfo.mediaType = MediaType.DOS_35_DS_DD_9;
+ break;
+ case 15:
+ ImageInfo.mediaType = MediaType.DOS_525_HD;
+ break;
+ case 18:
+ ImageInfo.mediaType = MediaType.DOS_35_HD;
+ break;
+ case 23:
+ ImageInfo.mediaType = MediaType.XDF_35;
+ break;
+ case 36:
+ ImageInfo.mediaType = MediaType.DOS_35_ED;
+ break;
+ }
+ break;
+ }
+ break;
+ default:
+ ImageInfo.mediaType = MediaType.Unknown;
+ break;
+ }
+
+ ImageInfo.xmlMediaType = XmlMediaType.BlockMedia;
+
+ DicConsole.VerboseWriteLine("SaveDskF image contains a disk of type {0}", ImageInfo.mediaType);
+ if(!string.IsNullOrEmpty(ImageInfo.imageComments))
+ DicConsole.VerboseWriteLine("SaveDskF comments: {0}", ImageInfo.imageComments);
+
+ // TODO: Support compressed images
+ if(header.magic == SDF_MAGIC_COMPRESSED)
+ throw new FeatureSupportedButNotImplementedImageException("Compressed SaveDskF images are not supported.");
+
+ // SaveDskF only ommits ending clusters, leaving no gaps behind, so reading all data we have...
+ stream.Seek(header.dataOffset, SeekOrigin.Begin);
+ decodedDisk = new byte[ImageInfo.sectors * ImageInfo.sectorSize];
+ stream.Read(decodedDisk, 0, (int)(stream.Length - header.dataOffset));
+
+ return true;
+ }
+
+ public override bool? VerifySector(ulong sectorAddress)
+ {
+ return null;
+ }
+
+ public override bool? VerifySector(ulong sectorAddress, uint track)
+ {
+ return null;
+ }
+
+ public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs)
+ {
+ FailingLBAs = new List();
+ UnknownLBAs = new List();
+
+ for(ulong i = sectorAddress; i < sectorAddress + length; i++)
+ UnknownLBAs.Add(i);
+
+ return null;
+ }
+
+ public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs)
+ {
+ FailingLBAs = new List();
+ UnknownLBAs = new List();
+
+ for(ulong i = sectorAddress; i < sectorAddress + length; i++)
+ UnknownLBAs.Add(i);
+
+ return null;
+ }
+
+ public override bool? VerifyMediaImage()
+ {
+ return calculatedChk == header.checksum;
+ }
+
+ 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[] ReadSector(ulong sectorAddress)
+ {
+ return ReadSectors(sectorAddress, 1);
+ }
+
+ public override byte[] ReadSectors(ulong sectorAddress, uint length)
+ {
+ if(sectorAddress > ImageInfo.sectors - 1)
+ throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found");
+
+ if(sectorAddress + length > ImageInfo.sectors)
+ throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available");
+
+ byte[] buffer = new byte[length * ImageInfo.sectorSize];
+
+ Array.Copy(decodedDisk, (int)sectorAddress * ImageInfo.sectorSize, buffer, 0, length * ImageInfo.sectorSize);
+
+ return buffer;
+ }
+
+ public override string GetImageFormat()
+ {
+ return "IBM SaveDskF";
+ }
+
+ public override string GetImageVersion()
+ {
+ return ImageInfo.imageVersion;
+ }
+
+ public override string GetImageApplication()
+ {
+ return ImageInfo.imageApplication;
+ }
+
+ public override string GetImageApplicationVersion()
+ {
+ return ImageInfo.imageApplicationVersion;
+ }
+
+ public override DateTime GetImageCreationTime()
+ {
+ return ImageInfo.imageCreationTime;
+ }
+
+ public override DateTime GetImageLastModificationTime()
+ {
+ return ImageInfo.imageLastModificationTime;
+ }
+
+ public override string GetImageName()
+ {
+ return ImageInfo.imageName;
+ }
+
+ public override MediaType GetMediaType()
+ {
+ return ImageInfo.mediaType;
+ }
+ public override string GetImageCreator()
+ {
+ return ImageInfo.imageCreator;
+ }
+
+ 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 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;
+ }
+ #endregion Public methods
+
+ #region Unsupported features
+
+ public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag)
+ {
+ throw new FeatureUnsupportedImageException("Feature not supported by image format");
+ }
+
+ public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag)
+ {
+ throw new FeatureUnsupportedImageException("Feature not supported by image format");
+ }
+
+ public override byte[] ReadSectorLong(ulong sectorAddress)
+ {
+ throw new FeatureUnsupportedImageException("Feature not supported by image format");
+ }
+
+ public override byte[] ReadSectorsLong(ulong sectorAddress, uint length)
+ {
+ throw new FeatureUnsupportedImageException("Feature not supported by image format");
+ }
+
+ public override byte[] ReadDiskTag(MediaTagType tag)
+ {
+ 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