From 833f8c02a4595498ba877f7927edc1244227a170 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2017 19:41:16 -0400 Subject: [PATCH] Switches the CPC DSK implementation to building an in-memory version of the structure up front. Preparatory to making these things writeable. --- Storage/Disk/DiskImage/Formats/CPCDSK.cpp | 226 ++++++++++++++-------- Storage/Disk/DiskImage/Formats/CPCDSK.hpp | 38 +++- 2 files changed, 173 insertions(+), 91 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp index ed8e273b8..ccb2a4344 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp @@ -26,14 +26,133 @@ CPCDSK::CPCDSK(const char *file_name) : head_position_count_ = fgetc(file_); head_count_ = fgetc(file_); + // Used only for non-extended disks. + long size_of_a_track; + + // Used only for extended disks. + std::vector track_sizes; + if(is_extended_) { // Skip two unused bytes and grab the track size table. fseek(file_, 2, SEEK_CUR); for(int c = 0; c < head_position_count_ * head_count_; c++) { - track_sizes_.push_back(static_cast(fgetc(file_) << 8)); + track_sizes.push_back(static_cast(fgetc(file_) << 8)); } } else { - size_of_a_track_ = fgetc16le(); + size_of_a_track = fgetc16le(); + } + + long file_offset = 0x100; + for(size_t c = 0; c < static_cast(head_position_count_ * head_count_); c++) { + if(!is_extended_ || (track_sizes[c] > 0)) { + // Skip the introductory text, 'Track-Info\r\n' and its unused bytes. + fseek(file_, file_offset + 16, SEEK_SET); + + tracks_.emplace_back(new Track); + Track *track = tracks_.back().get(); + + // Track and side are stored, being a byte each. + track->track = static_cast(fgetc(file_)); + track->side = static_cast(fgetc(file_)); + + // If this is an extended disk image then John Elliott's extension provides some greater + // data rate and encoding context. Otherwise the next two bytes have no defined meaning. + if(is_extended_) { + switch(fgetc(file_)) { + default: track->data_rate = Track::DataRate::Unknown; break; + case 1: track->data_rate = Track::DataRate::SingleOrDoubleDensity; break; + case 2: track->data_rate = Track::DataRate::HighDensity; break; + case 3: track->data_rate = Track::DataRate::ExtendedDensity; break; + } + switch(fgetc(file_)) { + default: track->data_encoding = Track::DataEncoding::Unknown; break; + case 1: track->data_encoding = Track::DataEncoding::FM; break; + case 2: track->data_encoding = Track::Track::DataEncoding::MFM; break; + } + } else { + track->data_rate = Track::DataRate::Unknown; + track->data_encoding = Track::DataEncoding::Unknown; + fseek(file_, 2, SEEK_CUR); + } + + // Sector size, number of sectors, gap 3 length and the filler byte are then common + // between both variants of DSK. + track->sector_length = static_cast(fgetc(file_)); + size_t number_of_sectors = static_cast(fgetc(file_)); + track->gap3_length = static_cast(fgetc(file_)); + track->filler_byte = static_cast(fgetc(file_)); + + // Sector information begins immediately after the track information table. + while(number_of_sectors--) { + track->sectors.emplace_back(); + Track::Sector §or = track->sectors.back(); + + // Track, side, sector, size and two FDC8272-esque status bytes are stored + // per sector, in both regular and extended DSK files. + sector.track = static_cast(fgetc(file_)); + sector.side = static_cast(fgetc(file_)); + sector.sector = static_cast(fgetc(file_)); + sector.size = static_cast(fgetc(file_)); + sector.fdc_status1 = static_cast(fgetc(file_)); + sector.fdc_status2 = static_cast(fgetc(file_)); + + // Figuring out the actual data size is a little more work... + size_t data_size = static_cast(128 << sector.size); + size_t stored_data_size = data_size; + size_t number_of_samplings = 1; + + if(is_extended_) { + // In an extended DSK, oblige two Simon Owen extensions: + // + // Allow a declared data size less than the sector's declared size to act as an abbreviation. + // Extended DSK varies the 8kb -> 0x1800 bytes special case by this means. + // + // Use a declared data size greater than the sector's declared size as a record that this + // sector was weak or fuzzy and that multiple samplings are provided. If the greater size + // is not an exact multiple then my reading of the documentation is that this is an invalid + // disk image. + size_t declared_data_size = fgetc16le(); + if(declared_data_size != stored_data_size) { + if(declared_data_size > data_size) { + number_of_samplings = declared_data_size / data_size; + if(declared_data_size % data_size) + throw ErrorNotCPCDSK; + } else { + stored_data_size = declared_data_size; + } + } + } else { + // In a regular DSK, these two bytes are unused, and a special case is applied that ostensibly 8kb + // sectors are abbreviated to only 0x1800 bytes. + if(data_size == 0x2000) stored_data_size = 0x1800; + fseek(file_, 2, SEEK_CUR); + } + + // As per the weak/fuzzy sector extension, multiple samplings may be stored here. + // Plan to tead as many as there were. + sector.data.resize(number_of_samplings); + while(number_of_samplings--) { + sector.data[number_of_samplings].resize(stored_data_size); + } + } + + // Sector contents are at offset 0x100 into the track. + fseek(file_, file_offset + 0x100, SEEK_SET); + for(auto §or: track->sectors) { + for(auto &data : sector.data) { + fread(data.data(), 1, data.size(), file_); + } + } + } else { + // An extended disk image, which declares that there is no data stored for this track. + tracks_.emplace_back(); + } + + // Advance to the beginning of the next track. + if(is_extended_) + file_offset += static_cast(track_sizes[c]); + else + file_offset += size_of_a_track; } } @@ -45,101 +164,45 @@ int CPCDSK::get_head_count() { return head_count_; } -std::shared_ptr CPCDSK::get_track_at_position(Track::Address address) { +std::shared_ptr CPCDSK::get_track_at_position(::Storage::Disk::Track::Address address) { // Given that thesea are interleaved images, determine which track, chronologically, is being requested. size_t chronological_track = static_cast((address.position * head_count_) + address.head); - // All DSK images reserve 0x100 bytes for their headers. - long file_offset = 0x100; - if(is_extended_) { - // Tracks are a variable size in the original DSK file format. + // Return a nullptr if out of range or not provided. + if(chronological_track >= tracks_.size()) return nullptr; + + Track *track = tracks_[chronological_track].get(); + if(!track) return nullptr; - // Check that there is anything stored for this track. - if(!track_sizes_[chronological_track]) { - return nullptr; - } - - // Sum the lengths of all tracks prior to the interesting one to get a file offset. - size_t t = 0; - while(t < chronological_track && t < track_sizes_.size()) { - file_offset += track_sizes_[t]; - t++; - } - } else { - // Tracks are a fixed size in the original DSK file format. - file_offset += size_of_a_track_ * static_cast(chronological_track); - } - - // Find the track, and skip the unused part of track information. - fseek(file_, file_offset + 16, SEEK_SET); - - // Grab the track information. - fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector - int number_of_sectors = fgetc(file_); - uint8_t gap3_length = static_cast(fgetc(file_)); - uint8_t filler_byte = static_cast(fgetc(file_)); - - // Grab the sector information - struct SectorInfo { - uint8_t track; - uint8_t side; - uint8_t sector; - uint8_t length; - uint8_t status1; - uint8_t status2; - size_t actual_length; - }; - std::vector sector_infos; - while(number_of_sectors--) { - SectorInfo sector_info; - - sector_info.track = static_cast(fgetc(file_)); - sector_info.side = static_cast(fgetc(file_)); - sector_info.sector = static_cast(fgetc(file_)); - sector_info.length = static_cast(fgetc(file_)); - sector_info.status1 = static_cast(fgetc(file_)); - sector_info.status2 = static_cast(fgetc(file_)); - sector_info.actual_length = fgetc16le(); - - sector_infos.push_back(sector_info); - } - - // Get the sectors. - fseek(file_, file_offset + 0x100, SEEK_SET); + // Transcribe sectors and return. + // TODO: is this transcription really necessary? std::vector sectors; - for(auto §or_info : sector_infos) { + for(auto §or : track->sectors) { Storage::Encodings::MFM::Sector new_sector; - new_sector.address.track = sector_info.track; - new_sector.address.side = sector_info.side; - new_sector.address.sector = sector_info.sector; - new_sector.size = sector_info.length; + new_sector.address.track = sector.track; + new_sector.address.side = sector.side; + new_sector.address.sector = sector.sector; + new_sector.size = sector.size; - size_t data_size; - if(is_extended_) { - data_size = sector_info.actual_length; - } else { - data_size = static_cast(128 << sector_info.length); - if(data_size == 0x2000) data_size = 0x1800; - } - new_sector.data.resize(data_size); - fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_); + // TODO: deal with weak/fuzzy sectors. + new_sector.data.insert(new_sector.data.begin(), sector.data[0].begin(), sector.data[0].end()); - if(sector_info.status2 & 0x20) { + if(sector.fdc_status2 & 0x20) { // The CRC failed in the data field. new_sector.has_data_crc_error = true; } else { - if(sector_info.status1 & 0x20) { + if(sector.fdc_status1 & 0x20) { // The CRC failed in the ID field. new_sector.has_header_crc_error = true; } } - if(sector_info.status2 & 0x40) { + if(sector.fdc_status2 & 0x40) { // This sector is marked as deleted. new_sector.is_deleted = true; } - if(sector_info.status2 & 0x01) { + if(sector.fdc_status2 & 0x01) { // Data field wasn't found. new_sector.data.clear(); } @@ -147,11 +210,6 @@ std::shared_ptr CPCDSK::get_track_at_position(Track::Address address) { sectors.push_back(std::move(new_sector)); } - // TODO: extensions to the extended format; John Elliot's addition of single-density support, - // and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential - // FM/MFM encoding that can produce specified weak values. - - if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte); - - return nullptr; + // TODO: FM encoding, data rate? + return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte); } diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index f7c5b6a12..d5670d9b1 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -38,18 +38,42 @@ class CPCDSK: public DiskImage, public Storage::FileHolder { int get_head_position_count() override; int get_head_count() override; using DiskImage::get_is_read_only; - std::shared_ptr get_track_at_position(Track::Address address) override; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; private: + struct Track { + uint8_t track; + uint8_t side; + enum class DataRate { + Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity + } data_rate; + enum class DataEncoding { + Unknown, FM, MFM + } data_encoding; + uint8_t sector_length; + uint8_t gap3_length; + uint8_t filler_byte; + + struct Sector { + uint8_t track; + uint8_t side; + uint8_t sector; + uint8_t size; + uint8_t fdc_status1; + uint8_t fdc_status2; + + // If multiple copies are present, that implies a sector with weak bits, for which multiple + // samplings were obtained. + std::vector> data; + }; + + std::vector sectors; + }; + std::vector> tracks_; + int head_count_; int head_position_count_; bool is_extended_; - - // Used only for non-extended disks. - long size_of_a_track_; - - // Used only for extended disks. - std::vector track_sizes_; }; }