diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1522bb3df..263dd9bb3 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; }; 4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; }; 4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; }; + 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; }; 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; }; 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; }; 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; }; @@ -562,6 +563,8 @@ 4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = ""; }; 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = ""; }; 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = ""; }; + 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = ""; }; + 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = ""; }; 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = ""; }; 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = ""; }; 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = ""; }; @@ -1330,6 +1333,15 @@ path = Bridges; sourceTree = ""; }; + 4B3FE75F1F3CF6BA00448EE4 /* Parsers */ = { + isa = PBXGroup; + children = ( + 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */, + 4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */, + ); + name = Parsers; + sourceTree = ""; + }; 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { isa = PBXGroup; children = ( @@ -1446,12 +1458,12 @@ 4B69FB3A1C4D908A00B5F0AA /* Tape */ = { isa = PBXGroup; children = ( + 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */, + 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, + 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */, + 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, 4B69FB411C4D941400B5F0AA /* Formats */, 4B8805F11DCFC9A2003085B1 /* Parsers */, - 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */, - 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */, - 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, - 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, ); path = Tape; sourceTree = ""; @@ -1545,6 +1557,7 @@ 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, + 4B3FE75F1F3CF6BA00448EE4 /* Parsers */, ); path = Disk; sourceTree = ""; @@ -2700,6 +2713,7 @@ 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, + 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 7a3f9aa6f..8f9245814 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -19,8 +19,8 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha std::unique_ptr catalogue(new Catalogue); Storage::Encodings::MFM::Parser parser(false, disk); - std::shared_ptr names = parser.get_sector(0, 0); - std::shared_ptr details = parser.get_sector(0, 1); + std::shared_ptr names = parser.get_sector(0, 0, 0); + std::shared_ptr details = parser.get_sector(0, 0, 1); if(!names || !details) return nullptr; if(names->data.size() != 256 || details->data.size() != 256) return nullptr; @@ -61,7 +61,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha uint8_t track = (uint8_t)(start_sector / 10); start_sector++; - std::shared_ptr next_sector = parser.get_sector(track, sector); + std::shared_ptr next_sector = parser.get_sector(0, track, sector); if(!next_sector) break; long length_from_sector = std::min(data_length, 256l); @@ -77,13 +77,13 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh std::unique_ptr catalogue(new Catalogue); Storage::Encodings::MFM::Parser parser(true, disk); - std::shared_ptr free_space_map_second_half = parser.get_sector(0, 1); + std::shared_ptr free_space_map_second_half = parser.get_sector(0, 0, 1); if(!free_space_map_second_half) return nullptr; std::vector root_directory; root_directory.reserve(5 * 256); for(uint8_t c = 2; c < 7; c++) { - std::shared_ptr sector = parser.get_sector(0, c); + std::shared_ptr sector = parser.get_sector(0, 0, c); if(!sector) return nullptr; root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end()); } diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 8e15fdd2b..91059e263 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -7,6 +7,39 @@ // #include "StaticAnalyser.hpp" +#include "../../Storage/Disk/Parsers/CPM.hpp" + +static void InspectDataCatalogue( + const std::unique_ptr &data_catalogue, + StaticAnalyser::Target &target) { + // If there's just one file, run that. + if(data_catalogue->files.size() == 1) { + target.loadingCommand = "run\"" + data_catalogue->files[0].name + "\n"; + return; + } + + // If only one file is [potentially] BASIC, run that one. + int basic_files = 0; + size_t last_basic_file = 0; + for(size_t c = 0; c < data_catalogue->files.size(); c++) { + if(!((data_catalogue->files[c].data[18] >> 1) & 7)) { + basic_files++; + last_basic_file = c; + } + } + if(basic_files == 1) { + target.loadingCommand = "run\"" + data_catalogue->files[last_basic_file].name + "\n"; + return; + } + + // Desperation. + target.loadingCommand = "cat\n"; +} + +static void InspectSystemCatalogue( + const std::unique_ptr &data_catalogue, + StaticAnalyser::Target &target) { +} void StaticAnalyser::AmstradCPC::AddTargets( const std::list> &disks, @@ -20,7 +53,39 @@ void StaticAnalyser::AmstradCPC::AddTargets( target.tapes = tapes; target.cartridges = cartridges; - target.amstradcpc.model = target.disks.empty() ? AmstradCPCModel::CPC464 : AmstradCPCModel::CPC6128; + target.amstradcpc.model = AmstradCPCModel::CPC6128; + + if(!target.tapes.empty()) { + target.loadingCommand = "|tape\nrun\"\n"; + } + + if(!target.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; + + std::unique_ptr data_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), data_format); + if(data_catalogue) { + InspectDataCatalogue(data_catalogue, target); + } else { + Storage::Disk::CPM::ParameterBlock system_format; + data_format.sectors_per_track = 9; + data_format.tracks = 40; + data_format.block_size = 1024; + data_format.first_sector = 0x41; + data_format.catalogue_allocation_bitmap = 0xc000; + data_format.reserved_tracks = 2; + + std::unique_ptr system_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), system_format); + if(system_catalogue) { + InspectSystemCatalogue(data_catalogue, target); + } + } + } destination.push_back(target); } diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 1aa908e15..1c2f19a4c 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -228,25 +228,26 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector &disk) : Parser(is_mfm) { - drive->set_disk(disk); + drive_->set_disk(disk); } Parser::Parser(bool is_mfm, const std::shared_ptr &track) : Parser(is_mfm) { - drive->set_disk_with_track(track); + drive_->set_disk_with_track(track); } void Parser::seek_to_track(uint8_t track) { @@ -261,7 +262,20 @@ void Parser::seek_to_track(uint8_t track) { } } -std::shared_ptr Parser::get_sector(uint8_t track, uint8_t sector) { +std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t sector) { + // Check cache for sector. + int index = get_index(head, track, sector); + auto cached_sector = sectors_by_index_.find(index); + if(cached_sector != sectors_by_index_.end()) { + return cached_sector->second; + } + + // Failing that, set the proper head and track, and search for the sector. get_sector automatically + // inserts everything found into sectors_by_index_. + if(head_ != head) { + drive_->set_head(head); + invalidate_track(); + } seek_to_track(track); return get_sector(sector); } @@ -384,8 +398,7 @@ std::vector Parser::get_track() { } -std::shared_ptr Parser::get_next_sector() -{ +std::shared_ptr Parser::get_next_sector() { std::shared_ptr sector(new Sector); index_count_ = 0; @@ -455,6 +468,10 @@ std::shared_ptr Parser::get_next_sector() if((data_crc >> 8) != get_next_byte()) continue; if((data_crc & 0xff) != get_next_byte()) continue; + // Put this sector into the cache. + int index = get_index(head_, track_, sector->sector); + sectors_by_index_[index] = sector; + return sector; } @@ -465,7 +482,7 @@ std::shared_ptr Parser::get_sector(uint8_t sector) { std::shared_ptr first_sector; index_count_ = 0; while(!first_sector && index_count_ < 2) first_sector = get_next_sector(); - if(!first_sector) return first_sector; + if(!first_sector) return nullptr; if(first_sector->sector == sector) return first_sector; while(1) { @@ -475,3 +492,7 @@ std::shared_ptr Parser::get_sector(uint8_t sector) { if(next_sector->sector == sector) return next_sector; } } + +int Parser::get_index(uint8_t head, uint8_t track, uint8_t sector) { + return head | (track << 8) | (sector << 16); +} diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index f482c93bd..37a8178f3 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -75,7 +75,7 @@ class Parser: public Storage::Disk::Controller { @returns a sector if one was found; @c nullptr otherwise. */ - std::shared_ptr get_sector(uint8_t track, uint8_t sector); + std::shared_ptr get_sector(uint8_t head, uint8_t track, uint8_t sector); /*! Attempts to read the track at @c track, starting from the index hole. @@ -92,10 +92,10 @@ class Parser: public Storage::Disk::Controller { private: Parser(bool is_mfm); - std::shared_ptr drive; + std::shared_ptr drive_; unsigned int shift_register_; int index_count_; - uint8_t track_; + uint8_t track_, head_; int bit_count_; NumberTheory::CRC16 crc_generator_; bool is_mfm_; @@ -110,6 +110,9 @@ class Parser: public Storage::Disk::Controller { std::shared_ptr get_next_sector(); std::shared_ptr get_sector(uint8_t sector); std::vector get_track(); + + std::map> sectors_by_index_; + int get_index(uint8_t head, uint8_t track, uint8_t sector); }; diff --git a/Storage/Disk/Formats/AcornADF.cpp b/Storage/Disk/Formats/AcornADF.cpp index 779bc29c0..f4e0664d7 100644 --- a/Storage/Disk/Formats/AcornADF.cpp +++ b/Storage/Disk/Formats/AcornADF.cpp @@ -87,7 +87,7 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p std::vector parsed_track; Storage::Encodings::MFM::Parser parser(true, track); for(unsigned int c = 0; c < sectors_per_track; c++) { - std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + std::shared_ptr sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c); if(sector) { parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); } else { diff --git a/Storage/Disk/Formats/SSD.cpp b/Storage/Disk/Formats/SSD.cpp index 4903784d5..5045e536b 100644 --- a/Storage/Disk/Formats/SSD.cpp +++ b/Storage/Disk/Formats/SSD.cpp @@ -81,7 +81,7 @@ void SSD::store_updated_track_at_position(unsigned int head, unsigned int positi std::vector parsed_track; Storage::Encodings::MFM::Parser parser(false, track); for(unsigned int c = 0; c < 10; c++) { - std::shared_ptr sector = parser.get_sector((uint8_t)position, (uint8_t)c); + std::shared_ptr sector = parser.get_sector(0, (uint8_t)position, (uint8_t)c); if(sector) { parsed_track.insert(parsed_track.end(), sector->data.begin(), sector->data.end()); } else { diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp new file mode 100644 index 000000000..eaf737404 --- /dev/null +++ b/Storage/Disk/Parsers/CPM.cpp @@ -0,0 +1,120 @@ +// +// CPM.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "CPM.hpp" + +#include "../Encodings/MFM.hpp" + +using namespace Storage::Disk::CPM; + +std::unique_ptr Storage::Disk::CPM::GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters) { + Storage::Encodings::MFM::Parser parser(true, disk); + + // Assemble the actual bytes of the catalogue. + std::vector catalogue; + size_t sector_size = 1; + uint16_t catalogue_allocation_bitmap = parameters.catalogue_allocation_bitmap; + if(!catalogue_allocation_bitmap) return nullptr; + int sector = 0; + int track = parameters.reserved_tracks; + while(catalogue_allocation_bitmap) { + if(catalogue_allocation_bitmap & 0x8000) { + std::shared_ptr sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + if(!sector_contents) { + return nullptr; + } + + catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end()); + sector_size = sector_contents->data.size(); + } + + catalogue_allocation_bitmap <<= 1; + + sector++; + if(sector == parameters.sectors_per_track) { + sector = 0; + track++; + } + } + + std::unique_ptr result(new Catalogue); + bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256; + size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size; + + // From the catalogue, create files. + std::map, size_t> indices_by_name; + File empty_file; + for(size_t c = 0; c < catalogue.size(); c += 32) { + // Skip this file if it's deleted; this is marked by it having 0xe5 as its user number + if(catalogue[c] == 0xe5) continue; + + // Check whether this file has yet been seen; if not then add it to the list + std::vector descriptor; + size_t index; + descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]); + auto iterator = indices_by_name.find(descriptor); + if(iterator != indices_by_name.end()) { + index = iterator->second; + } else { + File new_file; + new_file.user_number = catalogue[c]; + for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]); + for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f); + new_file.read_only = catalogue[c + 9] & 0x80; + new_file.system = catalogue[c + 10] & 0x80; + index = result->files.size(); + result->files.push_back(new_file); + indices_by_name[descriptor] = index; + } + + // figure out where this data needs to be pasted in + size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5)); + int number_of_records = catalogue[c + 15]; + + size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128; + if(result->files[index].data.size() < required_size) { + result->files[index].data.resize(required_size); + } + + int sectors_per_block = parameters.block_size / (int)sector_size; + int records_per_sector = (int)sector_size / 128; + int record = 0; + for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) { + int block_number; + if(has_long_allocation_units) { + block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8); + } else { + block_number = catalogue[c + 16 + block]; + } + if(!block_number) { + record += parameters.block_size / 128; + continue; + } + int first_sector = block_number * sectors_per_block; + + sector = first_sector % parameters.sectors_per_track; + track = first_sector / parameters.sectors_per_track; + + for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { + std::shared_ptr sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + if(!sector_contents) break; + sector++; + if(sector == parameters.sectors_per_track) { + sector = 0; + track++; + } + + int records_to_copy = std::min(number_of_records - record, records_per_sector); + memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128); + record += records_to_copy; + } + } + } + + return result; +} diff --git a/Storage/Disk/Parsers/CPM.hpp b/Storage/Disk/Parsers/CPM.hpp new file mode 100644 index 000000000..a51c8b5cc --- /dev/null +++ b/Storage/Disk/Parsers/CPM.hpp @@ -0,0 +1,51 @@ +// +// CPM.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/08/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Disk_Parsers_CPM_hpp +#define Storage_Disk_Parsers_CPM_hpp + +#include "../Disk.hpp" + +#include +#include +#include +#include + +namespace Storage { +namespace Disk { +namespace CPM { + +struct ParameterBlock { + int sectors_per_track; + int tracks; + int block_size; + int first_sector; + uint16_t catalogue_allocation_bitmap; + int reserved_tracks; +}; + +struct File { + uint8_t user_number; + std::string name; + std::string type; + bool read_only; + bool system; + std::vector data; +}; + +struct Catalogue { + std::vector files; +}; + +std::unique_ptr GetCatalogue(const std::shared_ptr &disk, const ParameterBlock ¶meters); + +} +} +} + +#endif /* Storage_Disk_Parsers_CPM_hpp */