From 833f8c02a4595498ba877f7927edc1244227a170 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2017 19:41:16 -0400 Subject: [PATCH 1/7] 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_; }; } From f807a6b608e640cb850d02f3f23ea63e8943d156 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2017 21:32:28 -0400 Subject: [PATCH 2/7] Generalises the concept of multiple samplings of an FM/MFM sector, simplifying CPC DSK support and paving the way for generic weak/fuzzy bit support. --- StaticAnalyser/Acorn/Disk.cpp | 27 +++---- StaticAnalyser/AmstradCPC/StaticAnalyser.cpp | 4 +- Storage/Disk/DiskImage/Formats/CPCDSK.cpp | 72 ++++++++----------- Storage/Disk/DiskImage/Formats/CPCDSK.hpp | 11 +-- .../Formats/Utility/ImplicitSectors.cpp | 6 +- Storage/Disk/Encodings/MFM/Encoder.cpp | 57 +++++++++++---- Storage/Disk/Encodings/MFM/Encoder.hpp | 2 + Storage/Disk/Encodings/MFM/Sector.hpp | 6 +- Storage/Disk/Encodings/MFM/SegmentParser.cpp | 3 +- Storage/Disk/Parsers/CPM.cpp | 10 +-- 10 files changed, 110 insertions(+), 88 deletions(-) diff --git a/StaticAnalyser/Acorn/Disk.cpp b/StaticAnalyser/Acorn/Disk.cpp index 6ab4ab585..40cfa820a 100644 --- a/StaticAnalyser/Acorn/Disk.cpp +++ b/StaticAnalyser/Acorn/Disk.cpp @@ -23,17 +23,18 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha 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; + if(names->samples.empty() || details->samples.empty()) return nullptr; + if(names->samples[0].size() != 256 || details->samples[0].size() != 256) return nullptr; - uint8_t final_file_offset = details->data[5]; + uint8_t final_file_offset = details->samples[0][5]; if(final_file_offset&7) return nullptr; if(final_file_offset < 8) return nullptr; char disk_name[13]; - snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]); + snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]); catalogue->name = disk_name; - switch((details->data[6] >> 4)&3) { + switch((details->samples[0][6] >> 4)&3) { case 0: catalogue->bootOption = Catalogue::BootOption::None; break; case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; @@ -45,14 +46,14 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) { File new_file; char name[10]; - snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]); + snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); new_file.name = name; - new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14)); - new_file.execution_address = (uint32_t)(details->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10)); - new_file.is_protected = !!(names->data[file_offset + 7] & 0x80); + new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14)); + new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10)); + new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80); - long data_length = static_cast(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12)); - int start_sector = details->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8); + long data_length = static_cast(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12)); + int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8); new_file.data.reserve(static_cast(data_length)); if(start_sector < 2) continue; @@ -65,7 +66,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha if(!next_sector) break; long length_from_sector = std::min(data_length, 256l); - new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector); + new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector); data_length -= length_from_sector; } if(!data_length) catalogue->files.push_front(new_file); @@ -85,7 +86,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh for(uint8_t c = 2; c < 7; c++) { 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()); + root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end()); } // Quick sanity checks. @@ -93,7 +94,7 @@ std::unique_ptr StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr; if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr; - switch(free_space_map_second_half->data[0xfd]) { + switch(free_space_map_second_half->samples[0][0xfd]) { default: catalogue->bootOption = Catalogue::BootOption::None; break; case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; diff --git a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp index 70d066e9b..943f85255 100644 --- a/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp +++ b/StaticAnalyser/AmstradCPC/StaticAnalyser.cpp @@ -153,12 +153,12 @@ static void InspectCatalogue( static bool CheckBootSector(const std::shared_ptr &disk, StaticAnalyser::Target &target) { Storage::Encodings::MFM::Parser parser(true, disk); Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); - if(boot_sector != nullptr) { + if(boot_sector != nullptr && !boot_sector->samples.empty()) { // Check that the first 64 bytes of the sector aren't identical; if they are then probably // this disk was formatted and the filler byte never replaced. bool matched = true; for(size_t c = 1; c < 64; c++) { - if(boot_sector->data[c] != boot_sector->data[0]) { + if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) { matched = false; break; } diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp index ccb2a4344..921aac070 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp @@ -27,7 +27,7 @@ CPCDSK::CPCDSK(const char *file_name) : head_count_ = fgetc(file_); // Used only for non-extended disks. - long size_of_a_track; + long size_of_a_track = 0; // Used only for extended disks. std::vector track_sizes; @@ -89,13 +89,33 @@ CPCDSK::CPCDSK(const char *file_name) : // 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.address.track = static_cast(fgetc(file_)); + sector.address.side = static_cast(fgetc(file_)); + sector.address.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_)); + if(sector.fdc_status2 & 0x20) { + // The CRC failed in the data field. + sector.has_data_crc_error = true; + } else { + if(sector.fdc_status1 & 0x20) { + // The CRC failed in the ID field. + sector.has_header_crc_error = true; + } + } + + if(sector.fdc_status2 & 0x40) { + // This sector is marked as deleted. + sector.is_deleted = true; + } + + if(sector.fdc_status2 & 0x01) { + // Data field wasn't found. + sector.samples.clear(); + } + // 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; @@ -130,16 +150,17 @@ CPCDSK::CPCDSK(const char *file_name) : // 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); + sector.samples.emplace_back(); + sector.samples.resize(number_of_samplings); while(number_of_samplings--) { - sector.data[number_of_samplings].resize(stored_data_size); + sector.samples[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) { + for(auto &data : sector.samples) { fread(data.data(), 1, data.size(), file_); } } @@ -174,40 +195,9 @@ std::shared_ptr CPCDSK::get_track_at_position(::Storage::Disk::Track::Add Track *track = tracks_[chronological_track].get(); if(!track) return nullptr; - // Transcribe sectors and return. - // TODO: is this transcription really necessary? - std::vector sectors; + std::vector sectors; for(auto §or : track->sectors) { - Storage::Encodings::MFM::Sector new_sector; - new_sector.address.track = sector.track; - new_sector.address.side = sector.side; - new_sector.address.sector = sector.sector; - new_sector.size = sector.size; - - // TODO: deal with weak/fuzzy sectors. - new_sector.data.insert(new_sector.data.begin(), sector.data[0].begin(), sector.data[0].end()); - - if(sector.fdc_status2 & 0x20) { - // The CRC failed in the data field. - new_sector.has_data_crc_error = true; - } else { - if(sector.fdc_status1 & 0x20) { - // The CRC failed in the ID field. - new_sector.has_header_crc_error = true; - } - } - - if(sector.fdc_status2 & 0x40) { - // This sector is marked as deleted. - new_sector.is_deleted = true; - } - - if(sector.fdc_status2 & 0x01) { - // Data field wasn't found. - new_sector.data.clear(); - } - - sectors.push_back(std::move(new_sector)); + sectors.push_back(§or); } // TODO: FM encoding, data rate? diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index d5670d9b1..905d58d1f 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -11,6 +11,7 @@ #include "../DiskImage.hpp" #include "../../../FileHolder.hpp" +#include "../../Encodings/MFM/Sector.hpp" #include @@ -54,17 +55,9 @@ class CPCDSK: public DiskImage, public Storage::FileHolder { uint8_t gap3_length; uint8_t filler_byte; - struct Sector { - uint8_t track; - uint8_t side; - uint8_t sector; - uint8_t size; + struct Sector: public ::Storage::Encodings::MFM::Sector { 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; diff --git a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp index 21ab01df6..5c1f62852 100644 --- a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp +++ b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp @@ -31,7 +31,8 @@ std::shared_ptr Storage::Disk::track_for_sectors(uint8_t *const source, u first_sector++; new_sector.size = size; - new_sector.data.insert(new_sector.data.begin(), source + source_pointer, source + source_pointer + byte_size); + new_sector.samples.emplace_back(); + new_sector.samples[0].insert(new_sector.samples[0].begin(), source + source_pointer, source + source_pointer + byte_size); source_pointer += byte_size; } @@ -53,6 +54,7 @@ void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uin if(pair.second.address.sector > last_sector) continue; if(pair.second.address.sector < first_sector) continue; if(pair.second.size != sector_size) continue; - memcpy(&destination[pair.second.address.sector * byte_size], pair.second.data.data(), std::min(pair.second.data.size(), byte_size)); + if(pair.second.samples.empty()) continue; + memcpy(&destination[pair.second.address.sector * byte_size], pair.second.samples[0].data(), std::min(pair.second.samples[0].size(), byte_size)); } } diff --git a/Storage/Disk/Encodings/MFM/Encoder.cpp b/Storage/Disk/Encodings/MFM/Encoder.cpp index d01da9420..b961191f3 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.cpp +++ b/Storage/Disk/Encodings/MFM/Encoder.cpp @@ -119,7 +119,7 @@ class FMEncoder: public Encoder { template std::shared_ptr GetTrackWithSectors( - const std::vector §ors, + const std::vector §ors, size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, size_t pre_address_mark_bytes, size_t post_address_mark_bytes, uint8_t post_address_mark_value, @@ -137,38 +137,39 @@ template std::shared_ptr for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value); // add sectors - for(const Sector §or : sectors) { + for(const Sector *sector : sectors) { // gap for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00); // sector header shifter.add_ID_address_mark(); - shifter.add_byte(sector.address.track); - shifter.add_byte(sector.address.side); - shifter.add_byte(sector.address.sector); - shifter.add_byte(sector.size); - shifter.add_crc(sector.has_header_crc_error); + shifter.add_byte(sector->address.track); + shifter.add_byte(sector->address.side); + shifter.add_byte(sector->address.sector); + shifter.add_byte(sector->size); + shifter.add_crc(sector->has_header_crc_error); // gap for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value); for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00); // data, if attached - if(!sector.data.empty()) { - if(sector.is_deleted) + // TODO: allow for weak/fuzzy data. + if(!sector->samples.empty()) { + if(sector->is_deleted) shifter.add_deleted_data_address_mark(); else shifter.add_data_address_mark(); size_t c = 0; - size_t declared_length = static_cast(128 << sector.size); - for(c = 0; c < sector.data.size() && c < declared_length; c++) { - shifter.add_byte(sector.data[c]); + size_t declared_length = static_cast(128 << sector->size); + for(c = 0; c < sector->samples[0].size() && c < declared_length; c++) { + shifter.add_byte(sector->samples[0][c]); } for(; c < declared_length; c++) { shifter.add_byte(0x00); } - shifter.add_crc(sector.has_data_crc_error); + shifter.add_crc(sector->has_data_crc_error); } // gap @@ -202,7 +203,26 @@ void Encoder::add_crc(bool incorrectly) { const size_t Storage::Encodings::MFM::DefaultSectorGapLength = std::numeric_limits::max(); +static std::vector sector_pointers(const std::vector §ors) { + std::vector pointers; + for(const Sector §or: sectors) { + pointers.push_back(§or); + } + return pointers; +} + std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { + return GetTrackWithSectors( + sector_pointers(sectors), + 26, 0xff, + 6, + 11, 0xff, + 6, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 27, 0xff, + 6250); // i.e. 250kbps (including clocks) * 60 = 15000kpm, at 300 rpm => 50 kbits/rotation => 6250 bytes/rotation +} + +std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( sectors, 26, 0xff, @@ -214,6 +234,17 @@ std::shared_ptr Storage::Encodings::MFM::GetFMTrackWithSec } std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { + return GetTrackWithSectors( + sector_pointers(sectors), + 50, 0x4e, + 12, + 22, 0x4e, + 12, + (sector_gap_length != DefaultSectorGapLength) ? sector_gap_length : 54, 0xff, + 12500); // unintelligently: double the single-density bytes/rotation (or: 500kbps @ 300 rpm) +} + +std::shared_ptr Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { return GetTrackWithSectors( sectors, 50, 0x4e, diff --git a/Storage/Disk/Encodings/MFM/Encoder.hpp b/Storage/Disk/Encodings/MFM/Encoder.hpp index 41cd6f007..791f10450 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.hpp +++ b/Storage/Disk/Encodings/MFM/Encoder.hpp @@ -28,6 +28,7 @@ extern const size_t DefaultSectorGapLength; @param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data. */ std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); +std::shared_ptr GetMFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); /*! Converts a vector of sectors into a properly-encoded FM track. @@ -37,6 +38,7 @@ std::shared_ptr GetMFMTrackWithSectors(const std::vector GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); +std::shared_ptr GetFMTrackWithSectors(const std::vector §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); class Encoder { public: diff --git a/Storage/Disk/Encodings/MFM/Sector.hpp b/Storage/Disk/Encodings/MFM/Sector.hpp index 559f770f2..38db6683e 100644 --- a/Storage/Disk/Encodings/MFM/Sector.hpp +++ b/Storage/Disk/Encodings/MFM/Sector.hpp @@ -34,7 +34,9 @@ struct Sector { Address address; uint8_t size = 0; - std::vector data; + + // Multiple samplings of the underlying data are accepted, to allow weak and fuzzy data to be communicated. + std::vector> samples; bool has_data_crc_error = false; bool has_header_crc_error = false; @@ -45,7 +47,7 @@ struct Sector { Sector(const Sector &&rhs) noexcept : address(rhs.address), size(rhs.size), - data(std::move(rhs.data)), + samples(std::move(rhs.samples)), has_data_crc_error(rhs.has_data_crc_error), has_header_crc_error(rhs.has_header_crc_error), is_deleted(rhs.is_deleted ){} diff --git a/Storage/Disk/Encodings/MFM/SegmentParser.cpp b/Storage/Disk/Encodings/MFM/SegmentParser.cpp index 25fed0618..d40d6431b 100644 --- a/Storage/Disk/Encodings/MFM/SegmentParser.cpp +++ b/Storage/Disk/Encodings/MFM/SegmentParser.cpp @@ -62,7 +62,8 @@ std::map Storage::Encodings::MFM::secto shifter.set_should_obey_syncs(true); break; default: - new_sector->data.push_back(shifter.get_byte()); + if(new_sector->samples.empty()) new_sector->samples.emplace_back(); + new_sector->samples[0].push_back(shifter.get_byte()); ++position; if(position == size + 4) { result.insert(std::make_pair(start_location, std::move(*new_sector))); diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index 0a4889664..81e26b6ae 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -27,12 +27,12 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( size_t size_read = 0; do { Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, static_cast(track), static_cast(parameters.first_sector + sector)); - if(!sector_contents) { + if(!sector_contents || sector_contents->samples.empty()) { return nullptr; } - catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end()); - sector_size = sector_contents->data.size(); + catalogue.insert(catalogue.end(), sector_contents->samples[0].begin(), sector_contents->samples[0].end()); + sector_size = sector_contents->samples[0].size(); size_read += sector_size; sector++; @@ -136,7 +136,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, static_cast(track), static_cast(parameters.first_sector + sector)); - if(!sector_contents) break; + if(!sector_contents || sector_contents->samples.empty()) break; sector++; if(sector == parameters.sectors_per_track) { sector = 0; @@ -144,7 +144,7 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( } int records_to_copy = std::min(entry->number_of_records - record, records_per_sector); - memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + static_cast(record) * 128], sector_contents->data.data(), static_cast(records_to_copy) * 128); + memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + static_cast(record) * 128], sector_contents->samples[0].data(), static_cast(records_to_copy) * 128); record += records_to_copy; } } From e384c505803e3c4d35799eeb821fe2ca3be12282 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Nov 2017 22:32:00 -0400 Subject: [PATCH 3/7] Switches FileHolder to have a usage much closer to FILE *. Thereby opens a route for file format implementations such as that appearing for CPC DSK that create an in-memory copy and perform a full rewrite. --- Storage/Disk/DiskImage/Formats/AcornADF.cpp | 12 +- Storage/Disk/DiskImage/Formats/CPCDSK.cpp | 127 +++++++++--- Storage/Disk/DiskImage/Formats/CPCDSK.hpp | 8 +- Storage/Disk/DiskImage/Formats/D64.cpp | 10 +- Storage/Disk/DiskImage/Formats/D64.hpp | 3 +- Storage/Disk/DiskImage/Formats/G64.cpp | 28 +-- Storage/Disk/DiskImage/Formats/G64.hpp | 3 +- Storage/Disk/DiskImage/Formats/HFE.cpp | 40 ++-- Storage/Disk/DiskImage/Formats/HFE.hpp | 5 +- .../Disk/DiskImage/Formats/MFMSectorDump.cpp | 22 +- .../Disk/DiskImage/Formats/MFMSectorDump.hpp | 8 +- Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp | 30 +-- Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp | 7 +- Storage/Disk/DiskImage/Formats/SSD.cpp | 8 +- Storage/FileHolder.cpp | 189 ++++++++++++------ Storage/FileHolder.hpp | 156 +++++++++------ Storage/Tape/Formats/CSW.cpp | 53 +++-- Storage/Tape/Formats/CSW.hpp | 4 +- Storage/Tape/Formats/CommodoreTAP.cpp | 19 +- Storage/Tape/Formats/CommodoreTAP.hpp | 3 +- Storage/Tape/Formats/OricTAP.cpp | 67 +++---- Storage/Tape/Formats/OricTAP.hpp | 3 +- Storage/Tape/Formats/TZX.cpp | 129 ++++++------ Storage/Tape/Formats/TZX.hpp | 4 +- Storage/Tape/Formats/TapePRG.cpp | 85 +++----- Storage/Tape/Formats/TapePRG.hpp | 3 +- Storage/Tape/Formats/ZX80O81P.cpp | 14 +- Storage/Tape/Formats/ZX80O81P.hpp | 2 +- 28 files changed, 597 insertions(+), 445 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.cpp b/Storage/Disk/DiskImage/Formats/AcornADF.cpp index 8e063224f..569681e73 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.cpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.cpp @@ -20,17 +20,17 @@ using namespace Storage::Disk; AcornADF::AcornADF(const char *file_name) : MFMSectorDump(file_name) { // very loose validation: the file needs to be a multiple of 256 bytes // and not ungainly large - if(file_stats_.st_size % static_cast(128 << sector_size)) throw ErrorNotAcornADF; - if(file_stats_.st_size < 7 * static_cast(128 << sector_size)) throw ErrorNotAcornADF; + if(file_.stats().st_size % static_cast(128 << sector_size)) throw ErrorNotAcornADF; + if(file_.stats().st_size < 7 * static_cast(128 << sector_size)) throw ErrorNotAcornADF; // check that the initial directory's 'Hugo's are present - fseek(file_, 513, SEEK_SET); + file_.seek(513, SEEK_SET); uint8_t bytes[4]; - fread(bytes, 1, 4, file_); + file_.read(bytes, 4); if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; - fseek(file_, 0x6fb, SEEK_SET); - fread(bytes, 1, 4, file_); + file_.seek(0x6fb, SEEK_SET); + file_.read(bytes, 4); if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; set_geometry(sectors_per_track, sector_size, true); diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp index 921aac070..c74f4daef 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp @@ -8,23 +8,31 @@ #include "CPCDSK.hpp" +#include "../../Encodings/MFM/Constants.hpp" #include "../../Encodings/MFM/Encoder.hpp" +#include "../../Encodings/MFM/SegmentParser.hpp" +#include "../../Track/TrackSerialiser.hpp" + +#include using namespace Storage::Disk; CPCDSK::CPCDSK(const char *file_name) : - Storage::FileHolder(file_name), is_extended_(false) { - if(!check_signature("MV - CPC", 8)) { + is_extended_(false) { + FileHolder file(file_name); + is_read_only_ = file.get_is_known_read_only(); + + if(!file.check_signature("MV - CPC")) { is_extended_ = true; - fseek(file_, 0, SEEK_SET); - if(!check_signature("EXTENDED", 8)) + file.seek(0, SEEK_SET); + if(!file.check_signature("EXTENDED")) throw ErrorNotCPCDSK; } // Don't really care about about the creator; skip. - fseek(file_, 0x30, SEEK_SET); - head_position_count_ = fgetc(file_); - head_count_ = fgetc(file_); + file.seek(0x30, SEEK_SET); + head_position_count_ = file.get8(); + head_count_ = file.get8(); // Used only for non-extended disks. long size_of_a_track = 0; @@ -34,37 +42,37 @@ CPCDSK::CPCDSK(const char *file_name) : if(is_extended_) { // Skip two unused bytes and grab the track size table. - fseek(file_, 2, SEEK_CUR); + file.seek(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(file.get8()) << 8); } } else { - size_of_a_track = fgetc16le(); + size_of_a_track = file.get16le(); } 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); + file.seek(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_)); + track->track = file.get8(); + track->side = file.get8(); // 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_)) { + switch(file.get8()) { 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_)) { + switch(file.get8()) { 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; @@ -72,15 +80,15 @@ CPCDSK::CPCDSK(const char *file_name) : } else { track->data_rate = Track::DataRate::Unknown; track->data_encoding = Track::DataEncoding::Unknown; - fseek(file_, 2, SEEK_CUR); + file.seek(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_)); + track->sector_length = file.get8(); + size_t number_of_sectors = file.get8(); + track->gap3_length = file.get8(); + track->filler_byte = file.get8(); // Sector information begins immediately after the track information table. while(number_of_sectors--) { @@ -89,12 +97,12 @@ CPCDSK::CPCDSK(const char *file_name) : // Track, side, sector, size and two FDC8272-esque status bytes are stored // per sector, in both regular and extended DSK files. - sector.address.track = static_cast(fgetc(file_)); - sector.address.side = static_cast(fgetc(file_)); - sector.address.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_)); + sector.address.track = file.get8(); + sector.address.side = file.get8(); + sector.address.sector = file.get8(); + sector.size = file.get8(); + sector.fdc_status1 = file.get8(); + sector.fdc_status2 = file.get8(); if(sector.fdc_status2 & 0x20) { // The CRC failed in the data field. @@ -131,7 +139,7 @@ CPCDSK::CPCDSK(const char *file_name) : // 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(); + size_t declared_data_size = file.get16le(); if(declared_data_size != stored_data_size) { if(declared_data_size > data_size) { number_of_samplings = declared_data_size / data_size; @@ -145,7 +153,7 @@ CPCDSK::CPCDSK(const char *file_name) : // 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); + file.seek(2, SEEK_CUR); } // As per the weak/fuzzy sector extension, multiple samplings may be stored here. @@ -158,10 +166,10 @@ CPCDSK::CPCDSK(const char *file_name) : } // Sector contents are at offset 0x100 into the track. - fseek(file_, file_offset + 0x100, SEEK_SET); + file.seek(file_offset + 0x100, SEEK_SET); for(auto §or: track->sectors) { for(auto &data : sector.samples) { - fread(data.data(), 1, data.size(), file_); + file.read(data.data(), data.size()); } } } else { @@ -185,9 +193,13 @@ int CPCDSK::get_head_count() { return head_count_; } +size_t CPCDSK::index_for_track(::Storage::Disk::Track::Address address) { + return static_cast((address.position * head_count_) + address.head); +} + 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); + size_t chronological_track = index_for_track(address); // Return a nullptr if out of range or not provided. if(chronological_track >= tracks_.size()) return nullptr; @@ -203,3 +215,56 @@ std::shared_ptr CPCDSK::get_track_at_position(::Storage::Disk::Track::Add // TODO: FM encoding, data rate? return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte); } + +void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) { + // Patch changed tracks into the disk image. + for(auto &pair: tracks) { + // Assume MFM for now; with extensions DSK can contain FM tracks. + const bool is_double_density = true; + std::map sectors = + Storage::Encodings::MFM::sectors_from_segment( + Storage::Disk::track_serialisation(*pair.second, is_double_density ? Storage::Encodings::MFM::MFMBitLength : Storage::Encodings::MFM::FMBitLength), + is_double_density); + + // Find slot for track, making it if neccessary. + size_t chronological_track = index_for_track(pair.first); + if(chronological_track >= tracks_.size()) { + tracks_.resize(chronological_track+1); + } + + // Get the track, or create it if necessary. + Track *track = tracks_[chronological_track].get(); + if(!track) { + track = new Track; + track->track = static_cast(pair.first.position); + track->side = static_cast(pair.first.head); + track->data_rate = Track::DataRate::SingleOrDoubleDensity; + track->data_encoding = Track::DataEncoding::MFM; + track->sector_length = 2; + track->gap3_length = 78; + track->filler_byte = 0xe5; + + tracks_[chronological_track] = std::unique_ptr(track); + } + + // Store sectors. + track->sectors.clear(); + for(auto &source_sector: sectors) { + track->sectors.emplace_back(); + Track::Sector §or = track->sectors.back(); + + sector.address = source_sector.second.address; + sector.size = source_sector.second.size; + sector.has_data_crc_error = source_sector.second.has_data_crc_error; + sector.has_header_crc_error = source_sector.second.has_header_crc_error; + sector.is_deleted = source_sector.second.is_deleted; + sector.samples = std::move(source_sector.second.samples); + } + } + + // Rewrite the entire disk image, in extended form. +} + +bool CPCDSK::get_is_read_only() { + return is_read_only_; +} diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index 905d58d1f..394d8dfba 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -21,7 +21,7 @@ namespace Disk { /*! Provies a @c Disk containing an Amstrad CPC-stype disk image — some arrangement of sectors with status bits. */ -class CPCDSK: public DiskImage, public Storage::FileHolder { +class CPCDSK: public DiskImage { public: /*! Construct an @c AcornADF containing content from the file with name @c file_name. @@ -38,7 +38,9 @@ class CPCDSK: public DiskImage, public Storage::FileHolder { // implemented to satisfy @c Disk int get_head_position_count() override; int get_head_count() override; - using DiskImage::get_is_read_only; + bool get_is_read_only() override; + + void set_tracks(const std::map> &tracks) override; std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; private: @@ -63,10 +65,12 @@ class CPCDSK: public DiskImage, public Storage::FileHolder { std::vector sectors; }; std::vector> tracks_; + size_t index_for_track(::Storage::Disk::Track::Address address); int head_count_; int head_position_count_; bool is_extended_; + bool is_read_only_; }; } diff --git a/Storage/Disk/DiskImage/Formats/D64.cpp b/Storage/Disk/DiskImage/Formats/D64.cpp index ce7433341..cd6bde7af 100644 --- a/Storage/Disk/DiskImage/Formats/D64.cpp +++ b/Storage/Disk/DiskImage/Formats/D64.cpp @@ -17,13 +17,13 @@ using namespace Storage::Disk; D64::D64(const char *file_name) : - Storage::FileHolder(file_name) { + file_(file_name) { // in D64, this is it for validation without imposing potential false-negative tests — check that // the file size appears to be correct. Stone-age stuff. - if(file_stats_.st_size != 174848 && file_stats_.st_size != 196608) + if(file_.stats().st_size != 174848 && file_.stats().st_size != 196608) throw ErrorNotD64; - number_of_tracks_ = (file_stats_.st_size == 174848) ? 35 : 40; + number_of_tracks_ = (file_.stats().st_size == 174848) ? 35 : 40; // then, ostensibly, this is a valid file. Hmmm. Pick a disk ID as a function of the file_name, // being the most stable thing available @@ -59,7 +59,7 @@ std::shared_ptr D64::get_track_at_position(Track::Address address) { } // seek to start of data - fseek(file_, offset_to_track * 256, SEEK_SET); + file_.seek(offset_to_track * 256, SEEK_SET); // build up a PCM sampling of the GCR version of this track @@ -117,7 +117,7 @@ std::shared_ptr D64::get_track_at_position(Track::Address address) { // get the actual contents uint8_t source_data[256]; - fread(source_data, 1, 256, file_); + file_.read(source_data, sizeof(source_data)); // compute the latest checksum checksum = 0; diff --git a/Storage/Disk/DiskImage/Formats/D64.hpp b/Storage/Disk/DiskImage/Formats/D64.hpp index 9c83669c5..2a0a8ef1b 100644 --- a/Storage/Disk/DiskImage/Formats/D64.hpp +++ b/Storage/Disk/DiskImage/Formats/D64.hpp @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing a D64 disk image — a decoded sector dump of a C1540-format disk. */ -class D64: public DiskImage, public Storage::FileHolder { +class D64: public DiskImage { public: /*! Construct a @c D64 containing content from the file with name @c file_name. @@ -38,6 +38,7 @@ class D64: public DiskImage, public Storage::FileHolder { std::shared_ptr get_track_at_position(Track::Address address) override; private: + Storage::FileHolder file_; int number_of_tracks_; uint16_t disk_id_; }; diff --git a/Storage/Disk/DiskImage/Formats/G64.cpp b/Storage/Disk/DiskImage/Formats/G64.cpp index 80b368661..3bb7870d5 100644 --- a/Storage/Disk/DiskImage/Formats/G64.cpp +++ b/Storage/Disk/DiskImage/Formats/G64.cpp @@ -15,17 +15,17 @@ using namespace Storage::Disk; G64::G64(const char *file_name) : - Storage::FileHolder(file_name) { + file_(file_name) { // read and check the file signature - if(!check_signature("GCR-1541", 8)) throw ErrorNotG64; + if(!file_.check_signature("GCR-1541")) throw ErrorNotG64; // check the version number - int version = fgetc(file_); + int version = file_.get8(); if(version != 0) throw ErrorUnknownVersion; // get the number of tracks and track size - number_of_tracks_ = static_cast(fgetc(file_)); - maximum_track_size_ = fgetc16le(); + number_of_tracks_ = file_.get8(); + maximum_track_size_ = file_.get16le(); } int G64::get_head_position_count() { @@ -43,43 +43,43 @@ std::shared_ptr G64::get_track_at_position(Track::Address address) { if(address.head >= 1) return resulting_track; // seek to this track's entry in the track table - fseek(file_, static_cast((address.position * 4) + 0xc), SEEK_SET); + file_.seek(static_cast((address.position * 4) + 0xc), SEEK_SET); // read the track offset uint32_t track_offset; - track_offset = fgetc32le(); + track_offset = file_.get32le(); // if the track offset is zero, this track doesn't exist, so... if(!track_offset) return resulting_track; // seek to the track start - fseek(file_, static_cast(track_offset), SEEK_SET); + file_.seek(static_cast(track_offset), SEEK_SET); // get the real track length uint16_t track_length; - track_length = fgetc16le(); + track_length = file_.get16le(); // grab the byte contents of this track std::vector track_contents(track_length); - fread(&track_contents[0], 1, track_length, file_); + file_.read(&track_contents[0], track_length); // seek to this track's entry in the speed zone table - fseek(file_, static_cast((address.position * 4) + 0x15c), SEEK_SET); + file_.seek(static_cast((address.position * 4) + 0x15c), SEEK_SET); // read the speed zone offsrt uint32_t speed_zone_offset; - speed_zone_offset = fgetc32le(); + speed_zone_offset = file_.get32le(); // if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant if(speed_zone_offset > 3) { // seek to start of speed zone - fseek(file_, static_cast(speed_zone_offset), SEEK_SET); + file_.seek(static_cast(speed_zone_offset), SEEK_SET); uint16_t speed_zone_length = (track_length + 3) >> 2; // read the speed zone bytes uint8_t speed_zone_contents[speed_zone_length]; - fread(speed_zone_contents, 1, speed_zone_length, file_); + file_.read(speed_zone_contents, speed_zone_length); // divide track into appropriately timed PCMSegments std::vector segments; diff --git a/Storage/Disk/DiskImage/Formats/G64.hpp b/Storage/Disk/DiskImage/Formats/G64.hpp index 92b57d3be..65fe6287c 100644 --- a/Storage/Disk/DiskImage/Formats/G64.hpp +++ b/Storage/Disk/DiskImage/Formats/G64.hpp @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing a G64 disk image — a raw but perfectly-clocked GCR stream. */ -class G64: public DiskImage, public Storage::FileHolder { +class G64: public DiskImage { public: /*! Construct a @c G64 containing content from the file with name @c file_name. @@ -41,6 +41,7 @@ class G64: public DiskImage, public Storage::FileHolder { using DiskImage::get_is_read_only; private: + Storage::FileHolder file_; uint8_t number_of_tracks_; uint16_t maximum_track_size_; }; diff --git a/Storage/Disk/DiskImage/Formats/HFE.cpp b/Storage/Disk/DiskImage/Formats/HFE.cpp index c067399e1..d9a141da6 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.cpp +++ b/Storage/Disk/DiskImage/Formats/HFE.cpp @@ -15,15 +15,15 @@ using namespace Storage::Disk; HFE::HFE(const char *file_name) : - Storage::FileHolder(file_name) { - if(!check_signature("HXCPICFE", 8)) throw ErrorNotHFE; + file_(file_name) { + if(!file_.check_signature("HXCPICFE")) throw ErrorNotHFE; - if(fgetc(file_)) throw ErrorNotHFE; - track_count_ = fgetc(file_); - head_count_ = fgetc(file_); + if(file_.get8()) throw ErrorNotHFE; + track_count_ = file_.get8(); + head_count_ = file_.get8(); - fseek(file_, 7, SEEK_CUR); - track_list_offset_ = static_cast(fgetc16le() << 9); + file_.seek(7, SEEK_CUR); + track_list_offset_ = static_cast(file_.get16le()) << 9; } HFE::~HFE() { @@ -47,13 +47,13 @@ int HFE::get_head_count() { uint16_t HFE::seek_track(Track::Address address) { // Get track position and length from the lookup table; data is then always interleaved // based on an assumption of two heads. - fseek(file_, track_list_offset_ + address.position * 4, SEEK_SET); + file_.seek(track_list_offset_ + address.position * 4, SEEK_SET); - long track_offset = static_cast(fgetc16le() << 9); - uint16_t track_length = fgetc16le(); + long track_offset = static_cast(file_.get16le()) << 9; + uint16_t track_length = file_.get16le(); - fseek(file_, track_offset, SEEK_SET); - if(address.head) fseek(file_, 256, SEEK_CUR); + file_.seek(track_offset, SEEK_SET); + if(address.head) file_.seek(256, SEEK_CUR); return track_length / 2; } @@ -61,7 +61,7 @@ uint16_t HFE::seek_track(Track::Address address) { std::shared_ptr HFE::get_track_at_position(Track::Address address) { PCMSegment segment; { - std::lock_guard lock_guard(file_access_mutex_); + std::lock_guard lock_guard(file_.get_file_access_mutex()); uint16_t track_length = seek_track(address); segment.data.resize(track_length); @@ -70,9 +70,9 @@ std::shared_ptr HFE::get_track_at_position(Track::Address address) { uint16_t c = 0; while(c < track_length) { uint16_t length = static_cast(std::min(256, track_length - c)); - fread(&segment.data[c], 1, length, file_); + file_.read(&segment.data[c], length); c += length; - fseek(file_, 256, SEEK_CUR); + file_.seek(256, SEEK_CUR); } } @@ -86,7 +86,7 @@ std::shared_ptr HFE::get_track_at_position(Track::Address address) { void HFE::set_tracks(const std::map> &tracks) { for(auto &track : tracks) { - std::unique_lock lock_guard(file_access_mutex_); + std::unique_lock lock_guard(file_.get_file_access_mutex()); uint16_t track_length = seek_track(track.first); lock_guard.unlock(); @@ -100,10 +100,14 @@ void HFE::set_tracks(const std::map> &tra uint16_t c = 0; while(c < data_length) { uint16_t length = static_cast(std::min(256, data_length - c)); - fwrite(&segment.data[c], 1, length, file_); + file_.write(&segment.data[c], length); c += length; - fseek(file_, 256, SEEK_CUR); + file_.seek(256, SEEK_CUR); } lock_guard.unlock(); } } + +bool HFE::get_is_read_only() { + return file_.get_is_known_read_only(); +} diff --git a/Storage/Disk/DiskImage/Formats/HFE.hpp b/Storage/Disk/DiskImage/Formats/HFE.hpp index 1720666b7..f2a27399e 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.hpp +++ b/Storage/Disk/DiskImage/Formats/HFE.hpp @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing an HFE disk image — a bit stream representation of a floppy. */ -class HFE: public DiskImage, public Storage::FileHolder { +class HFE: public DiskImage { public: /*! Construct an @c SSD containing content from the file with name @c file_name. @@ -36,11 +36,12 @@ class HFE: public DiskImage, public Storage::FileHolder { // implemented to satisfy @c Disk int get_head_position_count() override; int get_head_count() override; - using Storage::FileHolder::get_is_read_only; + bool get_is_read_only() override; void set_tracks(const std::map> &tracks) override; std::shared_ptr get_track_at_position(Track::Address address) override; private: + Storage::FileHolder file_; uint16_t seek_track(Track::Address address); int head_count_; diff --git a/Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp b/Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp index 8f558bf4f..d73dd9f8a 100644 --- a/Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp +++ b/Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp @@ -12,7 +12,7 @@ using namespace Storage::Disk; -MFMSectorDump::MFMSectorDump(const char *file_name) : Storage::FileHolder(file_name) {} +MFMSectorDump::MFMSectorDump(const char *file_name) : file_(file_name) {} void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density) { sectors_per_track_ = sectors_per_track; @@ -27,9 +27,9 @@ std::shared_ptr MFMSectorDump::get_track_at_position(Track::Address addre long file_offset = get_file_offset_for_position(address); { - std::lock_guard lock_guard(file_access_mutex_); - fseek(file_, file_offset, SEEK_SET); - fread(sectors, 1, sizeof(sectors), file_); + std::lock_guard lock_guard(file_.get_file_access_mutex()); + file_.seek(file_offset, SEEK_SET); + file_.read(sectors, sizeof(sectors)); } return track_for_sectors(sectors, static_cast(address.position), static_cast(address.head), 0, sector_size_, is_double_density_); @@ -46,10 +46,14 @@ void MFMSectorDump::set_tracks(const std::map(sectors_per_track_-1), sector_size_, is_double_density_); long file_offset = get_file_offset_for_position(track.first); - std::lock_guard lock_guard(file_access_mutex_); - ensure_file_is_at_least_length(file_offset); - fseek(file_, file_offset, SEEK_SET); - fwrite(parsed_track, 1, sizeof(parsed_track), file_); + std::lock_guard lock_guard(file_.get_file_access_mutex()); + file_.ensure_is_at_least_length(file_offset); + file_.seek(file_offset, SEEK_SET); + file_.write(parsed_track, sizeof(parsed_track)); } - fflush(file_); + file_.flush(); +} + +bool MFMSectorDump::get_is_read_only() { + return file_.get_is_known_read_only(); } diff --git a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp index 489072cdb..9fffcf677 100644 --- a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp +++ b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp @@ -18,17 +18,19 @@ namespace Disk { /*! Provies the base for writeable [M]FM disk images that just contain contiguous sector content dumps. */ -class MFMSectorDump: public DiskImage, public Storage::FileHolder { +class MFMSectorDump: public DiskImage { public: MFMSectorDump(const char *file_name); void set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density); - using Storage::FileHolder::get_is_read_only; + bool get_is_read_only() override; void set_tracks(const std::map> &tracks) override; std::shared_ptr get_track_at_position(Track::Address address) override; + protected: + Storage::FileHolder file_; + private: - std::mutex file_access_mutex_; virtual long get_file_offset_for_position(Track::Address address) = 0; int sectors_per_track_ = 0; diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp index e9ec533bf..6b5637c50 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp @@ -17,13 +17,13 @@ using namespace Storage::Disk; OricMFMDSK::OricMFMDSK(const char *file_name) : - Storage::FileHolder(file_name) { - if(!check_signature("MFM_DISK", 8)) + file_(file_name) { + if(!file_.check_signature("MFM_DISK")) throw ErrorNotOricMFMDSK; - head_count_ = fgetc32le(); - track_count_ = fgetc32le(); - geometry_type_ = fgetc32le(); + head_count_ = file_.get32le(); + track_count_ = file_.get32le(); + geometry_type_ = file_.get32le(); if(geometry_type_ < 1 || geometry_type_ > 2) throw ErrorNotOricMFMDSK; @@ -53,8 +53,8 @@ long OricMFMDSK::get_file_offset_for_position(Track::Address address) { std::shared_ptr OricMFMDSK::get_track_at_position(Track::Address address) { PCMSegment segment; { - std::lock_guard lock_guard(file_access_mutex_); - fseek(file_, get_file_offset_for_position(address), SEEK_SET); + std::lock_guard lock_guard(file_.get_file_access_mutex()); + file_.seek(get_file_offset_for_position(address), SEEK_SET); // The file format omits clock bits. So it's not a genuine MFM capture. // A consumer must contextually guess when an FB, FC, etc is meant to be a control mark. @@ -63,7 +63,7 @@ std::shared_ptr OricMFMDSK::get_track_at_position(Track::Address address) std::unique_ptr encoder = Encodings::MFM::GetMFMEncoder(segment.data); bool did_sync = false; while(track_offset < 6250) { - uint8_t next_byte = static_cast(fgetc(file_)); + uint8_t next_byte = file_.get8(); track_offset++; switch(next_byte) { @@ -75,7 +75,7 @@ std::shared_ptr OricMFMDSK::get_track_at_position(Track::Address address) case 0xfe: for(int byte = 0; byte < 6; byte++) { - last_header[byte] = static_cast(fgetc(file_)); + last_header[byte] = file_.get8(); encoder->add_byte(last_header[byte]); track_offset++; if(track_offset == 6250) break; @@ -84,7 +84,7 @@ std::shared_ptr OricMFMDSK::get_track_at_position(Track::Address address) case 0xfb: for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) { - encoder->add_byte(static_cast(fgetc(file_))); + encoder->add_byte(file_.get8()); track_offset++; if(track_offset == 6250) break; } @@ -156,9 +156,13 @@ void OricMFMDSK::set_tracks(const std::map lock_guard(file_access_mutex_); - fseek(file_, file_offset, SEEK_SET); + std::lock_guard lock_guard(file_.get_file_access_mutex()); + file_.seek(file_offset, SEEK_SET); size_t track_size = std::min(static_cast(6400), parsed_track.size()); - fwrite(parsed_track.data(), 1, track_size, file_); + file_.write(parsed_track.data(), track_size); } } + +bool OricMFMDSK::get_is_read_only() { + return file_.get_is_known_read_only(); +} diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp index 25ae3cb41..40d350636 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp @@ -18,7 +18,7 @@ namespace Disk { /*! Provies a @c Disk containing an Oric MFM-stype disk image — a stream of the MFM data bits with clocks omitted. */ -class OricMFMDSK: public DiskImage, public Storage::FileHolder { +class OricMFMDSK: public DiskImage { public: /*! Construct an @c OricMFMDSK containing content from the file with name @c file_name. @@ -34,12 +34,13 @@ class OricMFMDSK: public DiskImage, public Storage::FileHolder { // implemented to satisfy @c Disk int get_head_position_count() override; int get_head_count() override; - using Storage::FileHolder::get_is_read_only; + bool get_is_read_only() override; + void set_tracks(const std::map> &tracks) override; std::shared_ptr get_track_at_position(Track::Address address) override; private: - std::mutex file_access_mutex_; + Storage::FileHolder file_; long get_file_offset_for_position(Track::Address address); uint32_t head_count_; diff --git a/Storage/Disk/DiskImage/Formats/SSD.cpp b/Storage/Disk/DiskImage/Formats/SSD.cpp index 3d9939a3a..d5fcf570b 100644 --- a/Storage/Disk/DiskImage/Formats/SSD.cpp +++ b/Storage/Disk/DiskImage/Formats/SSD.cpp @@ -21,13 +21,13 @@ SSD::SSD(const char *file_name) : MFMSectorDump(file_name) { // very loose validation: the file needs to be a multiple of 256 bytes // and not ungainly large - if(file_stats_.st_size & 255) throw ErrorNotSSD; - if(file_stats_.st_size < 512) throw ErrorNotSSD; - if(file_stats_.st_size > 800*256) throw ErrorNotSSD; + if(file_.stats().st_size & 255) throw ErrorNotSSD; + if(file_.stats().st_size < 512) throw ErrorNotSSD; + if(file_.stats().st_size > 800*256) throw ErrorNotSSD; // this has two heads if the suffix is .dsd, one if it's .ssd head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1; - track_count_ = static_cast(file_stats_.st_size / (256 * 10)); + track_count_ = static_cast(file_.stats().st_size / (256 * 10)); if(track_count_ < 40) track_count_ = 40; else if(track_count_ < 80) track_count_ = 80; diff --git a/Storage/FileHolder.cpp b/Storage/FileHolder.cpp index f8c357d69..d9c6e88c1 100644 --- a/Storage/FileHolder.cpp +++ b/Storage/FileHolder.cpp @@ -16,19 +16,122 @@ FileHolder::~FileHolder() { if(file_) fclose(file_); } -FileHolder::FileHolder(const std::string &file_name) : file_(nullptr), name_(file_name) { +FileHolder::FileHolder(const std::string &file_name, FileMode ideal_mode) + : name_(file_name) { stat(file_name.c_str(), &file_stats_); is_read_only_ = false; - file_ = fopen(file_name.c_str(), "rb+"); - if(!file_) { - is_read_only_ = true; - file_ = fopen(file_name.c_str(), "rb"); + + switch(ideal_mode) { + case FileMode::ReadWrite: + file_ = fopen(file_name.c_str(), "rb+"); + if(file_) break; + is_read_only_ = true; + + // deliberate fallthrough... + case FileMode::Read: + file_ = fopen(file_name.c_str(), "rb"); + break; + + case FileMode::Rewrite: + file_ = fopen(file_name.c_str(), "w"); + break; } + if(!file_) throw ErrorCantOpen; } +uint32_t FileHolder::get32le() { + uint32_t result = (uint32_t)fgetc(file_); + result |= (uint32_t)(fgetc(file_) << 8); + result |= (uint32_t)(fgetc(file_) << 16); + result |= (uint32_t)(fgetc(file_) << 24); + + return result; +} + +uint32_t FileHolder::get32be() { + uint32_t result = (uint32_t)(fgetc(file_) << 24); + result |= (uint32_t)(fgetc(file_) << 16); + result |= (uint32_t)(fgetc(file_) << 8); + result |= (uint32_t)fgetc(file_); + + return result; +} + +uint32_t FileHolder::get24le() { + uint32_t result = (uint32_t)fgetc(file_); + result |= (uint32_t)(fgetc(file_) << 8); + result |= (uint32_t)(fgetc(file_) << 16); + + return result; +} + +uint32_t FileHolder::get24be() { + uint32_t result = (uint32_t)(fgetc(file_) << 16); + result |= (uint32_t)(fgetc(file_) << 8); + result |= (uint32_t)fgetc(file_); + + return result; +} + +uint16_t FileHolder::get16le() { + uint16_t result = static_cast(fgetc(file_)); + result |= static_cast(fgetc(file_) << 8); + + return result; +} + +uint16_t FileHolder::get16be() { + uint16_t result = static_cast(fgetc(file_) << 8); + result |= static_cast(fgetc(file_)); + + return result; +} + +uint8_t FileHolder::get8() { + return static_cast(fgetc(file_)); +} + +std::vector FileHolder::read(size_t size) { + std::vector result(size); + fread(result.data(), 1, size, file_); + return result; +} + +size_t FileHolder::read(uint8_t *buffer, size_t size) { + return fread(buffer, 1, size, file_); +} + +size_t FileHolder::write(const std::vector &buffer) { + return fwrite(buffer.data(), 1, buffer.size(), file_); +} + +size_t FileHolder::write(const uint8_t *buffer, size_t size) { + return fwrite(buffer, 1, size, file_); +} + +void FileHolder::seek(long offset, int whence) { + fseek(file_, offset, whence); +} + +long FileHolder::tell() { + return ftell(file_); +} + +void FileHolder::flush() { + fflush(file_); +} + +bool FileHolder::eof() { + return feof(file_); +} + +FileHolder::BitStream FileHolder::get_bitstream(bool lsb_first) { + return BitStream(file_, lsb_first); +} + bool FileHolder::check_signature(const char *signature, size_t length) { - if(!length) length = strlen(signature)+1; + if(!length) length = strlen(signature); // read and check the file signature char stored_signature[12]; @@ -37,67 +140,35 @@ bool FileHolder::check_signature(const char *signature, size_t length) { return true; } -uint32_t FileHolder::fgetc32le() { - uint32_t result = (uint32_t)fgetc(file_); - result |= (uint32_t)(fgetc(file_) << 8); - result |= (uint32_t)(fgetc(file_) << 16); - result |= (uint32_t)(fgetc(file_) << 24); +std::string FileHolder::extension() { + size_t pointer = name_.size() - 1; + while(pointer > 0 && name_[pointer] != '.') pointer--; + if(name_[pointer] == '.') pointer++; - return result; + std::string extension = name_.substr(pointer); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + return extension; } -uint32_t FileHolder::fgetc24le() { - uint32_t result = (uint32_t)fgetc(file_); - result |= (uint32_t)(fgetc(file_) << 8); - result |= (uint32_t)(fgetc(file_) << 16); - - return result; +void FileHolder::ensure_is_at_least_length(long length) { + fseek(file_, 0, SEEK_END); + long bytes_to_write = length - ftell(file_); + if(bytes_to_write > 0) { + uint8_t *empty = new uint8_t[static_cast(bytes_to_write)]; + memset(empty, 0, static_cast(bytes_to_write)); + fwrite(empty, sizeof(uint8_t), static_cast(bytes_to_write), file_); + delete[] empty; + } } -uint16_t FileHolder::fgetc16le() { - uint16_t result = static_cast(fgetc(file_)); - result |= static_cast(fgetc(file_) << 8); - - return result; -} - -uint32_t FileHolder::fgetc32be() { - uint32_t result = (uint32_t)(fgetc(file_) << 24); - result |= (uint32_t)(fgetc(file_) << 16); - result |= (uint32_t)(fgetc(file_) << 8); - result |= (uint32_t)fgetc(file_); - - return result; -} - -uint16_t FileHolder::fgetc16be() { - uint16_t result = static_cast(fgetc(file_) << 8); - result |= static_cast(fgetc(file_)); - - return result; -} - -bool FileHolder::get_is_read_only() { +bool FileHolder::get_is_known_read_only() { return is_read_only_; } -void FileHolder::ensure_file_is_at_least_length(long length) { - fseek(file_, 0, SEEK_END); - long bytes_to_write = length - ftell(file_); - if(bytes_to_write > 0) { - uint8_t *empty = new uint8_t[static_cast(bytes_to_write)]; - memset(empty, 0, static_cast(bytes_to_write)); - fwrite(empty, sizeof(uint8_t), static_cast(bytes_to_write), file_); - delete[] empty; - } +struct stat &FileHolder::stats() { + return file_stats_; } -std::string FileHolder::extension() { - size_t pointer = name_.size() - 1; - while(pointer > 0 && name_[pointer] != '.') pointer--; - if(name_[pointer] == '.') pointer++; - - std::string extension = name_.substr(pointer); - std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); - return extension; +std::mutex &FileHolder::get_file_access_mutex() { + return file_access_mutex_; } diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index a553cf926..add50aac0 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -14,84 +14,76 @@ #include #include #include +#include namespace Storage { -class FileHolder { +class FileHolder final { public: enum { ErrorCantOpen = -1 }; + + enum class FileMode { + ReadWrite, + Read, + Rewrite + }; - virtual ~FileHolder(); - - protected: - FileHolder(const std::string &file_name); + ~FileHolder(); + FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite); /*! - Reads @c length bytes from the file and compares them to the first - @c length bytes of @c signature. If @c length is 0, it is computed - as the length of @c signature up to and including the terminating null. - - @returns @c true if the bytes read match the signature; @c false otherwise. - */ - bool check_signature(const char *signature, size_t length); - - /*! - Performs @c fgetc four times on @c file_, casting each result to a @c uint32_t + Performs @c get8 four times on @c file, casting each result to a @c uint32_t and returning the four assembled in little endian order. */ - uint32_t fgetc32le(); + uint32_t get32le(); /*! - Performs @c fgetc three times on @c file_, casting each result to a @c uint32_t - and returning the three assembled in little endian order. - */ - uint32_t fgetc24le(); - - /*! - Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t - and returning the two assembled in little endian order. - */ - uint16_t fgetc16le(); - - /*! - Performs @c fgetc four times on @c file_, casting each result to a @c uint32_t + Performs @c get8 four times on @c file, casting each result to a @c uint32_t and returning the four assembled in big endian order. */ - uint32_t fgetc32be(); + uint32_t get32be(); /*! - Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t + Performs @c get8 three times on @c file, casting each result to a @c uint32_t + and returning the three assembled in little endian order. + */ + uint32_t get24le(); + + /*! + Performs @c get8 three times on @c file, casting each result to a @c uint32_t + and returning the three assembled in big endian order. + */ + uint32_t get24be(); + + /*! + Performs @c get8 two times on @c file, casting each result to a @c uint32_t + and returning the two assembled in little endian order. + */ + uint16_t get16le(); + + /*! + Performs @c get8 two times on @c file, casting each result to a @c uint32_t and returning the two assembled in big endian order. */ - uint16_t fgetc16be(); + uint16_t get16be(); - /*! - Determines and returns the file extension — everything from the final character - back to the first dot. The string is converted to lowercase before being returned. - */ - std::string extension(); - - /*! - Ensures the file is at least @c length bytes long, appending 0s until it is - if necessary. - */ - void ensure_file_is_at_least_length(long length); - - /*! - @returns @c true if this file is read-only; @c false otherwise. - */ - bool get_is_read_only(); + /*! Reads a single byte from @c file */ + uint8_t get8(); + + std::vector read(size_t size); + size_t read(uint8_t *buffer, size_t size); + size_t write(const std::vector &); + size_t write(const uint8_t *buffer, size_t size); + + void seek(long offset, int whence); + long tell(); + void flush(); + bool eof(); class BitStream { public: - BitStream(FILE *f, bool lsb_first) : - file_(f), - lsb_first_(lsb_first), - next_value_(0), - bits_remaining_(0) {} - uint8_t get_bits(int q) { uint8_t result = 0; while(q--) { @@ -101,6 +93,13 @@ class FileHolder { } private: + BitStream(FILE *file, bool lsb_first) : + file_(file), + lsb_first_(lsb_first), + next_value_(0), + bits_remaining_(0) {} + friend FileHolder; + FILE *file_; bool lsb_first_; uint8_t next_value_; @@ -126,14 +125,53 @@ class FileHolder { return bit; } }; + + /*! + Obtains a BitStream for reading from the file from the current reading cursor. + */ + BitStream get_bitstream(bool lsb_first); - FILE *file_; - struct stat file_stats_; - std::mutex file_access_mutex_; + /*! + Reads @c length bytes from the file and compares them to the first + @c length bytes of @c signature. If @c length is 0, it is computed + as the length of @c signature not including the terminating null. + + @returns @c true if the bytes read match the signature; @c false otherwise. + */ + bool check_signature(const char *signature, size_t length = 0); + + /*! + Determines and returns the file extension — everything from the final character + back to the first dot. The string is converted to lowercase before being returned. + */ + std::string extension(); + + /*! + Ensures the file is at least @c length bytes long, appending 0s until it is + if necessary. + */ + void ensure_is_at_least_length(long length); + + /*! + @returns @c true if this file is read-only; @c false otherwise. + */ + bool get_is_known_read_only(); + + /*! + @returns the stat struct describing this file. + */ + struct stat &stats(); + + std::mutex &get_file_access_mutex(); - const std::string name_; private: - bool is_read_only_; + FILE *file_ = nullptr; + const std::string name_; + + struct stat file_stats_; + bool is_read_only_ = false; + + std::mutex file_access_mutex_; }; } diff --git a/Storage/Tape/Formats/CSW.cpp b/Storage/Tape/Formats/CSW.cpp index 102c15dea..63e238ac7 100644 --- a/Storage/Tape/Formats/CSW.cpp +++ b/Storage/Tape/Formats/CSW.cpp @@ -11,22 +11,21 @@ using namespace Storage::Tape; CSW::CSW(const char *file_name) : - Storage::FileHolder(file_name), + file_(file_name), source_data_pointer_(0) { - if(file_stats_.st_size < 0x20) throw ErrorNotCSW; + if(file_.stats().st_size < 0x20) throw ErrorNotCSW; // Check signature. - char identifier[22]; - char signature[] = "Compressed Square Wave"; - fread(identifier, 1, strlen(signature), file_); - if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotCSW; + if(!file_.check_signature("Compressed Square Wave")) { + throw ErrorNotCSW; + } // Check terminating byte. - if(fgetc(file_) != 0x1a) throw ErrorNotCSW; + if(file_.get8() != 0x1a) throw ErrorNotCSW; // Get version file number. - uint8_t major_version = static_cast(fgetc(file_)); - uint8_t minor_version = static_cast(fgetc(file_)); + uint8_t major_version = file_.get8(); + uint8_t minor_version = file_.get8(); // Reject if this is an unknown version. if(major_version > 2 || !major_version || minor_version > 1) throw ErrorNotCSW; @@ -34,28 +33,28 @@ CSW::CSW(const char *file_name) : // The header now diverges based on version. uint32_t number_of_waves = 0; if(major_version == 1) { - pulse_.length.clock_rate = fgetc16le(); + pulse_.length.clock_rate = file_.get16le(); - if(fgetc(file_) != 1) throw ErrorNotCSW; + if(file_.get8() != 1) throw ErrorNotCSW; compression_type_ = RLE; - pulse_.type = (fgetc(file_) & 1) ? Pulse::High : Pulse::Low; + pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low; - fseek(file_, 0x20, SEEK_SET); + file_.seek(0x20, SEEK_SET); } else { - pulse_.length.clock_rate = fgetc32le(); - number_of_waves = fgetc32le(); - switch(fgetc(file_)) { + pulse_.length.clock_rate = file_.get32le(); + number_of_waves = file_.get32le(); + switch(file_.get8()) { case 1: compression_type_ = RLE; break; case 2: compression_type_ = ZRLE; break; default: throw ErrorNotCSW; } - pulse_.type = (fgetc(file_) & 1) ? Pulse::High : Pulse::Low; - uint8_t extension_length = static_cast(fgetc(file_)); + pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low; + uint8_t extension_length = file_.get8(); - if(file_stats_.st_size < 0x34 + extension_length) throw ErrorNotCSW; - fseek(file_, 0x34 + extension_length, SEEK_SET); + if(file_.stats().st_size < 0x34 + extension_length) throw ErrorNotCSW; + file_.seek(0x34 + extension_length, SEEK_SET); } if(compression_type_ == ZRLE) { @@ -65,9 +64,9 @@ CSW::CSW(const char *file_name) : source_data_.resize(static_cast(number_of_waves) * 5); std::vector file_data; - size_t remaining_data = static_cast(file_stats_.st_size) - static_cast(ftell(file_)); + size_t remaining_data = static_cast(file_.stats().st_size) - static_cast(file_.tell()); file_data.resize(remaining_data); - fread(file_data.data(), sizeof(uint8_t), remaining_data, file_); + file_.read(file_data.data(), remaining_data); // uncompress will tell how many compressed bytes there actually were, so use its // modification of output_length to throw away all the memory that isn't actually @@ -76,7 +75,7 @@ CSW::CSW(const char *file_name) : uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size()); source_data_.resize(static_cast(output_length)); } else { - rle_start_ = ftell(file_); + rle_start_ = file_.tell(); } invert_pulse(); @@ -84,7 +83,7 @@ CSW::CSW(const char *file_name) : uint8_t CSW::get_next_byte() { switch(compression_type_) { - case RLE: return static_cast(fgetc(file_)); + case RLE: return file_.get8(); case ZRLE: { if(source_data_pointer_ == source_data_.size()) return 0xff; uint8_t result = source_data_[source_data_pointer_]; @@ -96,7 +95,7 @@ uint8_t CSW::get_next_byte() { uint32_t CSW::get_next_int32le() { switch(compression_type_) { - case RLE: return fgetc32le(); + case RLE: return file_.get32le(); case ZRLE: { if(source_data_pointer_ > source_data_.size() - 4) return 0xffff; uint32_t result = (uint32_t)( @@ -116,14 +115,14 @@ void CSW::invert_pulse() { bool CSW::is_at_end() { switch(compression_type_) { - case RLE: return (bool)feof(file_); + case RLE: return file_.eof(); case ZRLE: return source_data_pointer_ == source_data_.size(); } } void CSW::virtual_reset() { switch(compression_type_) { - case RLE: fseek(file_, rle_start_, SEEK_SET); break; + case RLE: file_.seek(rle_start_, SEEK_SET); break; case ZRLE: source_data_pointer_ = 0; break; } } diff --git a/Storage/Tape/Formats/CSW.hpp b/Storage/Tape/Formats/CSW.hpp index 6f240e10b..da4ee52de 100644 --- a/Storage/Tape/Formats/CSW.hpp +++ b/Storage/Tape/Formats/CSW.hpp @@ -21,7 +21,7 @@ namespace Tape { /*! Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. */ -class CSW: public Tape, public Storage::FileHolder { +class CSW: public Tape { public: /*! Constructs a @c CSW containing content from the file with name @c file_name. @@ -38,6 +38,8 @@ class CSW: public Tape, public Storage::FileHolder { bool is_at_end(); private: + Storage::FileHolder file_; + void virtual_reset(); Pulse virtual_get_next_pulse(); diff --git a/Storage/Tape/Formats/CommodoreTAP.cpp b/Storage/Tape/Formats/CommodoreTAP.cpp index eb01cc1f6..428339cbb 100644 --- a/Storage/Tape/Formats/CommodoreTAP.cpp +++ b/Storage/Tape/Formats/CommodoreTAP.cpp @@ -14,14 +14,13 @@ using namespace Storage::Tape; CommodoreTAP::CommodoreTAP(const char *file_name) : is_at_end_(false), - Storage::FileHolder(file_name) + file_(file_name) { - if(!check_signature("C64-TAPE-RAW", 12)) + if(!file_.check_signature("C64-TAPE-RAW")) throw ErrorNotCommodoreTAP; // check the file version - int version = fgetc(file_); - switch(version) + switch(file_.get8()) { case 0: updated_layout_ = false; break; case 1: updated_layout_ = true; break; @@ -29,10 +28,10 @@ CommodoreTAP::CommodoreTAP(const char *file_name) : } // skip reserved bytes - fseek(file_, 3, SEEK_CUR); + file_.seek(3, SEEK_CUR); // read file size - file_size_ = fgetc32le(); + file_size_ = file_.get32le(); // set up for pulse output at the PAL clock rate, with each high and // low being half of whatever length values will be read; pretend that @@ -44,7 +43,7 @@ CommodoreTAP::CommodoreTAP(const char *file_name) : void CommodoreTAP::virtual_reset() { - fseek(file_, 0x14, SEEK_SET); + file_.seek(0x14, SEEK_SET); current_pulse_.type = Pulse::High; is_at_end_ = false; } @@ -64,17 +63,17 @@ Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse() if(current_pulse_.type == Pulse::High) { uint32_t next_length; - uint8_t next_byte = static_cast(fgetc(file_)); + uint8_t next_byte = file_.get8(); if(!updated_layout_ || next_byte > 0) { next_length = (uint32_t)next_byte << 3; } else { - next_length = fgetc24le(); + next_length = file_.get24le(); } - if(feof(file_)) + if(file_.eof()) { is_at_end_ = true; current_pulse_.length.length = current_pulse_.length.clock_rate; diff --git a/Storage/Tape/Formats/CommodoreTAP.hpp b/Storage/Tape/Formats/CommodoreTAP.hpp index b55feac55..d1f981ad0 100644 --- a/Storage/Tape/Formats/CommodoreTAP.hpp +++ b/Storage/Tape/Formats/CommodoreTAP.hpp @@ -19,7 +19,7 @@ namespace Tape { /*! Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings. */ -class CommodoreTAP: public Tape, public Storage::FileHolder { +class CommodoreTAP: public Tape { public: /*! Constructs a @c CommodoreTAP containing content from the file with name @c file_name. @@ -36,6 +36,7 @@ class CommodoreTAP: public Tape, public Storage::FileHolder { bool is_at_end(); private: + Storage::FileHolder file_; void virtual_reset(); Pulse virtual_get_next_pulse(); diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 30c431899..b5f6e8825 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -13,48 +13,41 @@ using namespace Storage::Tape; OricTAP::OricTAP(const char *file_name) : - Storage::FileHolder(file_name) + file_(file_name) { // check the file signature - if(!check_signature("\x16\x16\x16\x24", 4)) + if(!file_.check_signature("\x16\x16\x16\x24", 4)) throw ErrorNotOricTAP; // then rewind and start again virtual_reset(); } -void OricTAP::virtual_reset() -{ - fseek(file_, 0, SEEK_SET); +void OricTAP::virtual_reset() { + file_.seek(0, SEEK_SET); bit_count_ = 13; phase_ = next_phase_ = LeadIn; phase_counter_ = 0; pulse_counter_ = 0; } -Tape::Pulse OricTAP::virtual_get_next_pulse() -{ +Tape::Pulse OricTAP::virtual_get_next_pulse() { // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s. - if(bit_count_ == 13) - { - if(next_phase_ != phase_) - { + if(bit_count_ == 13) { + if(next_phase_ != phase_) { phase_ = next_phase_; phase_counter_ = 0; } bit_count_ = 0; uint8_t next_byte = 0; - switch(phase_) - { + switch(phase_) { case LeadIn: next_byte = phase_counter_ < 258 ? 0x16 : 0x24; phase_counter_++; - if(phase_counter_ == 259) // 256 artificial bytes plus the three in the file = 259 - { - while(1) - { - if(fgetc(file_) != 0x16) break; + if(phase_counter_ == 259) { // 256 artificial bytes plus the three in the file = 259 + while(1) { + if(file_.get8() != 0x16) break; } next_phase_ = Header; } @@ -69,19 +62,17 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() // [6, 7]: start address of data // 8: "unused" (on the Oric 1) // [9...]: filename, up to NULL byte - next_byte = static_cast(fgetc(file_)); + next_byte = file_.get8(); if(phase_counter_ == 4) data_end_address_ = static_cast(next_byte << 8); if(phase_counter_ == 5) data_end_address_ |= next_byte; if(phase_counter_ == 6) data_start_address_ = static_cast(next_byte << 8); if(phase_counter_ == 7) data_start_address_ |= next_byte; - if(phase_counter_ >= 9 && !next_byte) // advance after the filename-ending NULL byte - { + if(phase_counter_ >= 9 && !next_byte) { // advance after the filename-ending NULL byte next_phase_ = Gap; } - if(feof(file_)) - { + if(file_.eof()) { next_phase_ = End; } phase_counter_++; @@ -89,23 +80,19 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() case Gap: phase_counter_++; - if(phase_counter_ == 8) - { + if(phase_counter_ == 8) { next_phase_ = Data; } break; case Data: - next_byte = static_cast(fgetc(file_)); + next_byte = file_.get8(); phase_counter_++; - if(phase_counter_ >= (data_end_address_ - data_start_address_)+1) - { - if(next_byte == 0x16) - { + if(phase_counter_ >= (data_end_address_ - data_start_address_)+1) { + if(next_byte == 0x16) { next_phase_ = LeadIn; } - else if(feof(file_)) - { + else if(file_.eof()) { next_phase_ = End; } } @@ -129,8 +116,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() pulse.length.clock_rate = 4800; int next_bit; - switch(phase_) - { + switch(phase_) { case End: pulse.type = Pulse::Zero; pulse.length.length = 4800; @@ -147,26 +133,21 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() break; } - if(next_bit) - { + if(next_bit) { pulse.length.length = 1; - } - else - { + } else { pulse.length.length = pulse_counter_ ? 2 : 1; } pulse.type = pulse_counter_ ? Pulse::High : Pulse::Low; // TODO pulse_counter_ ^= 1; - if(!pulse_counter_) - { + if(!pulse_counter_) { current_value_ >>= 1; bit_count_++; } return pulse; } -bool OricTAP::is_at_end() -{ +bool OricTAP::is_at_end() { return phase_ == End; } diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp index 53b56a7f2..5e1214ce9 100644 --- a/Storage/Tape/Formats/OricTAP.hpp +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -19,7 +19,7 @@ namespace Tape { /*! Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. */ -class OricTAP: public Tape, public Storage::FileHolder { +class OricTAP: public Tape { public: /*! Constructs an @c OricTAP containing content from the file with name @c file_name. @@ -36,6 +36,7 @@ class OricTAP: public Tape, public Storage::FileHolder { bool is_at_end(); private: + Storage::FileHolder file_; void virtual_reset(); Pulse virtual_get_next_pulse(); diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp index 0cd130ccb..1c5dfe9ed 100644 --- a/Storage/Tape/Formats/TZX.cpp +++ b/Storage/Tape/Formats/TZX.cpp @@ -16,19 +16,16 @@ const unsigned int TZXClockMSMultiplier = 3500; } TZX::TZX(const char *file_name) : - Storage::FileHolder(file_name), + file_(file_name), current_level_(false) { // Check for signature followed by a 0x1a - char identifier[7]; - char signature[] = "ZXTape!"; - fread(identifier, 1, strlen(signature), file_); - if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotTZX; - if(fgetc(file_) != 0x1a) throw ErrorNotTZX; + if(!file_.check_signature("ZXTape!")) throw ErrorNotTZX; + if(file_.get8() != 0x1a) throw ErrorNotTZX; // Get version number - uint8_t major_version = static_cast(fgetc(file_)); - uint8_t minor_version = static_cast(fgetc(file_)); + uint8_t major_version = file_.get8(); + uint8_t minor_version = file_.get8(); // Reject if an incompatible version if(major_version != 1 || minor_version > 20) throw ErrorNotTZX; @@ -39,7 +36,7 @@ TZX::TZX(const char *file_name) : void TZX::virtual_reset() { clear(); set_is_at_end(false); - fseek(file_, 0x0a, SEEK_SET); + file_.seek(0x0a, SEEK_SET); // This is a workaround for arguably dodgy ZX80/ZX81 TZXs; they launch straight // into data but both machines require a gap before data begins. So impose @@ -50,8 +47,8 @@ void TZX::virtual_reset() { void TZX::get_next_pulses() { while(empty()) { - uint8_t chunk_id = static_cast(fgetc(file_)); - if(feof(file_)) { + uint8_t chunk_id = file_.get8(); + if(file_.eof()) { set_is_at_end(true); return; } @@ -90,24 +87,24 @@ void TZX::get_next_pulses() { } void TZX::get_generalised_data_block() { - uint32_t block_length = fgetc32le(); - long endpoint = ftell(file_) + static_cast(block_length); - uint16_t pause_after_block = fgetc16le(); + uint32_t block_length = file_.get32le(); + long endpoint = file_.tell() + static_cast(block_length); + uint16_t pause_after_block = file_.get16le(); - uint32_t total_pilot_symbols = fgetc32le(); - uint8_t maximum_pulses_per_pilot_symbol = static_cast(fgetc(file_)); - uint8_t symbols_in_pilot_table = static_cast(fgetc(file_)); + uint32_t total_pilot_symbols = file_.get32le(); + uint8_t maximum_pulses_per_pilot_symbol = file_.get8(); + uint8_t symbols_in_pilot_table = file_.get8(); - uint32_t total_data_symbols = fgetc32le(); - uint8_t maximum_pulses_per_data_symbol = static_cast(fgetc(file_)); - uint8_t symbols_in_data_table = static_cast(fgetc(file_)); + uint32_t total_data_symbols = file_.get32le(); + uint8_t maximum_pulses_per_data_symbol = file_.get8(); + uint8_t symbols_in_data_table = file_.get8(); get_generalised_segment(total_pilot_symbols, maximum_pulses_per_pilot_symbol, symbols_in_pilot_table, false); get_generalised_segment(total_data_symbols, maximum_pulses_per_data_symbol, symbols_in_data_table, true); post_gap(pause_after_block); // This should be unnecessary, but intends to preserve sanity. - fseek(file_, endpoint, SEEK_SET); + file_.seek(endpoint, SEEK_SET); } void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data) { @@ -121,15 +118,15 @@ void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_pe std::vector symbol_table; for(int c = 0; c < number_of_symbols; c++) { Symbol symbol; - symbol.flags = static_cast(fgetc(file_)); + symbol.flags = file_.get8(); for(int ic = 0; ic < max_pulses_per_symbol; ic++) { - symbol.pulse_lengths.push_back(fgetc16le()); + symbol.pulse_lengths.push_back(file_.get16le()); } symbol_table.push_back(symbol); } // Hence produce the output. - BitStream stream(file_, false); + FileHolder::BitStream stream = file_.get_bitstream(false); int base = 2; int bits = 1; while(base < number_of_symbols) { @@ -143,8 +140,8 @@ void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_pe symbol_value = stream.get_bits(bits); count = 1; } else { - symbol_value = static_cast(fgetc(file_)); - count = fgetc16le(); + symbol_value = file_.get8(); + count = file_.get16le(); } if(symbol_value > number_of_symbols) { continue; @@ -178,29 +175,28 @@ void TZX::get_standard_speed_data_block() { data_block.data.length_of_one_bit_pulse = 1710; data_block.data.number_of_bits_in_final_byte = 8; - data_block.data.pause_after_block = fgetc16le(); - data_block.data.data_length = fgetc16le(); + data_block.data.pause_after_block = file_.get16le(); + data_block.data.data_length = file_.get16le(); if(!data_block.data.data_length) return; - uint8_t first_byte = static_cast(fgetc(file_)); + uint8_t first_byte = file_.get8(); data_block.length_of_pilot_tone = (first_byte < 128) ? 8063 : 3223; - ungetc(first_byte, file_); + file_.seek(-1, SEEK_CUR); get_data_block(data_block); } void TZX::get_turbo_speed_data_block() { DataBlock data_block; - data_block.length_of_pilot_pulse = fgetc16le(); - data_block.length_of_sync_first_pulse = fgetc16le(); - data_block.length_of_sync_second_pulse = fgetc16le(); - data_block.data.length_of_zero_bit_pulse = fgetc16le(); - data_block.data.length_of_one_bit_pulse = fgetc16le(); - data_block.length_of_pilot_tone = fgetc16le(); - data_block.data.number_of_bits_in_final_byte = static_cast(fgetc(file_)); - data_block.data.pause_after_block = fgetc16le(); - data_block.data.data_length = fgetc16le(); - data_block.data.data_length |= static_cast(fgetc(file_) << 16); + data_block.length_of_pilot_pulse = file_.get16le(); + data_block.length_of_sync_first_pulse = file_.get16le(); + data_block.length_of_sync_second_pulse = file_.get16le(); + data_block.data.length_of_zero_bit_pulse = file_.get16le(); + data_block.data.length_of_one_bit_pulse = file_.get16le(); + data_block.length_of_pilot_tone = file_.get16le(); + data_block.data.number_of_bits_in_final_byte = file_.get8(); + data_block.data.pause_after_block = file_.get16le(); + data_block.data.data_length = file_.get24le(); get_data_block(data_block); } @@ -221,7 +217,7 @@ void TZX::get_data_block(const DataBlock &data_block) { void TZX::get_data(const Data &data) { // Output data. for(unsigned int c = 0; c < data.data_length; c++) { - uint8_t next_byte = static_cast(fgetc(file_)); + uint8_t next_byte = file_.get8(); unsigned int bits = (c != data.data_length-1) ? 8 : data.number_of_bits_in_final_byte; while(bits--) { @@ -238,33 +234,32 @@ void TZX::get_data(const Data &data) { } void TZX::get_pure_tone_data_block() { - uint16_t length_of_pulse = fgetc16le(); - uint16_t nunber_of_pulses = fgetc16le(); + uint16_t length_of_pulse = file_.get16le(); + uint16_t nunber_of_pulses = file_.get16le(); while(nunber_of_pulses--) post_pulse(length_of_pulse); } void TZX::get_pure_data_block() { Data data; - data.length_of_zero_bit_pulse = fgetc16le(); - data.length_of_one_bit_pulse = fgetc16le(); - data.number_of_bits_in_final_byte = static_cast(fgetc(file_)); - data.pause_after_block = fgetc16le(); - data.data_length = fgetc16le(); - data.data_length |= static_cast(fgetc(file_) << 16); + data.length_of_zero_bit_pulse = file_.get16le(); + data.length_of_one_bit_pulse = file_.get16le(); + data.number_of_bits_in_final_byte = file_.get8(); + data.pause_after_block = file_.get16le(); + data.data_length = file_.get24le(); get_data(data); } void TZX::get_pulse_sequence() { - uint8_t number_of_pulses = static_cast(fgetc(file_)); + uint8_t number_of_pulses = file_.get8(); while(number_of_pulses--) { - post_pulse(fgetc16le()); + post_pulse(file_.get16le()); } } void TZX::get_pause() { - uint16_t duration = fgetc16le(); + uint16_t duration = file_.get16le(); if(!duration) { // TODO (maybe): post a 'pause the tape' suggestion } else { @@ -297,20 +292,20 @@ void TZX::post_pulse(const Storage::Time &time) { void TZX::ignore_group_start() { printf("Ignoring TZX group\n"); - uint8_t length = static_cast(fgetc(file_)); - fseek(file_, length, SEEK_CUR); + uint8_t length = file_.get8(); + file_.seek(length, SEEK_CUR); } void TZX::ignore_group_end() { } void TZX::ignore_jump_to_block() { - __unused uint16_t target = fgetc16le(); + __unused uint16_t target = file_.get16le(); printf("Ignoring TZX jump\n"); } void TZX::ignore_loop_start() { - __unused uint16_t number_of_repetitions = fgetc16le(); + __unused uint16_t number_of_repetitions = file_.get16le(); printf("Ignoring TZX loop\n"); } @@ -318,8 +313,8 @@ void TZX::ignore_loop_end() { } void TZX::ignore_call_sequence() { - __unused uint16_t number_of_entries = fgetc16le(); - fseek(file_, number_of_entries * sizeof(uint16_t), SEEK_CUR); + __unused uint16_t number_of_entries = file_.get16le(); + file_.seek(number_of_entries * sizeof(uint16_t), SEEK_CUR); printf("Ignoring TZX call sequence\n"); } @@ -328,29 +323,29 @@ void TZX::ignore_return_from_sequence() { } void TZX::ignore_select_block() { - __unused uint16_t length_of_block = fgetc16le(); - fseek(file_, length_of_block, SEEK_CUR); + __unused uint16_t length_of_block = file_.get16le(); + file_.seek(length_of_block, SEEK_CUR); printf("Ignoring TZX select block\n"); } #pragma mark - Messaging void TZX::ignore_text_description() { - uint8_t length = static_cast(fgetc(file_)); - fseek(file_, length, SEEK_CUR); + uint8_t length = file_.get8(); + file_.seek(length, SEEK_CUR); printf("Ignoring TZX text description\n"); } void TZX::ignore_message_block() { - __unused uint8_t time_for_display = static_cast(fgetc(file_)); - uint8_t length = static_cast(fgetc(file_)); - fseek(file_, length, SEEK_CUR); + __unused uint8_t time_for_display = file_.get8(); + uint8_t length = file_.get8(); + file_.seek(length, SEEK_CUR); printf("Ignoring TZX message\n"); } void TZX::get_hardware_type() { // TODO: pick a way to retain and communicate this. - uint8_t number_of_machines = static_cast(fgetc(file_)); - fseek(file_, number_of_machines * 3, SEEK_CUR); + uint8_t number_of_machines = file_.get8(); + file_.seek(number_of_machines * 3, SEEK_CUR); printf("Ignoring TZX hardware types (%d)\n", number_of_machines); } diff --git a/Storage/Tape/Formats/TZX.hpp b/Storage/Tape/Formats/TZX.hpp index 220c1dbaa..49ac58c21 100644 --- a/Storage/Tape/Formats/TZX.hpp +++ b/Storage/Tape/Formats/TZX.hpp @@ -18,7 +18,7 @@ namespace Tape { /*! Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. */ -class TZX: public PulseQueuedTape, public Storage::FileHolder { +class TZX: public PulseQueuedTape { public: /*! Constructs a @c TZX containing content from the file with name @c file_name. @@ -32,6 +32,8 @@ class TZX: public PulseQueuedTape, public Storage::FileHolder { }; private: + Storage::FileHolder file_; + void virtual_reset(); void get_next_pulses(); diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp index 27be6d234..8b8bc1d2f 100644 --- a/Storage/Tape/Formats/TapePRG.cpp +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -53,22 +53,21 @@ PRG::PRG(const char *file_name) : file_phase_(FilePhaseLeadIn), phase_offset_(0), copy_mask_(0x80), - Storage::FileHolder(file_name) + file_(file_name) { // There's really no way to validate other than that if this file is larger than 64kb, // of if load address + length > 65536 then it's broken. - if(file_stats_.st_size >= 65538 || file_stats_.st_size < 3) + if(file_.stats().st_size >= 65538 || file_.stats().st_size < 3) throw ErrorBadFormat; - load_address_ = fgetc16le(); - length_ = static_cast(file_stats_.st_size - 2); + load_address_ = file_.get16le(); + length_ = static_cast(file_.stats().st_size - 2); if (load_address_ + length_ >= 65536) throw ErrorBadFormat; } -Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() -{ +Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() { // these are all microseconds per pole static const unsigned int leader_zero_length = 179; static const unsigned int zero_length = 169; @@ -81,8 +80,7 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() Tape::Pulse pulse; pulse.length.clock_rate = 1000000; pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low; - switch(output_token_) - { + switch(output_token_) { case Leader: pulse.length.length = leader_zero_length; break; case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break; case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break; @@ -93,29 +91,25 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() return pulse; } -void PRG::virtual_reset() -{ +void PRG::virtual_reset() { bit_phase_ = 3; - fseek(file_, 2, SEEK_SET); + file_.seek(2, SEEK_SET); file_phase_ = FilePhaseLeadIn; phase_offset_ = 0; copy_mask_ = 0x80; } -bool PRG::is_at_end() -{ +bool PRG::is_at_end() { return file_phase_ == FilePhaseAtEnd; } -void PRG::get_next_output_token() -{ +void PRG::get_next_output_token() { static const int block_length = 192; // not counting the checksum static const int countdown_bytes = 9; static const int leadin_length = 20000; static const int block_leadin_length = 5000; - if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd) - { + if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd) { output_token_ = Silence; if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData; return; @@ -123,12 +117,10 @@ void PRG::get_next_output_token() // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 // before doing whatever it should be doing - if(file_phase_ == FilePhaseLeadIn || phase_offset_ < block_leadin_length) - { + if(file_phase_ == FilePhaseLeadIn || phase_offset_ < block_leadin_length) { output_token_ = Leader; phase_offset_++; - if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length) - { + if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length) { phase_offset_ = 0; file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData; } @@ -144,15 +136,13 @@ void PRG::get_next_output_token() if(!bit_offset && ( (file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) || - feof(file_) + file_.eof() ) - ) - { + ) { output_token_ = EndOfBlock; phase_offset_ = 0; - switch(file_phase_) - { + switch(file_phase_) { default: break; case FilePhaseHeader: copy_mask_ ^= 0x80; @@ -160,35 +150,25 @@ void PRG::get_next_output_token() break; case FilePhaseData: copy_mask_ ^= 0x80; - fseek(file_, 2, SEEK_SET); + file_.seek(2, SEEK_SET); if(copy_mask_) file_phase_ = FilePhaseAtEnd; break; } return; } - if(bit_offset == 0) - { + if(bit_offset == 0) { // the first nine bytes are countdown; the high bit is set if this is a header - if(byte_offset < countdown_bytes) - { + if(byte_offset < countdown_bytes) { output_byte_ = static_cast(countdown_bytes - byte_offset) | copy_mask_; - } - else - { - if(file_phase_ == FilePhaseHeader) - { - if(byte_offset == countdown_bytes + block_length) - { + } else { + if(file_phase_ == FilePhaseHeader) { + if(byte_offset == countdown_bytes + block_length) { output_byte_ = check_digit_; - } - else - { + } else { if(byte_offset == countdown_bytes) check_digit_ = 0; - if(file_phase_ == FilePhaseHeader) - { - switch(byte_offset - countdown_bytes) - { + if(file_phase_ == FilePhaseHeader) { + switch(byte_offset - countdown_bytes) { case 0: output_byte_ = 0x03; break; case 1: output_byte_ = load_address_ & 0xff; break; case 2: output_byte_ = (load_address_ >> 8)&0xff; break; @@ -204,12 +184,9 @@ void PRG::get_next_output_token() } } } - } - else - { - output_byte_ = static_cast(fgetc(file_)); - if(feof(file_)) - { + } else { + output_byte_ = file_.get8(); + if(file_.eof()) { output_byte_ = check_digit_; } } @@ -218,16 +195,14 @@ void PRG::get_next_output_token() } } - switch(bit_offset) - { + switch(bit_offset) { case 0: output_token_ = WordMarker; break; default: // i.e. 1–8 output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero; break; - case 9: - { + case 9: { uint8_t parity = output_byte_; parity ^= (parity >> 4); parity ^= (parity >> 2); diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index a8de13790..e8a91f63b 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -19,7 +19,7 @@ namespace Tape { /*! Provides a @c Tape containing a .PRG, which is a direct local file. */ -class PRG: public Tape, public Storage::FileHolder { +class PRG: public Tape { public: /*! Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. @@ -37,6 +37,7 @@ class PRG: public Tape, public Storage::FileHolder { bool is_at_end(); private: + FileHolder file_; Pulse virtual_get_next_pulse(); void virtual_reset(); diff --git a/Storage/Tape/Formats/ZX80O81P.cpp b/Storage/Tape/Formats/ZX80O81P.cpp index a446e1be3..3d701b185 100644 --- a/Storage/Tape/Formats/ZX80O81P.cpp +++ b/Storage/Tape/Formats/ZX80O81P.cpp @@ -11,15 +11,15 @@ using namespace Storage::Tape; -ZX80O81P::ZX80O81P(const char *file_name) : - Storage::FileHolder(file_name) { +ZX80O81P::ZX80O81P(const char *file_name) { + Storage::FileHolder file(file_name); // Grab the actual file contents - data_.resize(static_cast(file_stats_.st_size)); - fread(data_.data(), 1, static_cast(file_stats_.st_size), file_); + data_.resize(static_cast(file.stats().st_size)); + file.read(data_.data(), static_cast(file.stats().st_size)); // If it's a ZX81 file, prepend a file name. - std::string type = extension(); + std::string type = file.extension(); platform_type_ = TargetPlatform::ZX80; if(type == "p" || type == "81") { // TODO, maybe: prefix a proper file name; this is leaving the file nameless. @@ -27,8 +27,8 @@ ZX80O81P::ZX80O81P(const char *file_name) : platform_type_ = TargetPlatform::ZX81; } - std::shared_ptr<::Storage::Data::ZX8081::File> file = Storage::Data::ZX8081::FileFromData(data_); - if(!file) throw ErrorNotZX80O81P; + std::shared_ptr<::Storage::Data::ZX8081::File> zx_file = Storage::Data::ZX8081::FileFromData(data_); + if(!zx_file) throw ErrorNotZX80O81P; // then rewind and start again virtual_reset(); diff --git a/Storage/Tape/Formats/ZX80O81P.hpp b/Storage/Tape/Formats/ZX80O81P.hpp index 2c3ce4829..22a928a76 100644 --- a/Storage/Tape/Formats/ZX80O81P.hpp +++ b/Storage/Tape/Formats/ZX80O81P.hpp @@ -23,7 +23,7 @@ namespace Tape { /*! Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture. */ -class ZX80O81P: public Tape, public Storage::FileHolder, public TargetPlatform::TypeDistinguisher { +class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher { public: /*! Constructs a @c ZX80O containing content from the file with name @c file_name. From 5a3ca0e447bce2867071b8124e0f2b0181e8690e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Nov 2017 21:10:22 -0400 Subject: [PATCH 4/7] Adds output for modified CPC DSKs. --- Storage/Disk/DiskImage/Formats/CPCDSK.cpp | 109 ++++++++++++++++++++++ Storage/Disk/DiskImage/Formats/CPCDSK.hpp | 1 + Storage/FileHolder.cpp | 18 ++++ Storage/FileHolder.hpp | 12 ++- 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp index c74f4daef..9c3e0d1ce 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp @@ -18,6 +18,7 @@ using namespace Storage::Disk; CPCDSK::CPCDSK(const char *file_name) : + file_name_(file_name), is_extended_(false) { FileHolder file(file_name); is_read_only_ = file.get_is_known_read_only(); @@ -230,6 +231,7 @@ void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::sha size_t chronological_track = index_for_track(pair.first); if(chronological_track >= tracks_.size()) { tracks_.resize(chronological_track+1); + head_position_count_ = pair.first.position; } // Get the track, or create it if necessary. @@ -263,6 +265,113 @@ void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::sha } // Rewrite the entire disk image, in extended form. + Storage::FileHolder output(file_name_, Storage::FileHolder::FileMode::Rewrite); + output.write(reinterpret_cast("EXTENDED CPC DSK File\r\nDisk-Info\r\n"), 34); + output.write(reinterpret_cast("Clock Signal "), 14); + output.put8(static_cast(head_position_count_)); + output.put8(static_cast(head_count_)); + output.putn(2, 0); + + // Output size table. + for(size_t index = 0; index < static_cast(head_position_count_ * head_count_); ++index) { + if(index >= tracks_.size()) { + output.put8(0); + continue; + } + Track *track = tracks_[index].get(); + if(!track) { + output.put8(0); + continue; + } + + // Calculate size of track. + size_t track_size = 256; + for(auto §or: track->sectors) { + for(auto &sample: sector.samples) { + track_size += sample.size(); + } + } + + // Round upward and output. + track_size += (256 - (track_size & 255)) & 255; + output.put8(static_cast(track_size >> 8)); + } + + // Advance to offset 256. + output.putn(static_cast(256 - output.tell()), 0); + + // Output each track. + for(size_t index = 0; index < static_cast(head_position_count_ * head_count_); ++index) { + if(index >= tracks_.size()) continue; + Track *track = tracks_[index].get(); + if(!track) continue; + + // Output track header. + output.write(reinterpret_cast("Track-Info\r\n"), 13); + output.putn(3, 0); + output.put8(track->track); + output.put8(track->side); + switch (track->data_rate) { + default: + output.put8(0); + break; + case Track::DataRate::SingleOrDoubleDensity: + output.put8(1); + break; + case Track::DataRate::HighDensity: + output.put8(2); + break; + case Track::DataRate::ExtendedDensity: + output.put8(3); + break; + } + switch (track->data_encoding) { + default: + output.put8(0); + break; + case Track::DataEncoding::FM: + output.put8(1); + break; + case Track::DataEncoding::MFM: + output.put8(2); + break; + } + output.put8(track->sector_length); + output.put8(static_cast(track->sectors.size())); + output.put8(track->gap3_length); + output.put8(track->filler_byte); + + // Output sector information list. + for(auto §or: track->sectors) { + output.put8(sector.address.track); + output.put8(sector.address.side); + output.put8(sector.address.sector); + output.put8(sector.size); + output.put8(sector.fdc_status1); + output.put8(sector.fdc_status2); + + size_t data_size = 0; + for(auto &sample: sector.samples) { + data_size += sample.size(); + } + output.put16le(static_cast(data_size)); + } + + // Move to next 256-byte boundary. + long distance = (256 - output.tell()&255)&255; + output.putn(static_cast(distance), 0); + + // Output sector contents. + for(auto §or: track->sectors) { + for(auto &sample: sector.samples) { + output.write(sample); + } + } + + // Move to next 256-byte boundary. + distance = (256 - output.tell()&255)&255; + output.putn(static_cast(distance), 0); + } } bool CPCDSK::get_is_read_only() { diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index 394d8dfba..58edf60e9 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -64,6 +64,7 @@ class CPCDSK: public DiskImage { std::vector sectors; }; + std::string file_name_; std::vector> tracks_; size_t index_for_track(::Storage::Disk::Track::Address address); diff --git a/Storage/FileHolder.cpp b/Storage/FileHolder.cpp index d9c6e88c1..1745fe53f 100644 --- a/Storage/FileHolder.cpp +++ b/Storage/FileHolder.cpp @@ -92,6 +92,24 @@ uint8_t FileHolder::get8() { return static_cast(fgetc(file_)); } +void FileHolder::put16be(uint16_t value) { + fputc(value >> 8, file_); + fputc(value, file_); +} + +void FileHolder::put16le(uint16_t value) { + fputc(value, file_); + fputc(value >> 8, file_); +} + +void FileHolder::put8(uint8_t value) { + fputc(value, file_); +} + +void FileHolder::putn(size_t repeats, uint8_t value) { + while(repeats--) put8(value); +} + std::vector FileHolder::read(size_t size) { std::vector result(size); fread(result.data(), 1, size, file_); diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index add50aac0..7ac73a3a7 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -62,21 +62,27 @@ class FileHolder final { and returning the two assembled in little endian order. */ uint16_t get16le(); + void put16le(uint16_t value); /*! Performs @c get8 two times on @c file, casting each result to a @c uint32_t and returning the two assembled in big endian order. */ uint16_t get16be(); + void put16be(uint16_t value); - /*! Reads a single byte from @c file */ + /*! Reads a single byte from @c file. */ uint8_t get8(); - + + /*! Writes a single byte from @c file. */ + void put8(uint8_t value); + void putn(size_t repeats, uint8_t value); + std::vector read(size_t size); size_t read(uint8_t *buffer, size_t size); size_t write(const std::vector &); size_t write(const uint8_t *buffer, size_t size); - + void seek(long offset, int whence); long tell(); void flush(); From 5070a8414f5cdff57cdeab35db508a031efaa523 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Nov 2017 21:29:15 -0400 Subject: [PATCH 5/7] Improves FileHolder documentation --- Storage/FileHolder.hpp | 44 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index 7ac73a3a7..57878a81d 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -31,6 +31,19 @@ class FileHolder final { }; ~FileHolder(); + + /*! + Attempts to open the file indicated by @c file_name. @c ideal_mode nominates how the file would + most ideally be opened. It can be one of: + + ReadWrite attempt to open this file for random access reading and writing. If that fails, + will attept to open in Read mode. + Read attempts to open this file for reading only. + Rewrite opens the file for rewriting — none of the original content is preserved; whatever + the caller outputs will replace the existing file. + + @raises ErrorCantOpen if the file cannot be opened. + */ FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite); /*! @@ -62,6 +75,10 @@ class FileHolder final { and returning the two assembled in little endian order. */ uint16_t get16le(); + + /*! + Writes @c value using two successive @c put8s, in little endian order. + */ void put16le(uint16_t value); /*! @@ -69,6 +86,10 @@ class FileHolder final { and returning the two assembled in big endian order. */ uint16_t get16be(); + + /*! + Writes @c value using two successive @c put8s, in big endian order. + */ void put16be(uint16_t value); /*! Reads a single byte from @c file. */ @@ -76,16 +97,32 @@ class FileHolder final { /*! Writes a single byte from @c file. */ void put8(uint8_t value); + + /*! Writes @c value a total of @c repeats times. */ void putn(size_t repeats, uint8_t value); + /*! Reads @c size bytes and returns them as a vector. */ std::vector read(size_t size); + + /*! Reads @c size bytes and writes them to @c buffer. */ size_t read(uint8_t *buffer, size_t size); - size_t write(const std::vector &); + + /*! Writes @c buffer one byte at a time in order. */ + size_t write(const std::vector &buffer); + + /*! Writes @c buffer one byte at a time in order, writing @c size bytes in total. */ size_t write(const uint8_t *buffer, size_t size); + /*! Moves @c bytes from the anchor indicated by @c whence — SEEK_SET, SEEK_CUR or SEEK_END. */ void seek(long offset, int whence); + + /*! @returns The current cursor position within this file. */ long tell(); + + /*! Flushes any queued content that has not yet been written to disk. */ void flush(); + + /*! @returns @c true if the end-of-file indicator is set, @c false otherwise. */ bool eof(); class BitStream { @@ -159,7 +196,7 @@ class FileHolder final { void ensure_is_at_least_length(long length); /*! - @returns @c true if this file is read-only; @c false otherwise. + @returns @c true if an attempt was made to read this file in ReadWrite mode but it could be opened only for reading; @c false otherwise. */ bool get_is_known_read_only(); @@ -168,6 +205,9 @@ class FileHolder final { */ struct stat &stats(); + /*! + @returns a mutex owned by the file that can be used to serialise file access. + */ std::mutex &get_file_access_mutex(); private: From 23d5849cda7db69ea8a68a1b85ab174595abe234 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Nov 2017 21:29:42 -0400 Subject: [PATCH 6/7] Attempts to map recognised [M]FM errors back to FDC status codes. --- Storage/Disk/DiskImage/Formats/CPCDSK.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp index 9c3e0d1ce..bcf0794e7 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.cpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.cpp @@ -261,6 +261,13 @@ void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::sha sector.has_header_crc_error = source_sector.second.has_header_crc_error; sector.is_deleted = source_sector.second.is_deleted; sector.samples = std::move(source_sector.second.samples); + + sector.fdc_status1 = 0; + sector.fdc_status2 = 0; + + if(source_sector.second.has_data_crc_error) sector.fdc_status2 |= 0x20; + if(source_sector.second.has_header_crc_error) sector.fdc_status1 |= 0x20; + if(source_sector.second.is_deleted) sector.fdc_status2 |= 0x40; } } From 794437f20f6f23ebe405e995ea77656868a4b03c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 3 Nov 2017 21:43:31 -0400 Subject: [PATCH 7/7] Corrects fixed buffer size error in `FileHolder::check_signature`. --- Storage/FileHolder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Storage/FileHolder.cpp b/Storage/FileHolder.cpp index 1745fe53f..2ae9702be 100644 --- a/Storage/FileHolder.cpp +++ b/Storage/FileHolder.cpp @@ -152,9 +152,9 @@ bool FileHolder::check_signature(const char *signature, size_t length) { if(!length) length = strlen(signature); // read and check the file signature - char stored_signature[12]; - if(fread(stored_signature, 1, length, file_) != length) return false; - if(memcmp(stored_signature, signature, length)) return false; + std::vector stored_signature = read(length); + if(stored_signature.size() != length) return false; + if(memcmp(stored_signature.data(), signature, length)) return false; return true; }