From 69ba94e379679f86e249fd0ca7deae934816dc33 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 20 Aug 2024 21:43:31 -0400 Subject: [PATCH] Add some CP/M smarts to +3 disk analysis. --- Analyser/Static/AmstradCPC/StaticAnalyser.cpp | 17 +---- Analyser/Static/ZXSpectrum/StaticAnalyser.cpp | 74 ++++++++++++++++--- Storage/Disk/Encodings/MFM/Parser.cpp | 12 +++ Storage/Disk/Encodings/MFM/Parser.hpp | 8 ++ Storage/Disk/Parsers/CPM.hpp | 23 ++++++ 5 files changed, 108 insertions(+), 26 deletions(-) diff --git a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp index 842371fe2..419e855dc 100644 --- a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp +++ b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp @@ -228,21 +228,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi } if(!media.disks.empty()) { - Storage::Disk::CPM::ParameterBlock data_format; - data_format.sectors_per_track = 9; - data_format.tracks = 40; - data_format.block_size = 1024; - data_format.first_sector = 0xc1; - data_format.catalogue_allocation_bitmap = 0xc000; - data_format.reserved_tracks = 0; - - Storage::Disk::CPM::ParameterBlock system_format; - system_format.sectors_per_track = 9; - system_format.tracks = 40; - system_format.block_size = 1024; - system_format.first_sector = 0x41; - system_format.catalogue_allocation_bitmap = 0xc000; - system_format.reserved_tracks = 2; + const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format(); + const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format(); for(auto &disk: media.disks) { // Check for an ordinary catalogue. diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp index 13a76841f..1a60b5ae8 100644 --- a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -8,6 +8,7 @@ #include "StaticAnalyser.hpp" +#include "../../../Storage/Disk/Parsers/CPM.hpp" #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" #include "../../../Storage/Tape/Parsers/Spectrum.hpp" @@ -37,21 +38,72 @@ bool IsSpectrumTape(const std::shared_ptr &tape) { bool IsSpectrumDisk(const std::shared_ptr &disk) { Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk); - // Get logical sector 1; the Spectrum appears to support various physical - // sectors as sector 1. - const Storage::Encodings::MFM::Sector *boot_sector = nullptr; - uint8_t sector_mask = 0; - while(!boot_sector) { - boot_sector = parser.sector(0, 0, sector_mask + 1); - sector_mask += 0x40; - if(!sector_mask) break; - } + // Grab absolutely any sector from the first track to determine general encoding. + const Storage::Encodings::MFM::Sector *any_sector = parser.any_sector(0, 0); + if(!any_sector) return false; + + // Determine the sector base and get logical sector 1. + const uint8_t sector_base = any_sector->address.sector & 0xc0; + const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, sector_base + 1); if(!boot_sector) return false; - // Test that the contents of the boot sector sum to 3, modulo 256. + Storage::Disk::CPM::ParameterBlock cpm_format{}; + switch(sector_base) { + case 0x40: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format(); break; + case 0xc0: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format(); break; + + default: { + // Check the first ten bytes of the first sector for the disk format; if these are all + // the same value then instead substitute a default format. + std::array format; + std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin()); + if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) { + format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52}; + } + + // Parse those ten bytes as: + // + // Byte 0: disc type + // Byte 1: sidedness + // bits 0-6: arrangement + // 0 => single sided + // 1 => double sided, flip sides + // 2 => double sided, up and over + // bit 7: double-track + // Byte 2: number of tracks per side + // Byte 3: number of sectors per track + // Byte 4: Log2(sector size) - 7 + // Byte 5: number of reserved tracks + // Byte 6: Log2(block size) - 7 + // Byte 7: number of directory blocks + // Byte 8: gap length (read/write) + // Byte 9: gap length(format) + cpm_format.sectors_per_track = format[3]; + cpm_format.tracks = format[2]; + cpm_format.block_size = 128 << format[6]; + cpm_format.first_sector = sector_base + 1; + cpm_format.reserved_tracks = format[5]; + + // i.e. bits set downward from 0x4000 for as many blocks as form the catalogue. + cpm_format.catalogue_allocation_bitmap = 0x8000 - (0x8000 >> format[7]); + } break; + } + + // If the boot sector sums to 3 modulo 256 then this is a Spectrum disk. const auto byte_sum = static_cast( std::accumulate(boot_sector->samples[0].begin(), boot_sector->samples[0].end(), 0)); - return byte_sum == 3; + if(byte_sum == 3) { + return true; + } + + // ... otherwise read a CPM directory and look for a BASIC program called "DISK". + const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format); + if(!catalogue) return false; + const auto file = std::find_if(catalogue->files.begin(), catalogue->files.end(), [](const auto &file) { return file.name == "DISK "; }); + if(file == catalogue->files.end()) return false; + + // TODO: check out contents of "DISK" + return true; } } diff --git a/Storage/Disk/Encodings/MFM/Parser.cpp b/Storage/Disk/Encodings/MFM/Parser.cpp index b6ad3a58a..7b1589dde 100644 --- a/Storage/Disk/Encodings/MFM/Parser.cpp +++ b/Storage/Disk/Encodings/MFM/Parser.cpp @@ -71,3 +71,15 @@ const Sector *Parser::sector(int head, int track, uint8_t sector) { return &stored_sector->second; } + +const Sector *Parser::any_sector(int head, int track) { + const Disk::Track::Address address(head, Storage::Disk::HeadPosition(track)); + install_track(address); + + const auto sectors = sectors_by_address_by_track_.find(address); + if(sectors == sectors_by_address_by_track_.end()) { + return nullptr; + } + + return §ors->second.begin()->second; +} diff --git a/Storage/Disk/Encodings/MFM/Parser.hpp b/Storage/Disk/Encodings/MFM/Parser.hpp index 9430b1df3..24edfc5ca 100644 --- a/Storage/Disk/Encodings/MFM/Parser.hpp +++ b/Storage/Disk/Encodings/MFM/Parser.hpp @@ -37,6 +37,14 @@ class Parser { */ const Storage::Encodings::MFM::Sector *sector(int head, int track, uint8_t sector); + + /*! + Seeks to the physical track at @c head and @c track. Searches on it for any sector. + + @returns a sector if one was found; @c nullptr otherwise. + */ + const Storage::Encodings::MFM::Sector *any_sector(int head, int track); + // TODO: set_sector. private: diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp index 31b08153f..847a5ab24 100644 --- a/Storage/Disk/Parsers/CPM.hpp +++ b/Storage/Disk/Parsers/CPM.hpp @@ -24,6 +24,29 @@ struct ParameterBlock { int first_sector; uint16_t catalogue_allocation_bitmap; int reserved_tracks; + + // Some well-known formats. + static ParameterBlock cpc_data_format() { + Storage::Disk::CPM::ParameterBlock data_format; + data_format.sectors_per_track = 9; + data_format.tracks = 40; + data_format.block_size = 1024; + data_format.first_sector = 0xc1; + data_format.catalogue_allocation_bitmap = 0xc000; + data_format.reserved_tracks = 0; + return data_format; + } + + static ParameterBlock cpc_system_format() { + Storage::Disk::CPM::ParameterBlock system_format; + system_format.sectors_per_track = 9; + system_format.tracks = 40; + system_format.block_size = 1024; + system_format.first_sector = 0x41; + system_format.catalogue_allocation_bitmap = 0xc000; + system_format.reserved_tracks = 2; + return system_format; + } }; struct File {