diff --git a/DiscImageChef.CommonTypes/MediaType.cs b/DiscImageChef.CommonTypes/MediaType.cs index a7dce359b..8bb2a2df1 100644 --- a/DiscImageChef.CommonTypes/MediaType.cs +++ b/DiscImageChef.CommonTypes/MediaType.cs @@ -448,6 +448,11 @@ namespace DiscImageChef.CommonTypes FDFORMAT_35_HD, #endregion FDFORMAT, non-standard floppy formats + #region Apricot ACT standard floppy formats + /// 3.5", DS, DD, 77 tracks, 8 spt, 512 bytes/sector, MFM + Apricot_35, + #endregion Apricot ACT standard floppy formats + #region OnStream ADR ADR2120, ADR260, diff --git a/DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs b/DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs index cae1cb7c2..92f878718 100644 --- a/DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs +++ b/DiscImageChef.CommonTypes/MediaTypeFromSCSI.cs @@ -189,6 +189,8 @@ namespace DiscImageChef.CommonTypes return MediaType.DOS_35_SS_DD_9; case 610: return MediaType.IBM33FD_512; + case 616: + return MediaType.Apricot_35; case 640: return MediaType.DOS_35_SS_DD_8; case 720: diff --git a/DiscImageChef.DiscImages/Anex86.cs b/DiscImageChef.DiscImages/Anex86.cs index bf6ef486b..afb3ade0b 100644 --- a/DiscImageChef.DiscImages/Anex86.cs +++ b/DiscImageChef.DiscImages/Anex86.cs @@ -184,6 +184,15 @@ namespace DiscImageChef.ImagePlugins break; } break; + case 512: + switch(fdihdr.spt) + { + case 8: + if(fdihdr.heads == 1) + ImageInfo.mediaType = MediaType.Apricot_35; + break; + } + break; case 1024: switch(fdihdr.spt) { diff --git a/DiscImageChef.DiscImages/Apridisk.cs b/DiscImageChef.DiscImages/Apridisk.cs new file mode 100644 index 000000000..dc5d5858e --- /dev/null +++ b/DiscImageChef.DiscImages/Apridisk.cs @@ -0,0 +1,619 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : Apridisk.cs +// Author(s) : Natalia Portillo +// +// Component : Component +// +// --[ Description ] ---------------------------------------------------------- +// +// Description +// +// --[ 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 Apridisk : ImagePlugin + { + #region Internal enumerations + enum RecordType : uint + { + Deleted = 0xE31D0000, + Sector = 0xE31D0001, + Comment = 0xE31D0002, + Creator = 0xE31D0003, + } + + enum CompressType : ushort + { + Uncompresed = 0x9E90, + Compressed = 0x3E5A, + } + #endregion + + #region Internal constants + readonly byte[] signature = { + 0x41, 0x43, 0x54, 0x20, 0x41, 0x70, 0x72, 0x69, 0x63, 0x6F, 0x74, 0x20, 0x64, 0x69, 0x73, 0x6B, + 0x20, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x1A, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + #endregion + + #region Internal structures + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ApridiskRecord + { + public RecordType type; + public CompressType compression; + public ushort headerSize; + public uint dataSize; + public byte head; + public byte sector; + public ushort cylinder; + } + #endregion + + // Cylinder by head, sector data matrix + byte[][][][] sectorsData; + + public Apridisk() + { + Name = "ACT Apricot Disk Image"; + PluginUUID = new Guid("43408CF3-6DB3-449F-A779-2B0E497C5B14"); + 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 + }; + } + + public override bool IdentifyImage(Filter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < signature.Length) + return false; + + byte[] sig_b = new byte[signature.Length]; + stream.Read(sig_b, 0, signature.Length); + + return sig_b.SequenceEqual(signature); + } + + public override bool OpenImage(Filter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + // Skip signature + stream.Seek(signature.Length, SeekOrigin.Begin); + + int totalCylinders = -1; + int totalHeads = -1; + int maxSector = - 1; + int recordSize = Marshal.SizeOf(typeof(ApridiskRecord)); + + // Count cylinders + while(stream.Position < stream.Length) + { + ApridiskRecord record = new ApridiskRecord(); + byte[] rec_b = new byte[recordSize]; + stream.Read(rec_b, 0, recordSize); + + GCHandle handle = GCHandle.Alloc(rec_b, GCHandleType.Pinned); + record = (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); + handle.Free(); + + switch(record.type) + { + // Deleted record, just skip it + case RecordType.Deleted: + DicConsole.DebugWriteLine("Apridisk plugin", "Found deleted record at {0}", stream.Position); + stream.Seek((record.headerSize - recordSize) + record.dataSize, SeekOrigin.Current); + break; + case RecordType.Comment: + DicConsole.DebugWriteLine("Apridisk plugin", "Found comment record at {0}", stream.Position); + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + byte[] comment_b = new byte[record.dataSize]; + stream.Read(comment_b, 0, comment_b.Length); + ImageInfo.imageComments = StringHandlers.CToString(comment_b); + DicConsole.DebugWriteLine("Apridisk plugin", "Comment: \"{0}\"", ImageInfo.imageComments); + break; + case RecordType.Creator: + DicConsole.DebugWriteLine("Apridisk plugin", "Found creator record at {0}", stream.Position); + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + byte[] creator_b = new byte[record.dataSize]; + stream.Read(creator_b, 0, creator_b.Length); + ImageInfo.imageCreator = StringHandlers.CToString(creator_b); + DicConsole.DebugWriteLine("Apridisk plugin", "Creator: \"{0}\"", ImageInfo.imageCreator); + break; + case RecordType.Sector: + if(record.compression != CompressType.Compressed && record.compression != CompressType.Uncompresed) + throw new ImageNotSupportedException(string.Format("Found record with unknown compression type 0x{0:X4} at {1}", (ushort)record.compression, stream.Position)); + + DicConsole.DebugWriteLine("Apridisk plugin", "Found {4} sector record at {0} for cylinder {1} head {2} sector {3}", stream.Position, record.cylinder, record.head, + record.sector, record.compression == CompressType.Compressed ? "compressed" : "uncompressed"); + + if(record.cylinder > totalCylinders) + totalCylinders = record.cylinder; + if(record.head > totalHeads) + totalHeads = record.head; + if(record.sector > maxSector) + maxSector = record.sector; + + stream.Seek((record.headerSize - recordSize) + record.dataSize, SeekOrigin.Current); + break; + default: + throw new ImageNotSupportedException(string.Format("Found record with unknown type 0x{0:X8} at {1}", (uint)record.type, stream.Position)); + } + } + + totalCylinders++; + totalHeads++; + + if(totalCylinders <= 0 || totalHeads <= 0) + throw new ImageNotSupportedException("No cylinders or heads found"); + + sectorsData = new byte[totalCylinders][][][]; + // Total sectors per track + uint[][] spts = new uint[totalCylinders][]; + + ImageInfo.cylinders = (ushort)totalCylinders; + ImageInfo.heads = (byte)totalHeads; + + DicConsole.DebugWriteLine("Apridisk plugin", "Found {0} cylinders and {1} heads with a maximum sector number of {2}", totalCylinders, totalHeads, maxSector); + + // Create heads + for(int i = 0; i < totalCylinders; i++) + { + sectorsData[i] = new byte[totalHeads][][]; + spts[i] = new uint[totalHeads]; + + for(int j = 0; j < totalHeads; j++) + sectorsData[i][j] = new byte[maxSector + 1][]; + } + + ImageInfo.sectorSize = uint.MaxValue; + + ulong headersizes = 0; + + // Read sectors + stream.Seek(signature.Length, SeekOrigin.Begin); + while(stream.Position < stream.Length) + { + ApridiskRecord record = new ApridiskRecord(); + byte[] rec_b = new byte[recordSize]; + stream.Read(rec_b, 0, recordSize); + + GCHandle handle = GCHandle.Alloc(rec_b, GCHandleType.Pinned); + record = (ApridiskRecord)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ApridiskRecord)); + handle.Free(); + + switch(record.type) + { + // Not sector record, just skip it + case RecordType.Deleted: + case RecordType.Comment: + case RecordType.Creator: + stream.Seek((record.headerSize - recordSize) + record.dataSize, SeekOrigin.Current); + headersizes += record.headerSize + record.dataSize; + break; + case RecordType.Sector: + stream.Seek(record.headerSize - recordSize, SeekOrigin.Current); + + byte[] data = new byte[record.dataSize]; + stream.Read(data, 0, data.Length); + + spts[record.cylinder][record.head]++; + uint realLength = record.dataSize; + + if(record.compression == CompressType.Compressed) + realLength = Decompress(data, out sectorsData[record.cylinder][record.head][record.sector]); + else + sectorsData[record.cylinder][record.head][record.sector] = data; + + if(realLength < ImageInfo.sectorSize) + ImageInfo.sectorSize = realLength; + + headersizes += record.headerSize + record.dataSize; + + break; + } + } + + DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} bytes per sector", ImageInfo.sectorSize); + + // Count sectors per track + uint spt = uint.MaxValue; + for(ushort cyl = 0; cyl < ImageInfo.cylinders; cyl++) + { + for(ushort head = 0; head < ImageInfo.heads; head++) + { + if(spts[cyl][head] < spt) + spt = spts[cyl][head]; + } + } + ImageInfo.sectorsPerTrack = spt; + + DicConsole.DebugWriteLine("Apridisk plugin", "Found a minimum of {0} sectors per track", ImageInfo.sectorsPerTrack); + + if(ImageInfo.cylinders == 77 && ImageInfo.heads == 1 && ImageInfo.sectorsPerTrack == 8) + ImageInfo.mediaType = MediaType.Apricot_35; + else if(ImageInfo.cylinders == 80 && ImageInfo.heads == 1 && ImageInfo.sectorsPerTrack == 9) + ImageInfo.mediaType = MediaType.DOS_35_SS_DD_9; + else if(ImageInfo.cylinders == 80 && ImageInfo.heads == 2 && ImageInfo.sectorsPerTrack == 9) + ImageInfo.mediaType = MediaType.DOS_35_DS_DD_9; + + ImageInfo.imageSize = (ulong)stream.Length - headersizes; + ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); + ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); + ImageInfo.imageName = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + ImageInfo.sectors = ImageInfo.cylinders * ImageInfo.heads * ImageInfo.sectorsPerTrack; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + + /* + FileStream debugFs = new FileStream("debug.img", FileMode.CreateNew, FileAccess.Write); + for(ulong i = 0; i < ImageInfo.sectors; i++) + debugFs.Write(ReadSector(i), 0, (int)ImageInfo.sectorSize); + debugFs.Dispose(); + */ + + return false; + } + + static uint Decompress(byte[] compressed, out byte[] decompressed) + { + int readp = 0; + ushort blklen; + uint u_len = 0; + int c_len = compressed.Length; + MemoryStream buffer = new MemoryStream(); + + u_len = 0; + + while(c_len >= 3) + { + blklen = BitConverter.ToUInt16(compressed, readp); + readp += 2; + + for(int i = 0; i < blklen; i++) + buffer.WriteByte(compressed[readp]); + + u_len += blklen; + readp++; + c_len -= 3; + } + + decompressed = buffer.ToArray(); + return u_len; + } + + public override bool ImageHasPartitions() + { + return false; + } + + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + + public override string GetImageFormat() + { + return "ACT Apricot disk image"; + } + + 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 MediaType GetMediaType() + { + return ImageInfo.mediaType; + } + + public override byte[] ReadSector(ulong sectorAddress) + { + (ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress); + + if(cylinder >= sectorsData.Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(head >= sectorsData[cylinder].Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sector > sectorsData[cylinder][head].Length) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + return sectorsData[cylinder][head][sector]; + } + + 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"); + + MemoryStream buffer = new MemoryStream(); + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + buffer.Write(sector, 0, sector.Length); + } + + return buffer.ToArray(); + } + + (ushort cylinder, byte head, byte sector) LbaToChs(ulong lba) + { + ushort cylinder = (ushort)(lba / (ImageInfo.heads * ImageInfo.sectorsPerTrack)); + byte head = (byte)((lba / ImageInfo.sectorsPerTrack) % ImageInfo.heads); + byte sector = (byte)((lba % ImageInfo.sectorsPerTrack) + 1); + + return (cylinder, head, sector); + } + + #region Unsupported features + + public override byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + 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[] ReadSectorsTag(ulong sectorAddress, uint length, 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[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + 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 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 string GetMediaManufacturer() + { + return null; + } + + public override string GetMediaModel() + { + return null; + } + + public override string GetMediaSerialNumber() + { + return null; + } + + public override string GetMediaBarcode() + { + return null; + } + + public override string GetMediaPartNumber() + { + return null; + } + + public override int GetMediaSequence() + { + return 0; + } + + public override int GetLastDiskSequence() + { + return 0; + } + + public override string GetDriveManufacturer() + { + return null; + } + + public override string GetDriveModel() + { + return null; + } + + public override string GetDriveSerialNumber() + { + return null; + } + + 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) + { + return null; + } + + 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) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + for(ulong i = 0; i < ImageInfo.sectors; i++) + UnknownLBAs.Add(i); + return null; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifyMediaImage() + { + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/CopyQM.cs b/DiscImageChef.DiscImages/CopyQM.cs index ea404902f..f923f3a6a 100644 --- a/DiscImageChef.DiscImages/CopyQM.cs +++ b/DiscImageChef.DiscImages/CopyQM.cs @@ -393,7 +393,9 @@ namespace DiscImageChef.ImagePlugins ImageInfo.mediaType = MediaType.ACORN_35_DS_DD; else if(header.heads == 2 && header.totalCylinders == 77 && header.sectorsPerTrack == 8 && header.sectorSize == 1024) ImageInfo.mediaType = MediaType.SHARP_35; - else + else if(header.heads == 1 && header.totalCylinders == 77 && header.sectorsPerTrack == 8 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.Apricot_35; + else ImageInfo.mediaType = MediaType.Unknown; break; default: diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index ad1b2401a..6a17e5939 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -97,6 +97,7 @@ + diff --git a/DiscImageChef.DiscImages/DriDiskCopy.cs b/DiscImageChef.DiscImages/DriDiskCopy.cs index 7f7a6b1a7..b21266620 100644 --- a/DiscImageChef.DiscImages/DriDiskCopy.cs +++ b/DiscImageChef.DiscImages/DriDiskCopy.cs @@ -344,6 +344,8 @@ namespace DiscImageChef.ImagePlugins ImageInfo.mediaType = MediaType.DOS_35_SS_DD_8; else if(ImageInfo.heads == 2 && ImageInfo.cylinders == 80 && ImageInfo.sectorsPerTrack == 5 && ImageInfo.sectorSize == 1024) ImageInfo.mediaType = MediaType.ACORN_35_DS_DD; + else if(ImageInfo.heads == 1 && ImageInfo.cylinders == 77 && ImageInfo.sectorsPerTrack == 8 && ImageInfo.sectorSize == 512) + ImageInfo.mediaType = MediaType.Apricot_35; else ImageInfo.mediaType = MediaType.Unknown; break; diff --git a/DiscImageChef.DiscImages/IMD.cs b/DiscImageChef.DiscImages/IMD.cs index 04c363d48..d54193979 100644 --- a/DiscImageChef.DiscImages/IMD.cs +++ b/DiscImageChef.DiscImages/IMD.cs @@ -308,6 +308,8 @@ namespace DiscImageChef.ImagePlugins ImageInfo.mediaType = MediaType.ACORN_35_DS_DD; else if(ImageInfo.heads == 2 && ImageInfo.cylinders == 82 && ImageInfo.sectorsPerTrack == 10 && ImageInfo.sectorSize == 512) ImageInfo.mediaType = MediaType.FDFORMAT_35_DD; + else if(ImageInfo.heads == 1 && ImageInfo.cylinders == 77 && ImageInfo.sectorsPerTrack == 8 && ImageInfo.sectorSize == 512) + ImageInfo.mediaType = MediaType.Apricot_35; break; case TransferRate.FiveHundredMFM: if(ImageInfo.heads == 2 && ImageInfo.cylinders == 80 && ImageInfo.sectorsPerTrack == 18 && ImageInfo.sectorSize == 512) diff --git a/DiscImageChef.DiscImages/SaveDskF.cs b/DiscImageChef.DiscImages/SaveDskF.cs index f6daa559e..c48cfad43 100644 --- a/DiscImageChef.DiscImages/SaveDskF.cs +++ b/DiscImageChef.DiscImages/SaveDskF.cs @@ -252,6 +252,19 @@ namespace DiscImageChef.ImagePlugins break; } break; + case 77: + switch(header.heads) + { + case 1: + switch(header.sectorsPerTrack) + { + case 8: + ImageInfo.mediaType = MediaType.Apricot_35; + break; + } + break; + } + break; case 80: switch(header.heads) { diff --git a/DiscImageChef.DiscImages/TeleDisk.cs b/DiscImageChef.DiscImages/TeleDisk.cs index 2f17599ae..acb0cb71c 100644 --- a/DiscImageChef.DiscImages/TeleDisk.cs +++ b/DiscImageChef.DiscImages/TeleDisk.cs @@ -949,7 +949,9 @@ namespace DiscImageChef.ImagePlugins { switch(totalDiskSize) { - case 327680: + case 315392: + return MediaType.Apricot_35; + case 327680: return MediaType.DOS_35_SS_DD_8; case 368640: return MediaType.DOS_35_SS_DD_9; diff --git a/DiscImageChef.DiscImages/ZZZRawImage.cs b/DiscImageChef.DiscImages/ZZZRawImage.cs index 8644b123c..3d474d871 100644 --- a/DiscImageChef.DiscImages/ZZZRawImage.cs +++ b/DiscImageChef.DiscImages/ZZZRawImage.cs @@ -452,6 +452,11 @@ namespace DiscImageChef.ImagePlugins ImageInfo.heads = 2; ImageInfo.sectorsPerTrack = 9; break; + case MediaType.Apricot_35: + ImageInfo.cylinders = 77; + ImageInfo.heads = 1; + ImageInfo.sectorsPerTrack = 8; + break; case MediaType.DOS_35_ED: ImageInfo.cylinders = 80; ImageInfo.heads = 2; @@ -962,6 +967,8 @@ namespace DiscImageChef.ImagePlugins return MediaType.IBM33FD_256; case 306432: return MediaType.IBM33FD_512; + case 315392: + return MediaType.Apricot_35; case 325632: return MediaType.ECMA_70; case 327680: diff --git a/DiscImageChef.Metadata/Dimensions.cs b/DiscImageChef.Metadata/Dimensions.cs index 562454d91..980a863af 100644 --- a/DiscImageChef.Metadata/Dimensions.cs +++ b/DiscImageChef.Metadata/Dimensions.cs @@ -112,6 +112,7 @@ namespace DiscImageChef.Metadata case CommonTypes.MediaType.Floptical: case CommonTypes.MediaType.HiFD: case CommonTypes.MediaType.UHD144: + case CommonTypes.MediaType.Apricot_35: // According to ECMA-100 et al dmns.Height = 94; dmns.HeightSpecified = true; diff --git a/DiscImageChef.Metadata/MediaType.cs b/DiscImageChef.Metadata/MediaType.cs index 429a33bf7..c38318497 100644 --- a/DiscImageChef.Metadata/MediaType.cs +++ b/DiscImageChef.Metadata/MediaType.cs @@ -406,6 +406,10 @@ namespace DiscImageChef.Metadata DiscType = "3.5\" floppy"; DiscSubType = "IBM extra-density"; break; + case CommonTypes.MediaType.Apricot_35: + DiscType = "3.5\" floppy"; + DiscSubType = "Apricot double-density, single-sided, 77 tracks"; + break; case CommonTypes.MediaType.DMF: DiscType = "3.5\" floppy"; DiscSubType = "Microsoft DMF"; diff --git a/README.md b/README.md index 297d9bcae..903c121b1 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Supported disk image formats * Apple II nibble images (NIB) * Apple New Disk Image Format (NDIF, requires Resource Fork) * Apple Universal Disk Image Format (UDIF), including obsolete (previous than DiskCopy 6) versions +* Apridisk disk image formats (for ACT Apricot disks) * Anex86 disk images (.FDI for floppies, .HDI for hard disks) * BlindWrite 4 TOC files (.BWT/.BWI/.BWS) * BlindWrite 5/6 TOC files (.B5T/.B5I and .B6T/.B6I)