mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Introduces constant fields for respective debug module names, replacing the hardcoded ones. This is done to standardize the naming convention, reduce redundancy and potentially avoid any typos or name mismatches across the project. This change makes the code more maintainable and easier to update in case module names need to be changed.
1263 lines
56 KiB
C#
1263 lines
56 KiB
C#
// /***************************************************************************
|
|
// Aaru Data Preservation Suite
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Filename : Info.cs
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
//
|
|
// Component : CP/M filesystem plugin.
|
|
//
|
|
// --[ Description ] ----------------------------------------------------------
|
|
//
|
|
// Identifies the CP/M filesystem and shows information.
|
|
//
|
|
// --[ 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
// Copyright © 2011-2023 Natalia Portillo
|
|
// ****************************************************************************/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Aaru.CommonTypes;
|
|
using Aaru.CommonTypes.AaruMetadata;
|
|
using Aaru.CommonTypes.Enums;
|
|
using Aaru.CommonTypes.Interfaces;
|
|
using Aaru.Console;
|
|
using Aaru.Helpers;
|
|
using Partition = Aaru.CommonTypes.Partition;
|
|
|
|
namespace Aaru.Filesystems;
|
|
|
|
public sealed partial class CPM
|
|
{
|
|
/// <inheritdoc />
|
|
public bool Identify(IMediaImage imagePlugin, Partition partition)
|
|
{
|
|
// This will only continue on devices with a chance to have ever been used by CP/M while failing on all others
|
|
// It's ugly, but will stop a lot of false positives
|
|
switch(imagePlugin.Info.MediaType)
|
|
{
|
|
case MediaType.Unknown:
|
|
case MediaType.Apple32SS:
|
|
case MediaType.Apple32DS:
|
|
case MediaType.Apple33SS:
|
|
case MediaType.Apple33DS:
|
|
case MediaType.DOS_525_SS_DD_8:
|
|
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.IBM23FD:
|
|
case MediaType.IBM33FD_128:
|
|
case MediaType.IBM33FD_256:
|
|
case MediaType.IBM33FD_512:
|
|
case MediaType.IBM43FD_128:
|
|
case MediaType.IBM43FD_256:
|
|
case MediaType.IBM53FD_256:
|
|
case MediaType.IBM53FD_512:
|
|
case MediaType.IBM53FD_1024:
|
|
case MediaType.RX01:
|
|
case MediaType.RX02:
|
|
case MediaType.RX03:
|
|
case MediaType.RX50:
|
|
case MediaType.ACORN_525_SS_SD_40:
|
|
case MediaType.ACORN_525_SS_SD_80:
|
|
case MediaType.ACORN_525_SS_DD_40:
|
|
case MediaType.ACORN_525_SS_DD_80:
|
|
case MediaType.ACORN_525_DS_DD:
|
|
case MediaType.ATARI_525_SD:
|
|
case MediaType.ATARI_525_ED:
|
|
case MediaType.ATARI_525_DD:
|
|
case MediaType.CBM_35_DD:
|
|
case MediaType.CBM_1540:
|
|
case MediaType.CBM_1540_Ext:
|
|
case MediaType.CBM_1571:
|
|
case MediaType.NEC_8_SD:
|
|
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.SHARP_525_9:
|
|
case MediaType.SHARP_35_9:
|
|
case MediaType.ECMA_99_8:
|
|
case MediaType.ECMA_99_15:
|
|
case MediaType.ECMA_99_26:
|
|
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:
|
|
case MediaType.Apricot_35:
|
|
case MediaType.CompactFloppy:
|
|
case MediaType.DemiDiskette:
|
|
case MediaType.QuickDisk:
|
|
case MediaType.Wafer:
|
|
case MediaType.ZXMicrodrive:
|
|
case MediaType.AppleProfile:
|
|
case MediaType.AppleWidget:
|
|
case MediaType.AppleHD20:
|
|
case MediaType.RA60:
|
|
case MediaType.RA80:
|
|
case MediaType.RA81:
|
|
case MediaType.RC25:
|
|
case MediaType.RD31:
|
|
case MediaType.RD32:
|
|
case MediaType.RD51:
|
|
case MediaType.RD52:
|
|
case MediaType.RD53:
|
|
case MediaType.RD54:
|
|
case MediaType.RK06:
|
|
case MediaType.RK06_18:
|
|
case MediaType.RK07:
|
|
case MediaType.RK07_18:
|
|
case MediaType.RM02:
|
|
case MediaType.RM03:
|
|
case MediaType.RM05:
|
|
case MediaType.RP02:
|
|
case MediaType.RP02_18:
|
|
case MediaType.RP03:
|
|
case MediaType.RP03_18:
|
|
case MediaType.RP04:
|
|
case MediaType.RP04_18:
|
|
case MediaType.RP05:
|
|
case MediaType.RP05_18:
|
|
case MediaType.RP06:
|
|
case MediaType.RP06_18:
|
|
case MediaType.GENERIC_HDD:
|
|
case MediaType.FlashDrive:
|
|
case MediaType.MetaFloppy_Mod_I:
|
|
case MediaType.MetaFloppy_Mod_II: break;
|
|
default: return false;
|
|
}
|
|
|
|
// This will try to identify a CP/M filesystem
|
|
// However as it contains no identification marks whatsoever it's more something of trial-and-error
|
|
// As anything can happen, better try{}catch{} than sorry ;)
|
|
try
|
|
{
|
|
byte[] sector;
|
|
ulong sectorSize;
|
|
ulong firstDirectorySector;
|
|
byte[] directory = null;
|
|
_workingDefinition = null;
|
|
_label = null;
|
|
|
|
// Try Amstrad superblock
|
|
ErrorNumber errno;
|
|
|
|
if(!_cpmFound)
|
|
{
|
|
// Read CHS = {0,0,1}
|
|
errno = imagePlugin.ReadSector(0 + partition.Start, out sector);
|
|
|
|
if(errno == ErrorNumber.NoError)
|
|
{
|
|
int amsSbOffset = 0;
|
|
|
|
uint sig1 = BitConverter.ToUInt32(sector, 0x2B);
|
|
uint sig2 = BitConverter.ToUInt32(sector, 0x33) & 0x00FFFFFF;
|
|
uint sig3 = BitConverter.ToUInt32(sector, 0x7C);
|
|
|
|
// PCW16 extended boot record
|
|
if(sig1 == 0x4D2F5043 &&
|
|
sig2 == 0x004B5344 &&
|
|
sig3 == sig1)
|
|
amsSbOffset = 0x80;
|
|
|
|
// Read the superblock
|
|
AmstradSuperBlock amsSb =
|
|
Marshal.ByteArrayToStructureLittleEndian<AmstradSuperBlock>(sector, amsSbOffset, 16);
|
|
|
|
// Check that format byte and sidedness indicate the same number of sizes
|
|
if((amsSb.format == 0 && (amsSb.sidedness & 0x02) == 0) ||
|
|
(amsSb.format == 2 && (amsSb.sidedness & 0x02) == 1) ||
|
|
(amsSb.format == 2 && (amsSb.sidedness & 0x02) == 2))
|
|
{
|
|
// Calculate device limits
|
|
ulong sides = (ulong)(amsSb.format == 0 ? 1 : 2);
|
|
ulong sectorCount = (ulong)(amsSb.tps * amsSb.spt * (byte)sides);
|
|
sectorSize = (ulong)(128 << amsSb.psh);
|
|
|
|
// Compare device limits from superblock to real limits
|
|
if(sectorSize == imagePlugin.Info.SectorSize &&
|
|
sectorCount == imagePlugin.Info.Sectors)
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector = (ulong)(amsSb.off * amsSb.spt);
|
|
|
|
// Build a DiscParameterBlock
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = sectorCount == 1440 ? (byte)0xF0 : (byte)0xC0,
|
|
spt = amsSb.spt,
|
|
bsh = amsSb.bsh
|
|
};
|
|
|
|
for(int i = 0; i < _dpb.bsh; i++)
|
|
_dpb.blm += (byte)Math.Pow(2, i);
|
|
|
|
if(sectorCount >= 1440)
|
|
{
|
|
_dpb.cks = 0x40;
|
|
_dpb.drm = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
_dpb.cks = 0x10;
|
|
_dpb.drm = 0x3F;
|
|
}
|
|
|
|
_dpb.dsm = 0; // I don't care
|
|
_dpb.exm = sectorCount == 2880 ? (byte)1 : (byte)0;
|
|
_dpb.off = amsSb.off;
|
|
_dpb.psh = amsSb.psh;
|
|
|
|
for(int i = 0; i < _dpb.psh; i++)
|
|
_dpb.phm += (byte)Math.Pow(2, i);
|
|
|
|
_dpb.spt = (ushort)(amsSb.spt * (sectorSize / 128));
|
|
uint directoryLength = (uint)(((ulong)_dpb.drm + 1) * 32 / sectorSize);
|
|
|
|
imagePlugin.ReadSectors(firstDirectorySector + partition.Start, directoryLength,
|
|
out directory);
|
|
|
|
// Build a CP/M disk definition
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = amsSb.tps,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "Amstrad PCW superblock",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = amsSb.spt,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[amsSb.spt]
|
|
}
|
|
};
|
|
|
|
for(int si = 0; si < amsSb.spt; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
if(amsSb.format == 2)
|
|
{
|
|
_workingDefinition.order = (amsSb.sidedness & 0x02) switch
|
|
{
|
|
1 => "SIDES",
|
|
2 => "CYLINDERS",
|
|
_ => null
|
|
};
|
|
|
|
_workingDefinition.side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[amsSb.spt]
|
|
};
|
|
|
|
for(int si = 0; si < amsSb.spt; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
else
|
|
_workingDefinition.order = null;
|
|
|
|
_workingDefinition.skew = 2;
|
|
_workingDefinition.sofs = 0;
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_Amstrad_superblock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try CP/M-86 superblock for hard disks
|
|
if(!_cpmFound)
|
|
{
|
|
// Read CHS = {0,0,4}
|
|
errno = imagePlugin.ReadSector(3 + partition.Start, out sector);
|
|
|
|
if(errno == ErrorNumber.NoError)
|
|
{
|
|
ushort sum = 0;
|
|
|
|
// Sum of all 16-bit words that make this sector must be 0
|
|
for(int i = 0; i < sector.Length; i += 2)
|
|
sum += BitConverter.ToUInt16(sector, i);
|
|
|
|
// It may happen that there is a corrupted superblock
|
|
// Better to ignore corrupted than to false positive the rest
|
|
if(sum == 0)
|
|
{
|
|
// Read the superblock
|
|
HardDiskSuperBlock hddSb = Marshal.ByteArrayToStructureLittleEndian<HardDiskSuperBlock>(sector);
|
|
|
|
// Calculate volume size
|
|
sectorSize = (ulong)(hddSb.recordsPerSector * 128);
|
|
ulong sectorsInPartition = (ulong)(hddSb.cylinders * hddSb.heads * hddSb.sectorsPerTrack);
|
|
|
|
ulong startingSector =
|
|
(ulong)(((hddSb.firstCylinder * hddSb.heads) + hddSb.heads) * hddSb.sectorsPerTrack);
|
|
|
|
// If volume size corresponds with working partition (this variant will be inside MBR partitioning)
|
|
if(sectorSize == imagePlugin.Info.SectorSize &&
|
|
startingSector == partition.Start &&
|
|
sectorsInPartition + partition.Start <= partition.End)
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector = (ulong)(hddSb.off * hddSb.sectorsPerTrack);
|
|
|
|
// Build a DiscParameterBlock
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = (byte)hddSb.al0,
|
|
al1 = (byte)hddSb.al1,
|
|
blm = hddSb.blm,
|
|
bsh = hddSb.bsh,
|
|
cks = hddSb.cks,
|
|
drm = hddSb.drm,
|
|
dsm = hddSb.dsm,
|
|
exm = hddSb.exm,
|
|
off = hddSb.off,
|
|
|
|
// Needed?
|
|
phm = 0,
|
|
|
|
// Needed?
|
|
psh = 0,
|
|
spt = hddSb.spt
|
|
};
|
|
|
|
uint directoryLength = (uint)(((ulong)_dpb.drm + 1) * 32 / sectorSize);
|
|
|
|
imagePlugin.ReadSectors(firstDirectorySector + partition.Start, directoryLength,
|
|
out directory);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_CPM_86_hard_disk_superblock);
|
|
|
|
// Build a CP/M disk definition
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "HIGH",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = hddSb.cylinders,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 hard disk superblock",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = hddSb.sectorsPerTrack,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[hddSb.sectorsPerTrack]
|
|
},
|
|
order = "SIDES",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[hddSb.sectorsPerTrack]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < hddSb.sectorsPerTrack; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < hddSb.spt; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try CP/M-86 format ID for floppies
|
|
if(!_cpmFound)
|
|
{
|
|
// Read CHS = {0,0,1}
|
|
errno = imagePlugin.ReadSector(0 + partition.Start, out sector);
|
|
|
|
if(errno == ErrorNumber.NoError)
|
|
{
|
|
byte formatByte;
|
|
|
|
// Check for alternate location of format ID
|
|
if(sector.Last() == 0x00 ||
|
|
sector.Last() == 0xFF)
|
|
if(sector[0x40] == 0x94 ||
|
|
sector[0x40] == 0x26)
|
|
formatByte = sector[0x40];
|
|
else
|
|
formatByte = sector.Last();
|
|
else
|
|
formatByte = sector.Last();
|
|
|
|
uint firstDirectorySector86 = 0;
|
|
|
|
// Check format ID
|
|
// If it is one of the known IDs, check disk size corresponds to the one we expect
|
|
// If so, build a DiscParameterBlock and a CP/M disk definition
|
|
// Will not work on over-formatted disks (40 cylinder volume on an 80 cylinder disk,
|
|
// something that happens a lot in IBM PC 5.25" disks)
|
|
switch((FormatByte)formatByte)
|
|
{
|
|
case FormatByte.k160:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 320 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 8;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0xC0,
|
|
al1 = 0,
|
|
blm = 7,
|
|
bsh = 3,
|
|
cks = 0x10,
|
|
drm = 0x3F,
|
|
dsm = 0x9B,
|
|
exm = 0,
|
|
off = 1,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 8 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 40,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 8,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[8]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 8; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.k320:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 640 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 16;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0x80,
|
|
al1 = 0,
|
|
blm = 0x0F,
|
|
bsh = 4,
|
|
cks = 0x10,
|
|
drm = 0x3F,
|
|
dsm = 0x9D,
|
|
exm = 1,
|
|
off = 2,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 8 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 40,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 8,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[8]
|
|
},
|
|
order = "SIDES",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[8]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 8; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 8; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.k360:
|
|
case FormatByte.k360Alt:
|
|
case FormatByte.k360Alt2:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 720 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 36;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0x80,
|
|
al1 = 0,
|
|
blm = 0x0F,
|
|
bsh = 4,
|
|
cks = 0x10,
|
|
drm = 0x3F,
|
|
dsm = 0, // Unknown. Needed?
|
|
exm = 1,
|
|
off = 4,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 9 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 40,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 9,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[9]
|
|
},
|
|
order = "SIDES",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[9]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.k720:
|
|
case FormatByte.k720Alt:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 1440 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 36;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0xF0,
|
|
al1 = 0,
|
|
blm = 0x0F,
|
|
bsh = 4,
|
|
cks = 0x40,
|
|
drm = 0xFF,
|
|
dsm = 0x15E,
|
|
exm = 0,
|
|
off = 4,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 9 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 80,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 9,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[9]
|
|
},
|
|
order = "SIDES",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[9]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.f720:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 1440 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 18;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0xF0,
|
|
al1 = 0,
|
|
blm = 0x0F,
|
|
bsh = 4,
|
|
cks = 0x40,
|
|
drm = 0xFF,
|
|
dsm = 0x162,
|
|
exm = 0,
|
|
off = 2,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 9 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 80,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 9,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[9]
|
|
},
|
|
order = "CYLINDERS",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[9]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 9; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.f1200:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 2400 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 30;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0xC0,
|
|
al1 = 0,
|
|
blm = 0x1F,
|
|
bsh = 5,
|
|
cks = 0x40,
|
|
drm = 0xFF,
|
|
dsm = 0x127,
|
|
exm = 1,
|
|
off = 2,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 15 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "HIGH",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 80,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 15,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[15]
|
|
},
|
|
order = "CYLINDERS",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[15]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 15; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 15; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
case FormatByte.f1440:
|
|
if(imagePlugin.Info is { SectorSize: 512, Sectors: 2880 })
|
|
{
|
|
_cpmFound = true;
|
|
firstDirectorySector86 = 36;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = 0xC0,
|
|
al1 = 0,
|
|
blm = 0x1F,
|
|
bsh = 5,
|
|
cks = 0x40,
|
|
drm = 0xFF,
|
|
dsm = 0x162,
|
|
exm = 1,
|
|
off = 2,
|
|
phm = 3,
|
|
psh = 2,
|
|
spt = 18 * 4
|
|
};
|
|
|
|
_workingDefinition = new CpmDefinition
|
|
{
|
|
al0 = _dpb.al0,
|
|
al1 = _dpb.al1,
|
|
bitrate = "LOW",
|
|
blm = _dpb.blm,
|
|
bsh = _dpb.bsh,
|
|
bytesPerSector = 512,
|
|
cylinders = 80,
|
|
drm = _dpb.drm,
|
|
dsm = _dpb.dsm,
|
|
encoding = "MFM",
|
|
evenOdd = false,
|
|
exm = _dpb.exm,
|
|
label = null,
|
|
comment = "CP/M-86 floppy identifier",
|
|
ofs = _dpb.off,
|
|
sectorsPerTrack = 18,
|
|
side1 = new Side
|
|
{
|
|
sideId = 0,
|
|
sectorIds = new int[18]
|
|
},
|
|
order = "CYLINDERS",
|
|
side2 = new Side
|
|
{
|
|
sideId = 1,
|
|
sectorIds = new int[18]
|
|
},
|
|
skew = 0,
|
|
sofs = 0
|
|
};
|
|
|
|
for(int si = 0; si < 18; si++)
|
|
_workingDefinition.side1.sectorIds[si] = si + 1;
|
|
|
|
for(int si = 0; si < 18; si++)
|
|
_workingDefinition.side2.sectorIds[si] = si + 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if(_cpmFound)
|
|
{
|
|
uint directoryLength = (uint)(((ulong)_dpb.drm + 1) * 32 / imagePlugin.Info.SectorSize);
|
|
|
|
imagePlugin.ReadSectors(firstDirectorySector86 + partition.Start, directoryLength,
|
|
out directory);
|
|
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Found_CPM_86_floppy_identifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
// One of the few CP/M filesystem marks has been found, try for correcteness checking the whole directory
|
|
if(_cpmFound)
|
|
{
|
|
if(CheckDir(directory))
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.First_directory_block_seems_correct);
|
|
|
|
return true;
|
|
}
|
|
|
|
_cpmFound = false;
|
|
}
|
|
|
|
// Try all definitions
|
|
if(!_cpmFound)
|
|
{
|
|
// Load all definitions
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Trying_to_load_definitions);
|
|
|
|
if(LoadDefinitions() &&
|
|
_definitions?.definitions != null &&
|
|
_definitions.definitions.Count > 0)
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Trying_all_known_definitions);
|
|
|
|
foreach(CpmDefinition def in from def in _definitions.definitions let sectors =
|
|
(ulong)(def.cylinders * def.sides * def.sectorsPerTrack)
|
|
where sectors == imagePlugin.Info.Sectors &&
|
|
def.bytesPerSector == imagePlugin.Info.SectorSize select def)
|
|
{
|
|
// Definition seems to describe current disk, at least, same number of volume sectors and bytes per sector
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Trying_definition_0, def.comment);
|
|
ulong offset;
|
|
|
|
if(def.sofs != 0)
|
|
offset = (ulong)def.sofs;
|
|
else
|
|
offset = (ulong)(def.ofs * def.sectorsPerTrack);
|
|
|
|
int dirLen = (def.drm + 1) * 32 / def.bytesPerSector;
|
|
|
|
if(def.sides == 1)
|
|
{
|
|
_sectorMask = new int[def.side1.sectorIds.Length];
|
|
|
|
for(int m = 0; m < _sectorMask.Length; m++)
|
|
_sectorMask[m] = def.side1.sectorIds[m] - def.side1.sectorIds[0];
|
|
}
|
|
else
|
|
{
|
|
// Head changes after every track
|
|
if(string.Compare(def.order, "SIDES", StringComparison.InvariantCultureIgnoreCase) == 0)
|
|
{
|
|
_sectorMask = new int[def.side1.sectorIds.Length + def.side2.sectorIds.Length];
|
|
|
|
for(int m = 0; m < def.side1.sectorIds.Length; m++)
|
|
_sectorMask[m] = def.side1.sectorIds[m] - def.side1.sectorIds[0];
|
|
|
|
// Skip first track (first side)
|
|
for(int m = 0; m < def.side2.sectorIds.Length; m++)
|
|
_sectorMask[m + def.side1.sectorIds.Length] =
|
|
def.side2.sectorIds[m] - def.side2.sectorIds[0] + def.side1.sectorIds.Length;
|
|
}
|
|
|
|
// Head changes after whole side
|
|
else if(string.Compare(def.order, "CYLINDERS",
|
|
StringComparison.InvariantCultureIgnoreCase) == 0)
|
|
{
|
|
for(int m = 0; m < def.side1.sectorIds.Length; m++)
|
|
_sectorMask[m] = def.side1.sectorIds[m] - def.side1.sectorIds[0];
|
|
|
|
// Skip first track (first side) and first track (second side)
|
|
for(int m = 0; m < def.side1.sectorIds.Length; m++)
|
|
_sectorMask[m + def.side1.sectorIds.Length] =
|
|
def.side1.sectorIds[m] - def.side1.sectorIds[0] + def.side1.sectorIds.Length +
|
|
def.side2.sectorIds.Length;
|
|
}
|
|
|
|
// TODO: Implement COLUMBIA ordering
|
|
else if(string.Compare(def.order, "COLUMBIA",
|
|
StringComparison.InvariantCultureIgnoreCase) == 0)
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME,
|
|
Localization.
|
|
Dont_know_how_to_handle_COLUMBIA_ordering_not_proceeding_with_this_definition);
|
|
|
|
continue;
|
|
}
|
|
|
|
// TODO: Implement EAGLE ordering
|
|
else if(string.Compare(def.order, "EAGLE", StringComparison.InvariantCultureIgnoreCase) ==
|
|
0)
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME,
|
|
Localization.
|
|
Don_know_how_to_handle_EAGLE_ordering_not_proceeding_with_this_definition);
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME,
|
|
Localization.
|
|
Unknown_order_type_0_not_proceeding_with_this_definition,
|
|
def.order);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Read the directory marked by this definition
|
|
var ms = new MemoryStream();
|
|
|
|
for(int p = 0; p < dirLen; p++)
|
|
{
|
|
errno =
|
|
imagePlugin.
|
|
ReadSector((ulong)((int)offset + (int)partition.Start + (p / _sectorMask.Length * _sectorMask.Length) + _sectorMask[p % _sectorMask.Length]),
|
|
out byte[] dirSector);
|
|
|
|
if(errno != ErrorNumber.NoError)
|
|
break;
|
|
|
|
ms.Write(dirSector, 0, dirSector.Length);
|
|
}
|
|
|
|
directory = ms.ToArray();
|
|
|
|
if(def.evenOdd)
|
|
AaruConsole.DebugWriteLine(MODULE_NAME,
|
|
Localization.
|
|
Definition_contains_EVEN_ODD_field_with_unknown_meaning_detection_may_be_wrong);
|
|
|
|
// Complement of the directory bytes if needed
|
|
if(def.complement)
|
|
for(int b = 0; b < directory.Length; b++)
|
|
directory[b] = (byte)(~directory[b] & 0xFF);
|
|
|
|
// Check the directory
|
|
if(CheckDir(directory))
|
|
{
|
|
AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Definition_0_has_a_correct_directory,
|
|
def.comment);
|
|
|
|
// Build a Disc Parameter Block
|
|
_workingDefinition = def;
|
|
|
|
_dpb = new DiscParameterBlock
|
|
{
|
|
al0 = (byte)def.al0,
|
|
al1 = (byte)def.al1,
|
|
blm = (byte)def.blm,
|
|
bsh = (byte)def.bsh,
|
|
|
|
// Needed?
|
|
cks = 0,
|
|
drm = (ushort)def.drm,
|
|
dsm = (ushort)def.dsm,
|
|
exm = (byte)def.exm,
|
|
off = (ushort)def.ofs,
|
|
spt = (ushort)(def.sectorsPerTrack * def.bytesPerSector / 128)
|
|
};
|
|
|
|
switch(def.bytesPerSector)
|
|
{
|
|
case 128:
|
|
_dpb.psh = 0;
|
|
_dpb.phm = 0;
|
|
|
|
break;
|
|
case 256:
|
|
_dpb.psh = 1;
|
|
_dpb.phm = 1;
|
|
|
|
break;
|
|
case 512:
|
|
_dpb.psh = 2;
|
|
_dpb.phm = 3;
|
|
|
|
break;
|
|
case 1024:
|
|
_dpb.psh = 3;
|
|
_dpb.phm = 7;
|
|
|
|
break;
|
|
case 2048:
|
|
_dpb.psh = 4;
|
|
_dpb.phm = 15;
|
|
|
|
break;
|
|
case 4096:
|
|
_dpb.psh = 5;
|
|
_dpb.phm = 31;
|
|
|
|
break;
|
|
case 8192:
|
|
_dpb.psh = 6;
|
|
_dpb.phm = 63;
|
|
|
|
break;
|
|
case 16384:
|
|
_dpb.psh = 7;
|
|
_dpb.phm = 127;
|
|
|
|
break;
|
|
case 32768:
|
|
_dpb.psh = 8;
|
|
_dpb.phm = 255;
|
|
|
|
break;
|
|
}
|
|
|
|
_cpmFound = true;
|
|
_workingDefinition = def;
|
|
|
|
return true;
|
|
}
|
|
|
|
_label = null;
|
|
_labelCreationDate = null;
|
|
_labelUpdateDate = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear class variables
|
|
_cpmFound = false;
|
|
_workingDefinition = null;
|
|
_dpb = null;
|
|
_label = null;
|
|
_standardTimestamps = false;
|
|
_thirdPartyTimestamps = false;
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
//throw ex;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void GetInformation(IMediaImage imagePlugin, Partition partition, Encoding encoding, out string information,
|
|
out FileSystem metadata)
|
|
{
|
|
information = "";
|
|
metadata = new FileSystem();
|
|
|
|
// As the identification is so complex, just call Identify() and relay on its findings
|
|
if(!Identify(imagePlugin, partition) ||
|
|
!_cpmFound ||
|
|
_workingDefinition == null ||
|
|
_dpb == null)
|
|
return;
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine(Localization.CPM_filesystem);
|
|
|
|
if(!string.IsNullOrEmpty(_workingDefinition.comment))
|
|
sb.AppendFormat(Localization.Identified_as_0, _workingDefinition.comment).AppendLine();
|
|
|
|
sb.AppendFormat(Localization.Volume_block_is_0_bytes, 128 << _dpb.bsh).AppendLine();
|
|
|
|
if(_dpb.dsm > 0)
|
|
sb.AppendFormat(Localization.Volume_contains_0_blocks_1_bytes, _dpb.dsm, _dpb.dsm * (128 << _dpb.bsh)).
|
|
AppendLine();
|
|
|
|
sb.AppendFormat(Localization.Volume_contains_0_directory_entries, _dpb.drm + 1).AppendLine();
|
|
|
|
if(_workingDefinition.sofs > 0)
|
|
sb.AppendFormat(Localization.Volume_reserves_0_sectors_for_system, _workingDefinition.sofs).AppendLine();
|
|
else
|
|
sb.AppendFormat(Localization.Volume_reserves_1_tracks_0_sectors_for_system,
|
|
_workingDefinition.ofs * _workingDefinition.sectorsPerTrack, _workingDefinition.ofs).
|
|
AppendLine();
|
|
|
|
if(_workingDefinition.side1.sectorIds.Length >= 2)
|
|
{
|
|
int interleaveSide1 = _workingDefinition.side1.sectorIds[1] - _workingDefinition.side1.sectorIds[0];
|
|
|
|
if(interleaveSide1 > 1)
|
|
sb.AppendFormat(Localization.Side_zero_uses_0_one_software_interleaving, interleaveSide1).AppendLine();
|
|
}
|
|
|
|
if(_workingDefinition.sides == 2)
|
|
{
|
|
if(_workingDefinition.side2.sectorIds.Length >= 2)
|
|
{
|
|
int interleaveSide2 = _workingDefinition.side2.sectorIds[1] - _workingDefinition.side2.sectorIds[0];
|
|
|
|
if(interleaveSide2 > 1)
|
|
sb.AppendFormat(Localization.Side_one_uses_0_one_software_interleaving, interleaveSide2).
|
|
AppendLine();
|
|
}
|
|
|
|
switch(_workingDefinition.order)
|
|
{
|
|
case "SIDES":
|
|
sb.AppendLine(Localization.Head_changes_after_each_whole_track);
|
|
|
|
break;
|
|
case "CYLINDERS":
|
|
sb.AppendLine(Localization.Head_changes_after_whole_side);
|
|
|
|
break;
|
|
default:
|
|
sb.AppendFormat(Localization.Unknown_how_0_side_ordering_works, _workingDefinition.order).
|
|
AppendLine();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(_workingDefinition.skew > 0)
|
|
sb.AppendFormat(Localization.Device_uses_0_one_hardware_interleaving, _workingDefinition.skew).AppendLine();
|
|
|
|
if(_workingDefinition.sofs > 0)
|
|
sb.AppendLine($"BSH {_dpb.bsh} BLM {_dpb.blm} EXM {_dpb.exm} DSM {_dpb.dsm} DRM {_dpb.drm} AL0 {_dpb.al0
|
|
:X2}H AL1 {_dpb.al1:X2}H SOFS {_workingDefinition.sofs}");
|
|
else
|
|
sb.AppendLine($"BSH {_dpb.bsh} BLM {_dpb.blm} EXM {_dpb.exm} DSM {_dpb.dsm} DRM {_dpb.drm} AL0 {_dpb.al0
|
|
:X2}H AL1 {_dpb.al1:X2}H OFS {_workingDefinition.ofs}");
|
|
|
|
if(_label != null)
|
|
sb.AppendFormat(Localization.Volume_label_0, _label).AppendLine();
|
|
|
|
if(_standardTimestamps)
|
|
sb.AppendLine(Localization.Volume_uses_standard_CPM_timestamps);
|
|
|
|
if(_thirdPartyTimestamps)
|
|
sb.AppendLine(Localization.Volume_uses_third_party_timestamps);
|
|
|
|
if(_labelCreationDate != null)
|
|
sb.AppendFormat(Localization.Volume_created_on_0, DateHandlers.CpmToDateTime(_labelCreationDate)).
|
|
AppendLine();
|
|
|
|
if(_labelUpdateDate != null)
|
|
sb.AppendFormat(Localization.Volume_updated_on_0, DateHandlers.CpmToDateTime(_labelUpdateDate)).
|
|
AppendLine();
|
|
|
|
metadata = new FileSystem();
|
|
metadata.Bootable |= _workingDefinition.sofs > 0 || _workingDefinition.ofs > 0;
|
|
metadata.ClusterSize = (uint)(128 << _dpb.bsh);
|
|
|
|
if(_dpb.dsm > 0)
|
|
metadata.Clusters = _dpb.dsm;
|
|
else
|
|
metadata.Clusters = partition.End - partition.Start;
|
|
|
|
if(_labelCreationDate != null)
|
|
{
|
|
metadata.CreationDate = DateHandlers.CpmToDateTime(_labelCreationDate);
|
|
}
|
|
|
|
if(_labelUpdateDate != null)
|
|
{
|
|
metadata.ModificationDate = DateHandlers.CpmToDateTime(_labelUpdateDate);
|
|
}
|
|
|
|
metadata.Type = FS_TYPE;
|
|
metadata.VolumeName = _label;
|
|
|
|
information = sb.ToString();
|
|
}
|
|
} |