1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

Merge pull request #185 from TomHarte/CPMCatalogue

Introduces a CP/M catalogue parser, as a basic for the CPC's static analyser
This commit is contained in:
Thomas Harte 2017-08-11 11:00:49 -04:00 committed by GitHub
commit f0b7e58968
9 changed files with 298 additions and 24 deletions

View File

@ -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 = "<group>"; };
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CPM.hpp; path = Parsers/CPM.hpp; sourceTree = "<group>"; };
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
@ -1330,6 +1333,15 @@
path = Bridges;
sourceTree = "<group>";
};
4B3FE75F1F3CF6BA00448EE4 /* Parsers */ = {
isa = PBXGroup;
children = (
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */,
4B3FE75D1F3CF68B00448EE4 /* CPM.hpp */,
);
name = Parsers;
sourceTree = "<group>";
};
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 = "<group>";
@ -1545,6 +1557,7 @@
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */,
4B3FE75F1F3CF6BA00448EE4 /* Parsers */,
);
path = Disk;
sourceTree = "<group>";
@ -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 */,

View File

@ -19,8 +19,8 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(false, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0);
std::shared_ptr<Storage::Encodings::MFM::Sector> details = parser.get_sector(0, 1);
std::shared_ptr<Storage::Encodings::MFM::Sector> names = parser.get_sector(0, 0, 0);
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
uint8_t track = (uint8_t)(start_sector / 10);
start_sector++;
std::shared_ptr<Storage::Encodings::MFM::Sector> next_sector = parser.get_sector(track, sector);
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(true, disk);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 1);
std::shared_ptr<Storage::Encodings::MFM::Sector> free_space_map_second_half = parser.get_sector(0, 0, 1);
if(!free_space_map_second_half) return nullptr;
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, c);
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
}

View File

@ -7,6 +7,39 @@
//
#include "StaticAnalyser.hpp"
#include "../../Storage/Disk/Parsers/CPM.hpp"
static void InspectDataCatalogue(
const std::unique_ptr<Storage::Disk::CPM::Catalogue> &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<Storage::Disk::CPM::Catalogue> &data_catalogue,
StaticAnalyser::Target &target) {
}
void StaticAnalyser::AmstradCPC::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &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<Storage::Disk::CPM::Catalogue> 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<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.disks.front(), system_format);
if(system_catalogue) {
InspectSystemCatalogue(data_catalogue, target);
}
}
}
destination.push_back(target);
}

View File

@ -228,25 +228,26 @@ std::unique_ptr<Encoder> Storage::Encodings::MFM::GetFMEncoder(std::vector<uint8
Parser::Parser(bool is_mfm) :
Storage::Disk::Controller(4000000, 1, 300),
crc_generator_(0x1021, 0xffff),
shift_register_(0), track_(0), is_mfm_(is_mfm) {
shift_register_(0), is_mfm_(is_mfm),
track_(0), head_(0) {
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
set_expected_bit_length(bit_length);
drive.reset(new Storage::Disk::Drive);
set_drive(drive);
drive_.reset(new Storage::Disk::Drive);
set_drive(drive_);
set_motor_on(true);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Disk> &disk) :
Parser(is_mfm) {
drive->set_disk(disk);
drive_->set_disk(disk);
}
Parser::Parser(bool is_mfm, const std::shared_ptr<Storage::Disk::Track> &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<Sector> Parser::get_sector(uint8_t track, uint8_t sector) {
std::shared_ptr<Sector> 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<uint8_t> Parser::get_track() {
}
std::shared_ptr<Sector> Parser::get_next_sector()
{
std::shared_ptr<Sector> Parser::get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
@ -455,6 +468,10 @@ std::shared_ptr<Sector> 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<Sector> Parser::get_sector(uint8_t sector) {
std::shared_ptr<Sector> 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<Sector> 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);
}

View File

@ -75,7 +75,7 @@ class Parser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t track, uint8_t sector);
std::shared_ptr<Storage::Encodings::MFM::Sector> 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<Storage::Disk::Drive> drive;
std::shared_ptr<Storage::Disk::Drive> 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<Storage::Encodings::MFM::Sector> get_next_sector();
std::shared_ptr<Storage::Encodings::MFM::Sector> get_sector(uint8_t sector);
std::vector<uint8_t> get_track();
std::map<int, std::shared_ptr<Storage::Encodings::MFM::Sector>> sectors_by_index_;
int get_index(uint8_t head, uint8_t track, uint8_t sector);
};

View File

@ -87,7 +87,7 @@ void AcornADF::store_updated_track_at_position(unsigned int head, unsigned int p
std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(true, track);
for(unsigned int c = 0; c < sectors_per_track; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
std::shared_ptr<Storage::Encodings::MFM::Sector> 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 {

View File

@ -81,7 +81,7 @@ void SSD::store_updated_track_at_position(unsigned int head, unsigned int positi
std::vector<uint8_t> parsed_track;
Storage::Encodings::MFM::Parser parser(false, track);
for(unsigned int c = 0; c < 10; c++) {
std::shared_ptr<Storage::Encodings::MFM::Sector> sector = parser.get_sector((uint8_t)position, (uint8_t)c);
std::shared_ptr<Storage::Encodings::MFM::Sector> 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 {

View File

@ -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::Catalogue> Storage::Disk::CPM::GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock &parameters) {
Storage::Encodings::MFM::Parser parser(true, disk);
// Assemble the actual bytes of the catalogue.
std::vector<uint8_t> 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<Storage::Encodings::MFM::Sector> 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<Catalogue> 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<std::vector<uint8_t>, 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<uint8_t> 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<Storage::Encodings::MFM::Sector> 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;
}

View File

@ -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 <cstdint>
#include <memory>
#include <string>
#include <vector>
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<uint8_t> data;
};
struct Catalogue {
std::vector<File> files;
};
std::unique_ptr<Catalogue> GetCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk, const ParameterBlock &parameters);
}
}
}
#endif /* Storage_Disk_Parsers_CPM_hpp */