diff --git a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp index 842371fe2..49b002890 100644 --- a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp +++ b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp @@ -228,26 +228,13 @@ 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. + // Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk. std::unique_ptr data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format); - if(data_catalogue) { + if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) { InspectCatalogue(*data_catalogue, target); target->media.disks.push_back(disk); continue; @@ -261,7 +248,7 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi // Failing that check for a system catalogue. std::unique_ptr system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format); - if(system_catalogue) { + if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) { InspectCatalogue(*system_catalogue, target); target->media.disks.push_back(disk); continue; diff --git a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp index 396c67f23..d47e8d4c0 100644 --- a/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp +++ b/Analyser/Static/ZXSpectrum/StaticAnalyser.cpp @@ -8,11 +8,14 @@ #include "StaticAnalyser.hpp" +#include "../../../Storage/Disk/Parsers/CPM.hpp" #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" #include "../../../Storage/Tape/Parsers/Spectrum.hpp" #include "Target.hpp" +#include + namespace { bool IsSpectrumTape(const std::shared_ptr &tape) { @@ -35,23 +38,67 @@ 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. - uint8_t byte_sum = 0; - for(auto byte: boot_sector->samples[0]) { - byte_sum += byte; + 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; } - return byte_sum == 3; + + // 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)); + 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); + return catalogue && catalogue->is_zx_spectrum_booter(); } } 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.cpp b/Storage/Disk/Parsers/CPM.cpp index b6a757091..93e3efa9a 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -158,3 +158,13 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( return result; } + +bool Catalogue::is_zx_spectrum_booter() { + // Check for a file called 'DISK'. + const auto file = std::find_if(files.begin(), files.end(), [](const auto &file) { return file.name == "DISK "; }); + if(file == files.end()) return false; + + // TODO: check the file is valid ZX Spectrum BASIC. + + return true; +} diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp index 31b08153f..02958e822 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 { @@ -37,6 +60,8 @@ struct File { struct Catalogue { std::vector files; + + bool is_zx_spectrum_booter(); }; std::unique_ptr GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters);