1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-13 22:32:03 +00:00

Merge pull request #263 from TomHarte/WriteableDSK

Makes CPC-style .DSK files writeable
This commit is contained in:
Thomas Harte 2017-11-03 21:59:06 -04:00 committed by GitHub
commit fd10c42433
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 995 additions and 558 deletions

View File

@ -23,17 +23,18 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1); Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr; 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&7) return nullptr;
if(final_file_offset < 8) return nullptr; if(final_file_offset < 8) return nullptr;
char disk_name[13]; 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; 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 0: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
@ -45,14 +46,14 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) { for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
File new_file; File new_file;
char name[10]; 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.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.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->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10)); 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->data[file_offset + 7] & 0x80); new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
long data_length = static_cast<long>(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12)); long data_length = static_cast<long>(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->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8); int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
new_file.data.reserve(static_cast<size_t>(data_length)); new_file.data.reserve(static_cast<size_t>(data_length));
if(start_sector < 2) continue; if(start_sector < 2) continue;
@ -65,7 +66,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
if(!next_sector) break; if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l); 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; data_length -= length_from_sector;
} }
if(!data_length) catalogue->files.push_front(new_file); if(!data_length) catalogue->files.push_front(new_file);
@ -85,7 +86,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
for(uint8_t c = 2; c < 7; c++) { for(uint8_t c = 2; c < 7; c++) {
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c); Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr; 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. // Quick sanity checks.
@ -93,7 +94,7 @@ std::unique_ptr<Catalogue> 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[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; 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; default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break; case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break; case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;

View File

@ -153,12 +153,12 @@ static void InspectCatalogue(
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) { static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
Storage::Encodings::MFM::Parser parser(true, disk); Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); 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 // 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. // this disk was formatted and the filler byte never replaced.
bool matched = true; bool matched = true;
for(size_t c = 1; c < 64; c++) { 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; matched = false;
break; break;
} }

View File

@ -20,17 +20,17 @@ using namespace Storage::Disk;
AcornADF::AcornADF(const char *file_name) : MFMSectorDump(file_name) { AcornADF::AcornADF(const char *file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes // very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large // and not ungainly large
if(file_stats_.st_size % static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF; if(file_.stats().st_size % static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF;
if(file_stats_.st_size < 7 * static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF; if(file_.stats().st_size < 7 * static_cast<off_t>(128 << sector_size)) throw ErrorNotAcornADF;
// check that the initial directory's 'Hugo's are present // check that the initial directory's 'Hugo's are present
fseek(file_, 513, SEEK_SET); file_.seek(513, SEEK_SET);
uint8_t bytes[4]; 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; if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
fseek(file_, 0x6fb, SEEK_SET); file_.seek(0x6fb, SEEK_SET);
fread(bytes, 1, 4, file_); file_.read(bytes, 4);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF; if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
set_geometry(sectors_per_track, sector_size, true); set_geometry(sectors_per_track, sector_size, true);

View File

@ -8,32 +8,181 @@
#include "CPCDSK.hpp" #include "CPCDSK.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Encoder.hpp" #include "../../Encodings/MFM/Encoder.hpp"
#include "../../Encodings/MFM/SegmentParser.hpp"
#include "../../Track/TrackSerialiser.hpp"
#include <iostream>
using namespace Storage::Disk; using namespace Storage::Disk;
CPCDSK::CPCDSK(const char *file_name) : CPCDSK::CPCDSK(const char *file_name) :
Storage::FileHolder(file_name), is_extended_(false) { file_name_(file_name),
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; is_extended_ = true;
fseek(file_, 0, SEEK_SET); file.seek(0, SEEK_SET);
if(!check_signature("EXTENDED", 8)) if(!file.check_signature("EXTENDED"))
throw ErrorNotCPCDSK; throw ErrorNotCPCDSK;
} }
// Don't really care about about the creator; skip. // Don't really care about about the creator; skip.
fseek(file_, 0x30, SEEK_SET); file.seek(0x30, SEEK_SET);
head_position_count_ = fgetc(file_); head_position_count_ = file.get8();
head_count_ = fgetc(file_); head_count_ = file.get8();
// Used only for non-extended disks.
long size_of_a_track = 0;
// Used only for extended disks.
std::vector<size_t> track_sizes;
if(is_extended_) { if(is_extended_) {
// Skip two unused bytes and grab the track size table. // 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++) { for(int c = 0; c < head_position_count_ * head_count_; c++) {
track_sizes_.push_back(static_cast<size_t>(fgetc(file_) << 8)); track_sizes.push_back(static_cast<size_t>(file.get8()) << 8);
} }
} else { } else {
size_of_a_track_ = fgetc16le(); size_of_a_track = file.get16le();
}
long file_offset = 0x100;
for(size_t c = 0; c < static_cast<size_t>(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.
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 = 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(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(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;
}
} else {
track->data_rate = Track::DataRate::Unknown;
track->data_encoding = Track::DataEncoding::Unknown;
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 = 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--) {
track->sectors.emplace_back();
Track::Sector &sector = 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.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.
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<size_t>(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 = file.get16le();
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;
file.seek(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.samples.emplace_back();
sector.samples.resize(number_of_samplings);
while(number_of_samplings--) {
sector.samples[number_of_samplings].resize(stored_data_size);
}
}
// Sector contents are at offset 0x100 into the track.
file.seek(file_offset + 0x100, SEEK_SET);
for(auto &sector: track->sectors) {
for(auto &data : sector.samples) {
file.read(data.data(), data.size());
}
}
} 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<long>(track_sizes[c]);
else
file_offset += size_of_a_track;
} }
} }
@ -45,113 +194,193 @@ int CPCDSK::get_head_count() {
return head_count_; return head_count_;
} }
std::shared_ptr<Track> CPCDSK::get_track_at_position(Track::Address address) { size_t CPCDSK::index_for_track(::Storage::Disk::Track::Address address) {
return static_cast<size_t>((address.position * head_count_) + address.head);
}
std::shared_ptr<Track> CPCDSK::get_track_at_position(::Storage::Disk::Track::Address address) {
// Given that thesea are interleaved images, determine which track, chronologically, is being requested. // Given that thesea are interleaved images, determine which track, chronologically, is being requested.
size_t chronological_track = static_cast<size_t>((address.position * head_count_) + address.head); size_t chronological_track = index_for_track(address);
// All DSK images reserve 0x100 bytes for their headers. // Return a nullptr if out of range or not provided.
long file_offset = 0x100; if(chronological_track >= tracks_.size()) return nullptr;
if(is_extended_) {
// Tracks are a variable size in the original DSK file format. Track *track = tracks_[chronological_track].get();
if(!track) return nullptr;
// Check that there is anything stored for this track. std::vector<const Storage::Encodings::MFM::Sector *> sectors;
if(!track_sizes_[chronological_track]) { for(auto &sector : track->sectors) {
return nullptr; sectors.push_back(&sector);
}
// 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<long>(chronological_track);
} }
// Find the track, and skip the unused part of track information. // TODO: FM encoding, data rate?
fseek(file_, file_offset + 16, SEEK_SET); return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte);
}
// Grab the track information. void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) {
fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector // Patch changed tracks into the disk image.
int number_of_sectors = fgetc(file_); for(auto &pair: tracks) {
uint8_t gap3_length = static_cast<uint8_t>(fgetc(file_)); // Assume MFM for now; with extensions DSK can contain FM tracks.
uint8_t filler_byte = static_cast<uint8_t>(fgetc(file_)); const bool is_double_density = true;
std::map<size_t, Storage::Encodings::MFM::Sector> 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);
// Grab the sector information // Find slot for track, making it if neccessary.
struct SectorInfo { size_t chronological_track = index_for_track(pair.first);
uint8_t track; if(chronological_track >= tracks_.size()) {
uint8_t side; tracks_.resize(chronological_track+1);
uint8_t sector; head_position_count_ = pair.first.position;
uint8_t length;
uint8_t status1;
uint8_t status2;
size_t actual_length;
};
std::vector<SectorInfo> sector_infos;
while(number_of_sectors--) {
SectorInfo sector_info;
sector_info.track = static_cast<uint8_t>(fgetc(file_));
sector_info.side = static_cast<uint8_t>(fgetc(file_));
sector_info.sector = static_cast<uint8_t>(fgetc(file_));
sector_info.length = static_cast<uint8_t>(fgetc(file_));
sector_info.status1 = static_cast<uint8_t>(fgetc(file_));
sector_info.status2 = static_cast<uint8_t>(fgetc(file_));
sector_info.actual_length = fgetc16le();
sector_infos.push_back(sector_info);
}
// Get the sectors.
fseek(file_, file_offset + 0x100, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(auto &sector_info : sector_infos) {
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;
size_t data_size;
if(is_extended_) {
data_size = sector_info.actual_length;
} else {
data_size = static_cast<size_t>(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_);
if(sector_info.status2 & 0x20) { // Get the track, or create it if necessary.
// The CRC failed in the data field. Track *track = tracks_[chronological_track].get();
new_sector.has_data_crc_error = true; if(!track) {
} else { track = new Track;
if(sector_info.status1 & 0x20) { track->track = static_cast<uint8_t>(pair.first.position);
// The CRC failed in the ID field. track->side = static_cast<uint8_t>(pair.first.head);
new_sector.has_header_crc_error = true; 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>(track);
}
// Store sectors.
track->sectors.clear();
for(auto &source_sector: sectors) {
track->sectors.emplace_back();
Track::Sector &sector = 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);
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;
}
}
// Rewrite the entire disk image, in extended form.
Storage::FileHolder output(file_name_, Storage::FileHolder::FileMode::Rewrite);
output.write(reinterpret_cast<const uint8_t *>("EXTENDED CPC DSK File\r\nDisk-Info\r\n"), 34);
output.write(reinterpret_cast<const uint8_t *>("Clock Signal "), 14);
output.put8(static_cast<uint8_t>(head_position_count_));
output.put8(static_cast<uint8_t>(head_count_));
output.putn(2, 0);
// Output size table.
for(size_t index = 0; index < static_cast<size_t>(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 &sector: track->sectors) {
for(auto &sample: sector.samples) {
track_size += sample.size();
} }
} }
if(sector_info.status2 & 0x40) { // Round upward and output.
// This sector is marked as deleted. track_size += (256 - (track_size & 255)) & 255;
new_sector.is_deleted = true; output.put8(static_cast<uint8_t>(track_size >> 8));
}
if(sector_info.status2 & 0x01) {
// Data field wasn't found.
new_sector.data.clear();
}
sectors.push_back(std::move(new_sector));
} }
// Advance to offset 256.
output.putn(static_cast<size_t>(256 - output.tell()), 0);
// TODO: extensions to the extended format; John Elliot's addition of single-density support, // Output each track.
// and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential for(size_t index = 0; index < static_cast<size_t>(head_position_count_ * head_count_); ++index) {
// FM/MFM encoding that can produce specified weak values. if(index >= tracks_.size()) continue;
Track *track = tracks_[index].get();
if(!track) continue;
if(sectors.size()) return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, gap3_length, filler_byte); // Output track header.
output.write(reinterpret_cast<const uint8_t *>("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<uint8_t>(track->sectors.size()));
output.put8(track->gap3_length);
output.put8(track->filler_byte);
return nullptr; // Output sector information list.
for(auto &sector: 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<uint16_t>(data_size));
}
// Move to next 256-byte boundary.
long distance = (256 - output.tell()&255)&255;
output.putn(static_cast<size_t>(distance), 0);
// Output sector contents.
for(auto &sector: 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<size_t>(distance), 0);
}
}
bool CPCDSK::get_is_read_only() {
return is_read_only_;
} }

View File

@ -11,6 +11,7 @@
#include "../DiskImage.hpp" #include "../DiskImage.hpp"
#include "../../../FileHolder.hpp" #include "../../../FileHolder.hpp"
#include "../../Encodings/MFM/Sector.hpp"
#include <vector> #include <vector>
@ -20,7 +21,7 @@ namespace Disk {
/*! /*!
Provies a @c Disk containing an Amstrad CPC-stype disk image some arrangement of sectors with status bits. 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: public:
/*! /*!
Construct an @c AcornADF containing content from the file with name @c file_name. Construct an @c AcornADF containing content from the file with name @c file_name.
@ -37,19 +38,40 @@ class CPCDSK: public DiskImage, public Storage::FileHolder {
// implemented to satisfy @c Disk // implemented to satisfy @c Disk
int get_head_position_count() override; int get_head_position_count() override;
int get_head_count() override; int get_head_count() override;
using DiskImage::get_is_read_only; bool get_is_read_only() override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
private: 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: public ::Storage::Encodings::MFM::Sector {
uint8_t fdc_status1;
uint8_t fdc_status2;
};
std::vector<Sector> sectors;
};
std::string file_name_;
std::vector<std::unique_ptr<Track>> tracks_;
size_t index_for_track(::Storage::Disk::Track::Address address);
int head_count_; int head_count_;
int head_position_count_; int head_position_count_;
bool is_extended_; bool is_extended_;
bool is_read_only_;
// Used only for non-extended disks.
long size_of_a_track_;
// Used only for extended disks.
std::vector<size_t> track_sizes_;
}; };
} }

View File

@ -17,13 +17,13 @@
using namespace Storage::Disk; using namespace Storage::Disk;
D64::D64(const char *file_name) : 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 // 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. // 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; 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, // 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 // being the most stable thing available
@ -59,7 +59,7 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
} }
// seek to start of data // 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 // build up a PCM sampling of the GCR version of this track
@ -117,7 +117,7 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
// get the actual contents // get the actual contents
uint8_t source_data[256]; uint8_t source_data[256];
fread(source_data, 1, 256, file_); file_.read(source_data, sizeof(source_data));
// compute the latest checksum // compute the latest checksum
checksum = 0; checksum = 0;

View File

@ -18,7 +18,7 @@ namespace Disk {
/*! /*!
Provies a @c Disk containing a D64 disk image a decoded sector dump of a C1540-format 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: public:
/*! /*!
Construct a @c D64 containing content from the file with name @c file_name. 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<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private: private:
Storage::FileHolder file_;
int number_of_tracks_; int number_of_tracks_;
uint16_t disk_id_; uint16_t disk_id_;
}; };

View File

@ -15,17 +15,17 @@
using namespace Storage::Disk; using namespace Storage::Disk;
G64::G64(const char *file_name) : G64::G64(const char *file_name) :
Storage::FileHolder(file_name) { file_(file_name) {
// read and check the file signature // 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 // check the version number
int version = fgetc(file_); int version = file_.get8();
if(version != 0) throw ErrorUnknownVersion; if(version != 0) throw ErrorUnknownVersion;
// get the number of tracks and track size // get the number of tracks and track size
number_of_tracks_ = static_cast<uint8_t>(fgetc(file_)); number_of_tracks_ = file_.get8();
maximum_track_size_ = fgetc16le(); maximum_track_size_ = file_.get16le();
} }
int G64::get_head_position_count() { int G64::get_head_position_count() {
@ -43,43 +43,43 @@ std::shared_ptr<Track> G64::get_track_at_position(Track::Address address) {
if(address.head >= 1) return resulting_track; if(address.head >= 1) return resulting_track;
// seek to this track's entry in the track table // seek to this track's entry in the track table
fseek(file_, static_cast<long>((address.position * 4) + 0xc), SEEK_SET); file_.seek(static_cast<long>((address.position * 4) + 0xc), SEEK_SET);
// read the track offset // read the track offset
uint32_t 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 the track offset is zero, this track doesn't exist, so...
if(!track_offset) return resulting_track; if(!track_offset) return resulting_track;
// seek to the track start // seek to the track start
fseek(file_, static_cast<long>(track_offset), SEEK_SET); file_.seek(static_cast<long>(track_offset), SEEK_SET);
// get the real track length // get the real track length
uint16_t track_length; uint16_t track_length;
track_length = fgetc16le(); track_length = file_.get16le();
// grab the byte contents of this track // grab the byte contents of this track
std::vector<uint8_t> track_contents(track_length); std::vector<uint8_t> 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 // seek to this track's entry in the speed zone table
fseek(file_, static_cast<long>((address.position * 4) + 0x15c), SEEK_SET); file_.seek(static_cast<long>((address.position * 4) + 0x15c), SEEK_SET);
// read the speed zone offsrt // read the speed zone offsrt
uint32_t speed_zone_offset; 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 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) { if(speed_zone_offset > 3) {
// seek to start of speed zone // seek to start of speed zone
fseek(file_, static_cast<long>(speed_zone_offset), SEEK_SET); file_.seek(static_cast<long>(speed_zone_offset), SEEK_SET);
uint16_t speed_zone_length = (track_length + 3) >> 2; uint16_t speed_zone_length = (track_length + 3) >> 2;
// read the speed zone bytes // read the speed zone bytes
uint8_t speed_zone_contents[speed_zone_length]; 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 // divide track into appropriately timed PCMSegments
std::vector<PCMSegment> segments; std::vector<PCMSegment> segments;

View File

@ -18,7 +18,7 @@ namespace Disk {
/*! /*!
Provies a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream. 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: public:
/*! /*!
Construct a @c G64 containing content from the file with name @c file_name. 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; using DiskImage::get_is_read_only;
private: private:
Storage::FileHolder file_;
uint8_t number_of_tracks_; uint8_t number_of_tracks_;
uint16_t maximum_track_size_; uint16_t maximum_track_size_;
}; };

View File

@ -15,15 +15,15 @@
using namespace Storage::Disk; using namespace Storage::Disk;
HFE::HFE(const char *file_name) : HFE::HFE(const char *file_name) :
Storage::FileHolder(file_name) { file_(file_name) {
if(!check_signature("HXCPICFE", 8)) throw ErrorNotHFE; if(!file_.check_signature("HXCPICFE")) throw ErrorNotHFE;
if(fgetc(file_)) throw ErrorNotHFE; if(file_.get8()) throw ErrorNotHFE;
track_count_ = fgetc(file_); track_count_ = file_.get8();
head_count_ = fgetc(file_); head_count_ = file_.get8();
fseek(file_, 7, SEEK_CUR); file_.seek(7, SEEK_CUR);
track_list_offset_ = static_cast<long>(fgetc16le() << 9); track_list_offset_ = static_cast<long>(file_.get16le()) << 9;
} }
HFE::~HFE() { HFE::~HFE() {
@ -47,13 +47,13 @@ int HFE::get_head_count() {
uint16_t HFE::seek_track(Track::Address address) { uint16_t HFE::seek_track(Track::Address address) {
// Get track position and length from the lookup table; data is then always interleaved // Get track position and length from the lookup table; data is then always interleaved
// based on an assumption of two heads. // 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<long>(fgetc16le() << 9); long track_offset = static_cast<long>(file_.get16le()) << 9;
uint16_t track_length = fgetc16le(); uint16_t track_length = file_.get16le();
fseek(file_, track_offset, SEEK_SET); file_.seek(track_offset, SEEK_SET);
if(address.head) fseek(file_, 256, SEEK_CUR); if(address.head) file_.seek(256, SEEK_CUR);
return track_length / 2; return track_length / 2;
} }
@ -61,7 +61,7 @@ uint16_t HFE::seek_track(Track::Address address) {
std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) { std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
PCMSegment segment; PCMSegment segment;
{ {
std::lock_guard<std::mutex> lock_guard(file_access_mutex_); std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
uint16_t track_length = seek_track(address); uint16_t track_length = seek_track(address);
segment.data.resize(track_length); segment.data.resize(track_length);
@ -70,9 +70,9 @@ std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
uint16_t c = 0; uint16_t c = 0;
while(c < track_length) { while(c < track_length) {
uint16_t length = static_cast<uint16_t>(std::min(256, track_length - c)); uint16_t length = static_cast<uint16_t>(std::min(256, track_length - c));
fread(&segment.data[c], 1, length, file_); file_.read(&segment.data[c], length);
c += length; c += length;
fseek(file_, 256, SEEK_CUR); file_.seek(256, SEEK_CUR);
} }
} }
@ -86,7 +86,7 @@ std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) { void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
for(auto &track : tracks) { for(auto &track : tracks) {
std::unique_lock<std::mutex> lock_guard(file_access_mutex_); std::unique_lock<std::mutex> lock_guard(file_.get_file_access_mutex());
uint16_t track_length = seek_track(track.first); uint16_t track_length = seek_track(track.first);
lock_guard.unlock(); lock_guard.unlock();
@ -100,10 +100,14 @@ void HFE::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
uint16_t c = 0; uint16_t c = 0;
while(c < data_length) { while(c < data_length) {
uint16_t length = static_cast<uint16_t>(std::min(256, data_length - c)); uint16_t length = static_cast<uint16_t>(std::min(256, data_length - c));
fwrite(&segment.data[c], 1, length, file_); file_.write(&segment.data[c], length);
c += length; c += length;
fseek(file_, 256, SEEK_CUR); file_.seek(256, SEEK_CUR);
} }
lock_guard.unlock(); lock_guard.unlock();
} }
} }
bool HFE::get_is_read_only() {
return file_.get_is_known_read_only();
}

View File

@ -18,7 +18,7 @@ namespace Disk {
/*! /*!
Provies a @c Disk containing an HFE disk image a bit stream representation of a floppy. 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: public:
/*! /*!
Construct an @c SSD containing content from the file with name @c file_name. 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 // implemented to satisfy @c Disk
int get_head_position_count() override; int get_head_position_count() override;
int get_head_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<Track::Address, std::shared_ptr<Track>> &tracks) override; void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private: private:
Storage::FileHolder file_;
uint16_t seek_track(Track::Address address); uint16_t seek_track(Track::Address address);
int head_count_; int head_count_;

View File

@ -12,7 +12,7 @@
using namespace Storage::Disk; 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) { void MFMSectorDump::set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density) {
sectors_per_track_ = sectors_per_track; sectors_per_track_ = sectors_per_track;
@ -27,9 +27,9 @@ std::shared_ptr<Track> MFMSectorDump::get_track_at_position(Track::Address addre
long file_offset = get_file_offset_for_position(address); long file_offset = get_file_offset_for_position(address);
{ {
std::lock_guard<std::mutex> lock_guard(file_access_mutex_); std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
fseek(file_, file_offset, SEEK_SET); file_.seek(file_offset, SEEK_SET);
fread(sectors, 1, sizeof(sectors), file_); file_.read(sectors, sizeof(sectors));
} }
return track_for_sectors(sectors, static_cast<uint8_t>(address.position), static_cast<uint8_t>(address.head), 0, sector_size_, is_double_density_); return track_for_sectors(sectors, static_cast<uint8_t>(address.position), static_cast<uint8_t>(address.head), 0, sector_size_, is_double_density_);
@ -46,10 +46,14 @@ void MFMSectorDump::set_tracks(const std::map<Track::Address, std::shared_ptr<Tr
decode_sectors(*track.second, parsed_track, 0, static_cast<uint8_t>(sectors_per_track_-1), sector_size_, is_double_density_); decode_sectors(*track.second, parsed_track, 0, static_cast<uint8_t>(sectors_per_track_-1), sector_size_, is_double_density_);
long file_offset = get_file_offset_for_position(track.first); long file_offset = get_file_offset_for_position(track.first);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_); std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
ensure_file_is_at_least_length(file_offset); file_.ensure_is_at_least_length(file_offset);
fseek(file_, file_offset, SEEK_SET); file_.seek(file_offset, SEEK_SET);
fwrite(parsed_track, 1, sizeof(parsed_track), file_); file_.write(parsed_track, sizeof(parsed_track));
} }
fflush(file_); file_.flush();
}
bool MFMSectorDump::get_is_read_only() {
return file_.get_is_known_read_only();
} }

View File

@ -18,17 +18,19 @@ namespace Disk {
/*! /*!
Provies the base for writeable [M]FM disk images that just contain contiguous sector content dumps. 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: public:
MFMSectorDump(const char *file_name); MFMSectorDump(const char *file_name);
void set_geometry(int sectors_per_track, uint8_t sector_size, bool is_double_density); 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<Track::Address, std::shared_ptr<Track>> &tracks) override; void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
protected:
Storage::FileHolder file_;
private: private:
std::mutex file_access_mutex_;
virtual long get_file_offset_for_position(Track::Address address) = 0; virtual long get_file_offset_for_position(Track::Address address) = 0;
int sectors_per_track_ = 0; int sectors_per_track_ = 0;

View File

@ -17,13 +17,13 @@
using namespace Storage::Disk; using namespace Storage::Disk;
OricMFMDSK::OricMFMDSK(const char *file_name) : OricMFMDSK::OricMFMDSK(const char *file_name) :
Storage::FileHolder(file_name) { file_(file_name) {
if(!check_signature("MFM_DISK", 8)) if(!file_.check_signature("MFM_DISK"))
throw ErrorNotOricMFMDSK; throw ErrorNotOricMFMDSK;
head_count_ = fgetc32le(); head_count_ = file_.get32le();
track_count_ = fgetc32le(); track_count_ = file_.get32le();
geometry_type_ = fgetc32le(); geometry_type_ = file_.get32le();
if(geometry_type_ < 1 || geometry_type_ > 2) if(geometry_type_ < 1 || geometry_type_ > 2)
throw ErrorNotOricMFMDSK; throw ErrorNotOricMFMDSK;
@ -53,8 +53,8 @@ long OricMFMDSK::get_file_offset_for_position(Track::Address address) {
std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address) { std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address) {
PCMSegment segment; PCMSegment segment;
{ {
std::lock_guard<std::mutex> lock_guard(file_access_mutex_); std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
fseek(file_, get_file_offset_for_position(address), SEEK_SET); file_.seek(get_file_offset_for_position(address), SEEK_SET);
// The file format omits clock bits. So it's not a genuine MFM capture. // 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. // A consumer must contextually guess when an FB, FC, etc is meant to be a control mark.
@ -63,7 +63,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
std::unique_ptr<Encodings::MFM::Encoder> encoder = Encodings::MFM::GetMFMEncoder(segment.data); std::unique_ptr<Encodings::MFM::Encoder> encoder = Encodings::MFM::GetMFMEncoder(segment.data);
bool did_sync = false; bool did_sync = false;
while(track_offset < 6250) { while(track_offset < 6250) {
uint8_t next_byte = static_cast<uint8_t>(fgetc(file_)); uint8_t next_byte = file_.get8();
track_offset++; track_offset++;
switch(next_byte) { switch(next_byte) {
@ -75,7 +75,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
case 0xfe: case 0xfe:
for(int byte = 0; byte < 6; byte++) { for(int byte = 0; byte < 6; byte++) {
last_header[byte] = static_cast<uint8_t>(fgetc(file_)); last_header[byte] = file_.get8();
encoder->add_byte(last_header[byte]); encoder->add_byte(last_header[byte]);
track_offset++; track_offset++;
if(track_offset == 6250) break; if(track_offset == 6250) break;
@ -84,7 +84,7 @@ std::shared_ptr<Track> OricMFMDSK::get_track_at_position(Track::Address address)
case 0xfb: case 0xfb:
for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) { for(int byte = 0; byte < (128 << last_header[3]) + 2; byte++) {
encoder->add_byte(static_cast<uint8_t>(fgetc(file_))); encoder->add_byte(file_.get8());
track_offset++; track_offset++;
if(track_offset == 6250) break; if(track_offset == 6250) break;
} }
@ -156,9 +156,13 @@ void OricMFMDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track
long file_offset = get_file_offset_for_position(track.first); long file_offset = get_file_offset_for_position(track.first);
std::lock_guard<std::mutex> lock_guard(file_access_mutex_); std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
fseek(file_, file_offset, SEEK_SET); file_.seek(file_offset, SEEK_SET);
size_t track_size = std::min(static_cast<size_t>(6400), parsed_track.size()); size_t track_size = std::min(static_cast<size_t>(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();
}

View File

@ -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. 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: public:
/*! /*!
Construct an @c OricMFMDSK containing content from the file with name @c file_name. 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 // implemented to satisfy @c Disk
int get_head_position_count() override; int get_head_position_count() override;
int get_head_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<Track::Address, std::shared_ptr<Track>> &tracks) override; void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override; std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private: private:
std::mutex file_access_mutex_; Storage::FileHolder file_;
long get_file_offset_for_position(Track::Address address); long get_file_offset_for_position(Track::Address address);
uint32_t head_count_; uint32_t head_count_;

View File

@ -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 // very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large // and not ungainly large
if(file_stats_.st_size & 255) throw ErrorNotSSD; if(file_.stats().st_size & 255) throw ErrorNotSSD;
if(file_stats_.st_size < 512) throw ErrorNotSSD; if(file_.stats().st_size < 512) throw ErrorNotSSD;
if(file_stats_.st_size > 800*256) 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 // 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; head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
track_count_ = static_cast<int>(file_stats_.st_size / (256 * 10)); track_count_ = static_cast<int>(file_.stats().st_size / (256 * 10));
if(track_count_ < 40) track_count_ = 40; if(track_count_ < 40) track_count_ = 40;
else if(track_count_ < 80) track_count_ = 80; else if(track_count_ < 80) track_count_ = 80;

View File

@ -31,7 +31,8 @@ std::shared_ptr<Track> Storage::Disk::track_for_sectors(uint8_t *const source, u
first_sector++; first_sector++;
new_sector.size = size; 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; 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 > last_sector) continue;
if(pair.second.address.sector < first_sector) continue; if(pair.second.address.sector < first_sector) continue;
if(pair.second.size != sector_size) 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));
} }
} }

View File

@ -119,7 +119,7 @@ class FMEncoder: public Encoder {
template<class T> std::shared_ptr<Storage::Disk::Track> template<class T> std::shared_ptr<Storage::Disk::Track>
GetTrackWithSectors( GetTrackWithSectors(
const std::vector<Sector> &sectors, const std::vector<const Sector *> &sectors,
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value, size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
size_t pre_address_mark_bytes, size_t pre_address_mark_bytes,
size_t post_address_mark_bytes, uint8_t post_address_mark_value, size_t post_address_mark_bytes, uint8_t post_address_mark_value,
@ -137,38 +137,39 @@ template<class T> std::shared_ptr<Storage::Disk::Track>
for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value); for(size_t c = 0; c < post_index_address_mark_bytes; c++) shifter.add_byte(post_index_address_mark_value);
// add sectors // add sectors
for(const Sector &sector : sectors) { for(const Sector *sector : sectors) {
// gap // gap
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00); for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
// sector header // sector header
shifter.add_ID_address_mark(); shifter.add_ID_address_mark();
shifter.add_byte(sector.address.track); shifter.add_byte(sector->address.track);
shifter.add_byte(sector.address.side); shifter.add_byte(sector->address.side);
shifter.add_byte(sector.address.sector); shifter.add_byte(sector->address.sector);
shifter.add_byte(sector.size); shifter.add_byte(sector->size);
shifter.add_crc(sector.has_header_crc_error); shifter.add_crc(sector->has_header_crc_error);
// gap // 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 < 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); for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data, if attached // data, if attached
if(!sector.data.empty()) { // TODO: allow for weak/fuzzy data.
if(sector.is_deleted) if(!sector->samples.empty()) {
if(sector->is_deleted)
shifter.add_deleted_data_address_mark(); shifter.add_deleted_data_address_mark();
else else
shifter.add_data_address_mark(); shifter.add_data_address_mark();
size_t c = 0; size_t c = 0;
size_t declared_length = static_cast<size_t>(128 << sector.size); size_t declared_length = static_cast<size_t>(128 << sector->size);
for(c = 0; c < sector.data.size() && c < declared_length; c++) { for(c = 0; c < sector->samples[0].size() && c < declared_length; c++) {
shifter.add_byte(sector.data[c]); shifter.add_byte(sector->samples[0][c]);
} }
for(; c < declared_length; c++) { for(; c < declared_length; c++) {
shifter.add_byte(0x00); shifter.add_byte(0x00);
} }
shifter.add_crc(sector.has_data_crc_error); shifter.add_crc(sector->has_data_crc_error);
} }
// gap // gap
@ -202,7 +203,26 @@ void Encoder::add_crc(bool incorrectly) {
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = std::numeric_limits<size_t>::max(); const size_t Storage::Encodings::MFM::DefaultSectorGapLength = std::numeric_limits<size_t>::max();
static std::vector<const Sector *> sector_pointers(const std::vector<Sector> &sectors) {
std::vector<const Sector *> pointers;
for(const Sector &sector: sectors) {
pointers.push_back(&sector);
}
return pointers;
}
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>(
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::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<const Sector *> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<FMEncoder>( return GetTrackWithSectors<FMEncoder>(
sectors, sectors,
26, 0xff, 26, 0xff,
@ -214,6 +234,17 @@ std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSec
} }
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) { std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>(
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::Disk::Track> Storage::Encodings::MFM::GetMFMTrackWithSectors(const std::vector<const Sector *> &sectors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
return GetTrackWithSectors<MFMEncoder>( return GetTrackWithSectors<MFMEncoder>(
sectors, sectors,
50, 0x4e, 50, 0x4e,

View File

@ -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. @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<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<const Sector *> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
/*! /*!
Converts a vector of sectors into a properly-encoded FM track. Converts a vector of sectors into a properly-encoded FM track.
@ -37,6 +38,7 @@ std::shared_ptr<Storage::Disk::Track> GetMFMTrackWithSectors(const std::vector<S
@param sector_gap_filler_byte If specified, sets the value (unencoded) that is used to populate the gap between each ID and its data. @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<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e); std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<Sector> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
std::shared_ptr<Storage::Disk::Track> GetFMTrackWithSectors(const std::vector<const Sector *> &sectors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
class Encoder { class Encoder {
public: public:

View File

@ -34,7 +34,9 @@ struct Sector {
Address address; Address address;
uint8_t size = 0; uint8_t size = 0;
std::vector<uint8_t> data;
// Multiple samplings of the underlying data are accepted, to allow weak and fuzzy data to be communicated.
std::vector<std::vector<uint8_t>> samples;
bool has_data_crc_error = false; bool has_data_crc_error = false;
bool has_header_crc_error = false; bool has_header_crc_error = false;
@ -45,7 +47,7 @@ struct Sector {
Sector(const Sector &&rhs) noexcept : Sector(const Sector &&rhs) noexcept :
address(rhs.address), address(rhs.address),
size(rhs.size), size(rhs.size),
data(std::move(rhs.data)), samples(std::move(rhs.samples)),
has_data_crc_error(rhs.has_data_crc_error), has_data_crc_error(rhs.has_data_crc_error),
has_header_crc_error(rhs.has_header_crc_error), has_header_crc_error(rhs.has_header_crc_error),
is_deleted(rhs.is_deleted ){} is_deleted(rhs.is_deleted ){}

View File

@ -62,7 +62,8 @@ std::map<size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::secto
shifter.set_should_obey_syncs(true); shifter.set_should_obey_syncs(true);
break; break;
default: 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; ++position;
if(position == size + 4) { if(position == size + 4) {
result.insert(std::make_pair(start_location, std::move(*new_sector))); result.insert(std::make_pair(start_location, std::move(*new_sector)));

View File

@ -27,12 +27,12 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
size_t size_read = 0; size_t size_read = 0;
do { do {
Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, static_cast<uint8_t>(track), static_cast<uint8_t>(parameters.first_sector + sector)); Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, static_cast<uint8_t>(track), static_cast<uint8_t>(parameters.first_sector + sector));
if(!sector_contents) { if(!sector_contents || sector_contents->samples.empty()) {
return nullptr; return nullptr;
} }
catalogue.insert(catalogue.end(), sector_contents->data.begin(), sector_contents->data.end()); catalogue.insert(catalogue.end(), sector_contents->samples[0].begin(), sector_contents->samples[0].end());
sector_size = sector_contents->data.size(); sector_size = sector_contents->samples[0].size();
size_read += sector_size; size_read += sector_size;
sector++; sector++;
@ -136,7 +136,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { 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<uint8_t>(track), static_cast<uint8_t>(parameters.first_sector + sector)); Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, static_cast<uint8_t>(track), static_cast<uint8_t>(parameters.first_sector + sector));
if(!sector_contents) break; if(!sector_contents || sector_contents->samples.empty()) break;
sector++; sector++;
if(sector == parameters.sectors_per_track) { if(sector == parameters.sectors_per_track) {
sector = 0; sector = 0;
@ -144,7 +144,7 @@ std::unique_ptr<Storage::Disk::CPM::Catalogue> Storage::Disk::CPM::GetCatalogue(
} }
int records_to_copy = std::min(entry->number_of_records - record, records_per_sector); 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<size_t>(record) * 128], sector_contents->data.data(), static_cast<size_t>(records_to_copy) * 128); memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + static_cast<size_t>(record) * 128], sector_contents->samples[0].data(), static_cast<size_t>(records_to_copy) * 128);
record += records_to_copy; record += records_to_copy;
} }
} }

View File

@ -16,88 +16,177 @@ FileHolder::~FileHolder() {
if(file_) fclose(file_); 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_); stat(file_name.c_str(), &file_stats_);
is_read_only_ = false; is_read_only_ = false;
file_ = fopen(file_name.c_str(), "rb+");
if(!file_) { switch(ideal_mode) {
is_read_only_ = true; case FileMode::ReadWrite:
file_ = fopen(file_name.c_str(), "rb"); 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; 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<uint16_t>(fgetc(file_));
result |= static_cast<uint16_t>(fgetc(file_) << 8);
return result;
}
uint16_t FileHolder::get16be() {
uint16_t result = static_cast<uint16_t>(fgetc(file_) << 8);
result |= static_cast<uint16_t>(fgetc(file_));
return result;
}
uint8_t FileHolder::get8() {
return static_cast<uint8_t>(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<uint8_t> FileHolder::read(size_t size) {
std::vector<uint8_t> 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<uint8_t> &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) { 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 // read and check the file signature
char stored_signature[12]; std::vector<uint8_t> stored_signature = read(length);
if(fread(stored_signature, 1, length, file_) != length) return false; if(stored_signature.size() != length) return false;
if(memcmp(stored_signature, signature, length)) return false; if(memcmp(stored_signature.data(), signature, length)) return false;
return true; return true;
} }
uint32_t FileHolder::fgetc32le() { std::string FileHolder::extension() {
uint32_t result = (uint32_t)fgetc(file_); size_t pointer = name_.size() - 1;
result |= (uint32_t)(fgetc(file_) << 8); while(pointer > 0 && name_[pointer] != '.') pointer--;
result |= (uint32_t)(fgetc(file_) << 16); if(name_[pointer] == '.') pointer++;
result |= (uint32_t)(fgetc(file_) << 24);
return result; std::string extension = name_.substr(pointer);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
} }
uint32_t FileHolder::fgetc24le() { void FileHolder::ensure_is_at_least_length(long length) {
uint32_t result = (uint32_t)fgetc(file_); fseek(file_, 0, SEEK_END);
result |= (uint32_t)(fgetc(file_) << 8); long bytes_to_write = length - ftell(file_);
result |= (uint32_t)(fgetc(file_) << 16); if(bytes_to_write > 0) {
uint8_t *empty = new uint8_t[static_cast<size_t>(bytes_to_write)];
return result; memset(empty, 0, static_cast<size_t>(bytes_to_write));
fwrite(empty, sizeof(uint8_t), static_cast<size_t>(bytes_to_write), file_);
delete[] empty;
}
} }
uint16_t FileHolder::fgetc16le() { bool FileHolder::get_is_known_read_only() {
uint16_t result = static_cast<uint16_t>(fgetc(file_));
result |= static_cast<uint16_t>(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<uint16_t>(fgetc(file_) << 8);
result |= static_cast<uint16_t>(fgetc(file_));
return result;
}
bool FileHolder::get_is_read_only() {
return is_read_only_; return is_read_only_;
} }
void FileHolder::ensure_file_is_at_least_length(long length) { struct stat &FileHolder::stats() {
fseek(file_, 0, SEEK_END); return file_stats_;
long bytes_to_write = length - ftell(file_);
if(bytes_to_write > 0) {
uint8_t *empty = new uint8_t[static_cast<size_t>(bytes_to_write)];
memset(empty, 0, static_cast<size_t>(bytes_to_write));
fwrite(empty, sizeof(uint8_t), static_cast<size_t>(bytes_to_write), file_);
delete[] empty;
}
} }
std::string FileHolder::extension() { std::mutex &FileHolder::get_file_access_mutex() {
size_t pointer = name_.size() - 1; return file_access_mutex_;
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;
} }

View File

@ -14,84 +14,119 @@
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <vector>
namespace Storage { namespace Storage {
class FileHolder { class FileHolder final {
public: public:
enum { enum {
ErrorCantOpen = -1 ErrorCantOpen = -1
}; };
enum class FileMode {
ReadWrite,
Read,
Rewrite
};
virtual ~FileHolder(); ~FileHolder();
protected:
FileHolder(const std::string &file_name);
/*! /*!
Reads @c length bytes from the file and compares them to the first Attempts to open the file indicated by @c file_name. @c ideal_mode nominates how the file would
@c length bytes of @c signature. If @c length is 0, it is computed most ideally be opened. It can be one of:
as the length of @c signature up to and including the terminating null.
ReadWrite attempt to open this file for random access reading and writing. If that fails,
@returns @c true if the bytes read match the signature; @c false otherwise. 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.
*/ */
bool check_signature(const char *signature, size_t length); FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);
/*! /*!
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. 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 Performs @c get8 four 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
and returning the four assembled in big endian order. 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();
/*!
Writes @c value using two successive @c put8s, in little endian order.
*/
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. 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 Writes @c value using two successive @c put8s, in big endian order.
back to the first dot. The string is converted to lowercase before being returned.
*/ */
std::string extension(); void put16be(uint16_t value);
/*! /*! Reads a single byte from @c file. */
Ensures the file is at least @c length bytes long, appending 0s until it is uint8_t get8();
if necessary.
*/
void ensure_file_is_at_least_length(long length);
/*! /*! Writes a single byte from @c file. */
@returns @c true if this file is read-only; @c false otherwise. void put8(uint8_t value);
*/
bool get_is_read_only(); /*! 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<uint8_t> read(size_t size);
/*! Reads @c size bytes and writes them to @c buffer. */
size_t read(uint8_t *buffer, size_t size);
/*! Writes @c buffer one byte at a time in order. */
size_t write(const std::vector<uint8_t> &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 { class BitStream {
public: 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 get_bits(int q) {
uint8_t result = 0; uint8_t result = 0;
while(q--) { while(q--) {
@ -101,6 +136,13 @@ class FileHolder {
} }
private: private:
BitStream(FILE *file, bool lsb_first) :
file_(file),
lsb_first_(lsb_first),
next_value_(0),
bits_remaining_(0) {}
friend FileHolder;
FILE *file_; FILE *file_;
bool lsb_first_; bool lsb_first_;
uint8_t next_value_; uint8_t next_value_;
@ -126,14 +168,56 @@ class FileHolder {
return bit; 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_; Reads @c length bytes from the file and compares them to the first
std::mutex file_access_mutex_; @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 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();
/*!
@returns the stat struct describing this file.
*/
struct stat &stats();
/*!
@returns a mutex owned by the file that can be used to serialise file access.
*/
std::mutex &get_file_access_mutex();
const std::string name_;
private: 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_;
}; };
} }

View File

@ -11,22 +11,21 @@
using namespace Storage::Tape; using namespace Storage::Tape;
CSW::CSW(const char *file_name) : CSW::CSW(const char *file_name) :
Storage::FileHolder(file_name), file_(file_name),
source_data_pointer_(0) { source_data_pointer_(0) {
if(file_stats_.st_size < 0x20) throw ErrorNotCSW; if(file_.stats().st_size < 0x20) throw ErrorNotCSW;
// Check signature. // Check signature.
char identifier[22]; if(!file_.check_signature("Compressed Square Wave")) {
char signature[] = "Compressed Square Wave"; throw ErrorNotCSW;
fread(identifier, 1, strlen(signature), file_); }
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotCSW;
// Check terminating byte. // Check terminating byte.
if(fgetc(file_) != 0x1a) throw ErrorNotCSW; if(file_.get8() != 0x1a) throw ErrorNotCSW;
// Get version file number. // Get version file number.
uint8_t major_version = static_cast<uint8_t>(fgetc(file_)); uint8_t major_version = file_.get8();
uint8_t minor_version = static_cast<uint8_t>(fgetc(file_)); uint8_t minor_version = file_.get8();
// Reject if this is an unknown version. // Reject if this is an unknown version.
if(major_version > 2 || !major_version || minor_version > 1) throw ErrorNotCSW; 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. // The header now diverges based on version.
uint32_t number_of_waves = 0; uint32_t number_of_waves = 0;
if(major_version == 1) { 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; 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 { } else {
pulse_.length.clock_rate = fgetc32le(); pulse_.length.clock_rate = file_.get32le();
number_of_waves = fgetc32le(); number_of_waves = file_.get32le();
switch(fgetc(file_)) { switch(file_.get8()) {
case 1: compression_type_ = RLE; break; case 1: compression_type_ = RLE; break;
case 2: compression_type_ = ZRLE; break; case 2: compression_type_ = ZRLE; break;
default: throw ErrorNotCSW; default: throw ErrorNotCSW;
} }
pulse_.type = (fgetc(file_) & 1) ? Pulse::High : Pulse::Low; pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low;
uint8_t extension_length = static_cast<uint8_t>(fgetc(file_)); uint8_t extension_length = file_.get8();
if(file_stats_.st_size < 0x34 + extension_length) throw ErrorNotCSW; if(file_.stats().st_size < 0x34 + extension_length) throw ErrorNotCSW;
fseek(file_, 0x34 + extension_length, SEEK_SET); file_.seek(0x34 + extension_length, SEEK_SET);
} }
if(compression_type_ == ZRLE) { if(compression_type_ == ZRLE) {
@ -65,9 +64,9 @@ CSW::CSW(const char *file_name) :
source_data_.resize(static_cast<size_t>(number_of_waves) * 5); source_data_.resize(static_cast<size_t>(number_of_waves) * 5);
std::vector<uint8_t> file_data; std::vector<uint8_t> file_data;
size_t remaining_data = static_cast<size_t>(file_stats_.st_size) - static_cast<size_t>(ftell(file_)); size_t remaining_data = static_cast<size_t>(file_.stats().st_size) - static_cast<size_t>(file_.tell());
file_data.resize(remaining_data); 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 // 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 // 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()); uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size());
source_data_.resize(static_cast<size_t>(output_length)); source_data_.resize(static_cast<size_t>(output_length));
} else { } else {
rle_start_ = ftell(file_); rle_start_ = file_.tell();
} }
invert_pulse(); invert_pulse();
@ -84,7 +83,7 @@ CSW::CSW(const char *file_name) :
uint8_t CSW::get_next_byte() { uint8_t CSW::get_next_byte() {
switch(compression_type_) { switch(compression_type_) {
case RLE: return static_cast<uint8_t>(fgetc(file_)); case RLE: return file_.get8();
case ZRLE: { case ZRLE: {
if(source_data_pointer_ == source_data_.size()) return 0xff; if(source_data_pointer_ == source_data_.size()) return 0xff;
uint8_t result = source_data_[source_data_pointer_]; uint8_t result = source_data_[source_data_pointer_];
@ -96,7 +95,7 @@ uint8_t CSW::get_next_byte() {
uint32_t CSW::get_next_int32le() { uint32_t CSW::get_next_int32le() {
switch(compression_type_) { switch(compression_type_) {
case RLE: return fgetc32le(); case RLE: return file_.get32le();
case ZRLE: { case ZRLE: {
if(source_data_pointer_ > source_data_.size() - 4) return 0xffff; if(source_data_pointer_ > source_data_.size() - 4) return 0xffff;
uint32_t result = (uint32_t)( uint32_t result = (uint32_t)(
@ -116,14 +115,14 @@ void CSW::invert_pulse() {
bool CSW::is_at_end() { bool CSW::is_at_end() {
switch(compression_type_) { switch(compression_type_) {
case RLE: return (bool)feof(file_); case RLE: return file_.eof();
case ZRLE: return source_data_pointer_ == source_data_.size(); case ZRLE: return source_data_pointer_ == source_data_.size();
} }
} }
void CSW::virtual_reset() { void CSW::virtual_reset() {
switch(compression_type_) { 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; case ZRLE: source_data_pointer_ = 0; break;
} }
} }

View File

@ -21,7 +21,7 @@ namespace Tape {
/*! /*!
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. 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: public:
/*! /*!
Constructs a @c CSW containing content from the file with name @c file_name. 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(); bool is_at_end();
private: private:
Storage::FileHolder file_;
void virtual_reset(); void virtual_reset();
Pulse virtual_get_next_pulse(); Pulse virtual_get_next_pulse();

View File

@ -14,14 +14,13 @@ using namespace Storage::Tape;
CommodoreTAP::CommodoreTAP(const char *file_name) : CommodoreTAP::CommodoreTAP(const char *file_name) :
is_at_end_(false), 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; throw ErrorNotCommodoreTAP;
// check the file version // check the file version
int version = fgetc(file_); switch(file_.get8())
switch(version)
{ {
case 0: updated_layout_ = false; break; case 0: updated_layout_ = false; break;
case 1: updated_layout_ = true; break; case 1: updated_layout_ = true; break;
@ -29,10 +28,10 @@ CommodoreTAP::CommodoreTAP(const char *file_name) :
} }
// skip reserved bytes // skip reserved bytes
fseek(file_, 3, SEEK_CUR); file_.seek(3, SEEK_CUR);
// read file size // read file size
file_size_ = fgetc32le(); file_size_ = file_.get32le();
// set up for pulse output at the PAL clock rate, with each high and // 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 // 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() void CommodoreTAP::virtual_reset()
{ {
fseek(file_, 0x14, SEEK_SET); file_.seek(0x14, SEEK_SET);
current_pulse_.type = Pulse::High; current_pulse_.type = Pulse::High;
is_at_end_ = false; is_at_end_ = false;
} }
@ -64,17 +63,17 @@ Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse()
if(current_pulse_.type == Pulse::High) if(current_pulse_.type == Pulse::High)
{ {
uint32_t next_length; uint32_t next_length;
uint8_t next_byte = static_cast<uint8_t>(fgetc(file_)); uint8_t next_byte = file_.get8();
if(!updated_layout_ || next_byte > 0) if(!updated_layout_ || next_byte > 0)
{ {
next_length = (uint32_t)next_byte << 3; next_length = (uint32_t)next_byte << 3;
} }
else else
{ {
next_length = fgetc24le(); next_length = file_.get24le();
} }
if(feof(file_)) if(file_.eof())
{ {
is_at_end_ = true; is_at_end_ = true;
current_pulse_.length.length = current_pulse_.length.clock_rate; current_pulse_.length.length = current_pulse_.length.clock_rate;

View File

@ -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. 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: public:
/*! /*!
Constructs a @c CommodoreTAP containing content from the file with name @c file_name. 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(); bool is_at_end();
private: private:
Storage::FileHolder file_;
void virtual_reset(); void virtual_reset();
Pulse virtual_get_next_pulse(); Pulse virtual_get_next_pulse();

View File

@ -13,48 +13,41 @@
using namespace Storage::Tape; using namespace Storage::Tape;
OricTAP::OricTAP(const char *file_name) : OricTAP::OricTAP(const char *file_name) :
Storage::FileHolder(file_name) file_(file_name)
{ {
// check the file signature // check the file signature
if(!check_signature("\x16\x16\x16\x24", 4)) if(!file_.check_signature("\x16\x16\x16\x24", 4))
throw ErrorNotOricTAP; throw ErrorNotOricTAP;
// then rewind and start again // then rewind and start again
virtual_reset(); virtual_reset();
} }
void OricTAP::virtual_reset() void OricTAP::virtual_reset() {
{ file_.seek(0, SEEK_SET);
fseek(file_, 0, SEEK_SET);
bit_count_ = 13; bit_count_ = 13;
phase_ = next_phase_ = LeadIn; phase_ = next_phase_ = LeadIn;
phase_counter_ = 0; phase_counter_ = 0;
pulse_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. // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s.
if(bit_count_ == 13) if(bit_count_ == 13) {
{ if(next_phase_ != phase_) {
if(next_phase_ != phase_)
{
phase_ = next_phase_; phase_ = next_phase_;
phase_counter_ = 0; phase_counter_ = 0;
} }
bit_count_ = 0; bit_count_ = 0;
uint8_t next_byte = 0; uint8_t next_byte = 0;
switch(phase_) switch(phase_) {
{
case LeadIn: case LeadIn:
next_byte = phase_counter_ < 258 ? 0x16 : 0x24; next_byte = phase_counter_ < 258 ? 0x16 : 0x24;
phase_counter_++; phase_counter_++;
if(phase_counter_ == 259) // 256 artificial bytes plus the three in the file = 259 if(phase_counter_ == 259) { // 256 artificial bytes plus the three in the file = 259
{ while(1) {
while(1) if(file_.get8() != 0x16) break;
{
if(fgetc(file_) != 0x16) break;
} }
next_phase_ = Header; next_phase_ = Header;
} }
@ -69,19 +62,17 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
// [6, 7]: start address of data // [6, 7]: start address of data
// 8: "unused" (on the Oric 1) // 8: "unused" (on the Oric 1)
// [9...]: filename, up to NULL byte // [9...]: filename, up to NULL byte
next_byte = static_cast<uint8_t>(fgetc(file_)); next_byte = file_.get8();
if(phase_counter_ == 4) data_end_address_ = static_cast<uint16_t>(next_byte << 8); if(phase_counter_ == 4) data_end_address_ = static_cast<uint16_t>(next_byte << 8);
if(phase_counter_ == 5) data_end_address_ |= next_byte; if(phase_counter_ == 5) data_end_address_ |= next_byte;
if(phase_counter_ == 6) data_start_address_ = static_cast<uint16_t>(next_byte << 8); if(phase_counter_ == 6) data_start_address_ = static_cast<uint16_t>(next_byte << 8);
if(phase_counter_ == 7) data_start_address_ |= next_byte; 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; next_phase_ = Gap;
} }
if(feof(file_)) if(file_.eof()) {
{
next_phase_ = End; next_phase_ = End;
} }
phase_counter_++; phase_counter_++;
@ -89,23 +80,19 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
case Gap: case Gap:
phase_counter_++; phase_counter_++;
if(phase_counter_ == 8) if(phase_counter_ == 8) {
{
next_phase_ = Data; next_phase_ = Data;
} }
break; break;
case Data: case Data:
next_byte = static_cast<uint8_t>(fgetc(file_)); next_byte = file_.get8();
phase_counter_++; phase_counter_++;
if(phase_counter_ >= (data_end_address_ - data_start_address_)+1) if(phase_counter_ >= (data_end_address_ - data_start_address_)+1) {
{ if(next_byte == 0x16) {
if(next_byte == 0x16)
{
next_phase_ = LeadIn; next_phase_ = LeadIn;
} }
else if(feof(file_)) else if(file_.eof()) {
{
next_phase_ = End; next_phase_ = End;
} }
} }
@ -129,8 +116,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
pulse.length.clock_rate = 4800; pulse.length.clock_rate = 4800;
int next_bit; int next_bit;
switch(phase_) switch(phase_) {
{
case End: case End:
pulse.type = Pulse::Zero; pulse.type = Pulse::Zero;
pulse.length.length = 4800; pulse.length.length = 4800;
@ -147,26 +133,21 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
break; break;
} }
if(next_bit) if(next_bit) {
{
pulse.length.length = 1; pulse.length.length = 1;
} } else {
else
{
pulse.length.length = pulse_counter_ ? 2 : 1; pulse.length.length = pulse_counter_ ? 2 : 1;
} }
pulse.type = pulse_counter_ ? Pulse::High : Pulse::Low; // TODO pulse.type = pulse_counter_ ? Pulse::High : Pulse::Low; // TODO
pulse_counter_ ^= 1; pulse_counter_ ^= 1;
if(!pulse_counter_) if(!pulse_counter_) {
{
current_value_ >>= 1; current_value_ >>= 1;
bit_count_++; bit_count_++;
} }
return pulse; return pulse;
} }
bool OricTAP::is_at_end() bool OricTAP::is_at_end() {
{
return phase_ == End; return phase_ == End;
} }

View File

@ -19,7 +19,7 @@ namespace Tape {
/*! /*!
Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. 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: public:
/*! /*!
Constructs an @c OricTAP containing content from the file with name @c file_name. 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(); bool is_at_end();
private: private:
Storage::FileHolder file_;
void virtual_reset(); void virtual_reset();
Pulse virtual_get_next_pulse(); Pulse virtual_get_next_pulse();

View File

@ -16,19 +16,16 @@ const unsigned int TZXClockMSMultiplier = 3500;
} }
TZX::TZX(const char *file_name) : TZX::TZX(const char *file_name) :
Storage::FileHolder(file_name), file_(file_name),
current_level_(false) { current_level_(false) {
// Check for signature followed by a 0x1a // Check for signature followed by a 0x1a
char identifier[7]; if(!file_.check_signature("ZXTape!")) throw ErrorNotTZX;
char signature[] = "ZXTape!"; if(file_.get8() != 0x1a) throw ErrorNotTZX;
fread(identifier, 1, strlen(signature), file_);
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotTZX;
if(fgetc(file_) != 0x1a) throw ErrorNotTZX;
// Get version number // Get version number
uint8_t major_version = static_cast<uint8_t>(fgetc(file_)); uint8_t major_version = file_.get8();
uint8_t minor_version = static_cast<uint8_t>(fgetc(file_)); uint8_t minor_version = file_.get8();
// Reject if an incompatible version // Reject if an incompatible version
if(major_version != 1 || minor_version > 20) throw ErrorNotTZX; if(major_version != 1 || minor_version > 20) throw ErrorNotTZX;
@ -39,7 +36,7 @@ TZX::TZX(const char *file_name) :
void TZX::virtual_reset() { void TZX::virtual_reset() {
clear(); clear();
set_is_at_end(false); 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 // 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 // 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() { void TZX::get_next_pulses() {
while(empty()) { while(empty()) {
uint8_t chunk_id = static_cast<uint8_t>(fgetc(file_)); uint8_t chunk_id = file_.get8();
if(feof(file_)) { if(file_.eof()) {
set_is_at_end(true); set_is_at_end(true);
return; return;
} }
@ -90,24 +87,24 @@ void TZX::get_next_pulses() {
} }
void TZX::get_generalised_data_block() { void TZX::get_generalised_data_block() {
uint32_t block_length = fgetc32le(); uint32_t block_length = file_.get32le();
long endpoint = ftell(file_) + static_cast<long>(block_length); long endpoint = file_.tell() + static_cast<long>(block_length);
uint16_t pause_after_block = fgetc16le(); uint16_t pause_after_block = file_.get16le();
uint32_t total_pilot_symbols = fgetc32le(); uint32_t total_pilot_symbols = file_.get32le();
uint8_t maximum_pulses_per_pilot_symbol = static_cast<uint8_t>(fgetc(file_)); uint8_t maximum_pulses_per_pilot_symbol = file_.get8();
uint8_t symbols_in_pilot_table = static_cast<uint8_t>(fgetc(file_)); uint8_t symbols_in_pilot_table = file_.get8();
uint32_t total_data_symbols = fgetc32le(); uint32_t total_data_symbols = file_.get32le();
uint8_t maximum_pulses_per_data_symbol = static_cast<uint8_t>(fgetc(file_)); uint8_t maximum_pulses_per_data_symbol = file_.get8();
uint8_t symbols_in_data_table = static_cast<uint8_t>(fgetc(file_)); 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_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); get_generalised_segment(total_data_symbols, maximum_pulses_per_data_symbol, symbols_in_data_table, true);
post_gap(pause_after_block); post_gap(pause_after_block);
// This should be unnecessary, but intends to preserve sanity. // 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) { 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> symbol_table; std::vector<Symbol> symbol_table;
for(int c = 0; c < number_of_symbols; c++) { for(int c = 0; c < number_of_symbols; c++) {
Symbol symbol; Symbol symbol;
symbol.flags = static_cast<uint8_t>(fgetc(file_)); symbol.flags = file_.get8();
for(int ic = 0; ic < max_pulses_per_symbol; ic++) { 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); symbol_table.push_back(symbol);
} }
// Hence produce the output. // Hence produce the output.
BitStream stream(file_, false); FileHolder::BitStream stream = file_.get_bitstream(false);
int base = 2; int base = 2;
int bits = 1; int bits = 1;
while(base < number_of_symbols) { 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); symbol_value = stream.get_bits(bits);
count = 1; count = 1;
} else { } else {
symbol_value = static_cast<uint8_t>(fgetc(file_)); symbol_value = file_.get8();
count = fgetc16le(); count = file_.get16le();
} }
if(symbol_value > number_of_symbols) { if(symbol_value > number_of_symbols) {
continue; continue;
@ -178,29 +175,28 @@ void TZX::get_standard_speed_data_block() {
data_block.data.length_of_one_bit_pulse = 1710; data_block.data.length_of_one_bit_pulse = 1710;
data_block.data.number_of_bits_in_final_byte = 8; data_block.data.number_of_bits_in_final_byte = 8;
data_block.data.pause_after_block = fgetc16le(); data_block.data.pause_after_block = file_.get16le();
data_block.data.data_length = fgetc16le(); data_block.data.data_length = file_.get16le();
if(!data_block.data.data_length) return; if(!data_block.data.data_length) return;
uint8_t first_byte = static_cast<uint8_t>(fgetc(file_)); uint8_t first_byte = file_.get8();
data_block.length_of_pilot_tone = (first_byte < 128) ? 8063 : 3223; 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); get_data_block(data_block);
} }
void TZX::get_turbo_speed_data_block() { void TZX::get_turbo_speed_data_block() {
DataBlock data_block; DataBlock data_block;
data_block.length_of_pilot_pulse = fgetc16le(); data_block.length_of_pilot_pulse = file_.get16le();
data_block.length_of_sync_first_pulse = fgetc16le(); data_block.length_of_sync_first_pulse = file_.get16le();
data_block.length_of_sync_second_pulse = fgetc16le(); data_block.length_of_sync_second_pulse = file_.get16le();
data_block.data.length_of_zero_bit_pulse = fgetc16le(); data_block.data.length_of_zero_bit_pulse = file_.get16le();
data_block.data.length_of_one_bit_pulse = fgetc16le(); data_block.data.length_of_one_bit_pulse = file_.get16le();
data_block.length_of_pilot_tone = fgetc16le(); data_block.length_of_pilot_tone = file_.get16le();
data_block.data.number_of_bits_in_final_byte = static_cast<uint8_t>(fgetc(file_)); data_block.data.number_of_bits_in_final_byte = file_.get8();
data_block.data.pause_after_block = fgetc16le(); data_block.data.pause_after_block = file_.get16le();
data_block.data.data_length = fgetc16le(); data_block.data.data_length = file_.get24le();
data_block.data.data_length |= static_cast<long>(fgetc(file_) << 16);
get_data_block(data_block); 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) { void TZX::get_data(const Data &data) {
// Output data. // Output data.
for(unsigned int c = 0; c < data.data_length; c++) { for(unsigned int c = 0; c < data.data_length; c++) {
uint8_t next_byte = static_cast<uint8_t>(fgetc(file_)); uint8_t next_byte = file_.get8();
unsigned int bits = (c != data.data_length-1) ? 8 : data.number_of_bits_in_final_byte; unsigned int bits = (c != data.data_length-1) ? 8 : data.number_of_bits_in_final_byte;
while(bits--) { while(bits--) {
@ -238,33 +234,32 @@ void TZX::get_data(const Data &data) {
} }
void TZX::get_pure_tone_data_block() { void TZX::get_pure_tone_data_block() {
uint16_t length_of_pulse = fgetc16le(); uint16_t length_of_pulse = file_.get16le();
uint16_t nunber_of_pulses = fgetc16le(); uint16_t nunber_of_pulses = file_.get16le();
while(nunber_of_pulses--) post_pulse(length_of_pulse); while(nunber_of_pulses--) post_pulse(length_of_pulse);
} }
void TZX::get_pure_data_block() { void TZX::get_pure_data_block() {
Data data; Data data;
data.length_of_zero_bit_pulse = fgetc16le(); data.length_of_zero_bit_pulse = file_.get16le();
data.length_of_one_bit_pulse = fgetc16le(); data.length_of_one_bit_pulse = file_.get16le();
data.number_of_bits_in_final_byte = static_cast<uint8_t>(fgetc(file_)); data.number_of_bits_in_final_byte = file_.get8();
data.pause_after_block = fgetc16le(); data.pause_after_block = file_.get16le();
data.data_length = fgetc16le(); data.data_length = file_.get24le();
data.data_length |= static_cast<long>(fgetc(file_) << 16);
get_data(data); get_data(data);
} }
void TZX::get_pulse_sequence() { void TZX::get_pulse_sequence() {
uint8_t number_of_pulses = static_cast<uint8_t>(fgetc(file_)); uint8_t number_of_pulses = file_.get8();
while(number_of_pulses--) { while(number_of_pulses--) {
post_pulse(fgetc16le()); post_pulse(file_.get16le());
} }
} }
void TZX::get_pause() { void TZX::get_pause() {
uint16_t duration = fgetc16le(); uint16_t duration = file_.get16le();
if(!duration) { if(!duration) {
// TODO (maybe): post a 'pause the tape' suggestion // TODO (maybe): post a 'pause the tape' suggestion
} else { } else {
@ -297,20 +292,20 @@ void TZX::post_pulse(const Storage::Time &time) {
void TZX::ignore_group_start() { void TZX::ignore_group_start() {
printf("Ignoring TZX group\n"); printf("Ignoring TZX group\n");
uint8_t length = static_cast<uint8_t>(fgetc(file_)); uint8_t length = file_.get8();
fseek(file_, length, SEEK_CUR); file_.seek(length, SEEK_CUR);
} }
void TZX::ignore_group_end() { void TZX::ignore_group_end() {
} }
void TZX::ignore_jump_to_block() { void TZX::ignore_jump_to_block() {
__unused uint16_t target = fgetc16le(); __unused uint16_t target = file_.get16le();
printf("Ignoring TZX jump\n"); printf("Ignoring TZX jump\n");
} }
void TZX::ignore_loop_start() { 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"); printf("Ignoring TZX loop\n");
} }
@ -318,8 +313,8 @@ void TZX::ignore_loop_end() {
} }
void TZX::ignore_call_sequence() { void TZX::ignore_call_sequence() {
__unused uint16_t number_of_entries = fgetc16le(); __unused uint16_t number_of_entries = file_.get16le();
fseek(file_, number_of_entries * sizeof(uint16_t), SEEK_CUR); file_.seek(number_of_entries * sizeof(uint16_t), SEEK_CUR);
printf("Ignoring TZX call sequence\n"); printf("Ignoring TZX call sequence\n");
} }
@ -328,29 +323,29 @@ void TZX::ignore_return_from_sequence() {
} }
void TZX::ignore_select_block() { void TZX::ignore_select_block() {
__unused uint16_t length_of_block = fgetc16le(); __unused uint16_t length_of_block = file_.get16le();
fseek(file_, length_of_block, SEEK_CUR); file_.seek(length_of_block, SEEK_CUR);
printf("Ignoring TZX select block\n"); printf("Ignoring TZX select block\n");
} }
#pragma mark - Messaging #pragma mark - Messaging
void TZX::ignore_text_description() { void TZX::ignore_text_description() {
uint8_t length = static_cast<uint8_t>(fgetc(file_)); uint8_t length = file_.get8();
fseek(file_, length, SEEK_CUR); file_.seek(length, SEEK_CUR);
printf("Ignoring TZX text description\n"); printf("Ignoring TZX text description\n");
} }
void TZX::ignore_message_block() { void TZX::ignore_message_block() {
__unused uint8_t time_for_display = static_cast<uint8_t>(fgetc(file_)); __unused uint8_t time_for_display = file_.get8();
uint8_t length = static_cast<uint8_t>(fgetc(file_)); uint8_t length = file_.get8();
fseek(file_, length, SEEK_CUR); file_.seek(length, SEEK_CUR);
printf("Ignoring TZX message\n"); printf("Ignoring TZX message\n");
} }
void TZX::get_hardware_type() { void TZX::get_hardware_type() {
// TODO: pick a way to retain and communicate this. // TODO: pick a way to retain and communicate this.
uint8_t number_of_machines = static_cast<uint8_t>(fgetc(file_)); uint8_t number_of_machines = file_.get8();
fseek(file_, number_of_machines * 3, SEEK_CUR); file_.seek(number_of_machines * 3, SEEK_CUR);
printf("Ignoring TZX hardware types (%d)\n", number_of_machines); printf("Ignoring TZX hardware types (%d)\n", number_of_machines);
} }

View File

@ -18,7 +18,7 @@ namespace Tape {
/*! /*!
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. 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: public:
/*! /*!
Constructs a @c TZX containing content from the file with name @c file_name. 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: private:
Storage::FileHolder file_;
void virtual_reset(); void virtual_reset();
void get_next_pulses(); void get_next_pulses();

View File

@ -53,22 +53,21 @@ PRG::PRG(const char *file_name) :
file_phase_(FilePhaseLeadIn), file_phase_(FilePhaseLeadIn),
phase_offset_(0), phase_offset_(0),
copy_mask_(0x80), 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, // 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. // 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; throw ErrorBadFormat;
load_address_ = fgetc16le(); load_address_ = file_.get16le();
length_ = static_cast<uint16_t>(file_stats_.st_size - 2); length_ = static_cast<uint16_t>(file_.stats().st_size - 2);
if (load_address_ + length_ >= 65536) if (load_address_ + length_ >= 65536)
throw ErrorBadFormat; 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 // these are all microseconds per pole
static const unsigned int leader_zero_length = 179; static const unsigned int leader_zero_length = 179;
static const unsigned int zero_length = 169; static const unsigned int zero_length = 169;
@ -81,8 +80,7 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
Tape::Pulse pulse; Tape::Pulse pulse;
pulse.length.clock_rate = 1000000; pulse.length.clock_rate = 1000000;
pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low; 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 Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : 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; 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; return pulse;
} }
void PRG::virtual_reset() void PRG::virtual_reset() {
{
bit_phase_ = 3; bit_phase_ = 3;
fseek(file_, 2, SEEK_SET); file_.seek(2, SEEK_SET);
file_phase_ = FilePhaseLeadIn; file_phase_ = FilePhaseLeadIn;
phase_offset_ = 0; phase_offset_ = 0;
copy_mask_ = 0x80; copy_mask_ = 0x80;
} }
bool PRG::is_at_end() bool PRG::is_at_end() {
{
return file_phase_ == FilePhaseAtEnd; 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 block_length = 192; // not counting the checksum
static const int countdown_bytes = 9; static const int countdown_bytes = 9;
static const int leadin_length = 20000; static const int leadin_length = 20000;
static const int block_leadin_length = 5000; static const int block_leadin_length = 5000;
if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd) if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd) {
{
output_token_ = Silence; output_token_ = Silence;
if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData; if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData;
return; 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 // 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 // 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; output_token_ = Leader;
phase_offset_++; phase_offset_++;
if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length) if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length) {
{
phase_offset_ = 0; phase_offset_ = 0;
file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData; file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
} }
@ -144,15 +136,13 @@ void PRG::get_next_output_token()
if(!bit_offset && if(!bit_offset &&
( (
(file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) || (file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
feof(file_) file_.eof()
) )
) ) {
{
output_token_ = EndOfBlock; output_token_ = EndOfBlock;
phase_offset_ = 0; phase_offset_ = 0;
switch(file_phase_) switch(file_phase_) {
{
default: break; default: break;
case FilePhaseHeader: case FilePhaseHeader:
copy_mask_ ^= 0x80; copy_mask_ ^= 0x80;
@ -160,35 +150,25 @@ void PRG::get_next_output_token()
break; break;
case FilePhaseData: case FilePhaseData:
copy_mask_ ^= 0x80; copy_mask_ ^= 0x80;
fseek(file_, 2, SEEK_SET); file_.seek(2, SEEK_SET);
if(copy_mask_) file_phase_ = FilePhaseAtEnd; if(copy_mask_) file_phase_ = FilePhaseAtEnd;
break; break;
} }
return; 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 // 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<uint8_t>(countdown_bytes - byte_offset) | copy_mask_; output_byte_ = static_cast<uint8_t>(countdown_bytes - byte_offset) | copy_mask_;
} } else {
else if(file_phase_ == FilePhaseHeader) {
{ if(byte_offset == countdown_bytes + block_length) {
if(file_phase_ == FilePhaseHeader)
{
if(byte_offset == countdown_bytes + block_length)
{
output_byte_ = check_digit_; output_byte_ = check_digit_;
} } else {
else
{
if(byte_offset == countdown_bytes) check_digit_ = 0; if(byte_offset == countdown_bytes) check_digit_ = 0;
if(file_phase_ == FilePhaseHeader) if(file_phase_ == FilePhaseHeader) {
{ switch(byte_offset - countdown_bytes) {
switch(byte_offset - countdown_bytes)
{
case 0: output_byte_ = 0x03; break; case 0: output_byte_ = 0x03; break;
case 1: output_byte_ = load_address_ & 0xff; break; case 1: output_byte_ = load_address_ & 0xff; break;
case 2: output_byte_ = (load_address_ >> 8)&0xff; break; case 2: output_byte_ = (load_address_ >> 8)&0xff; break;
@ -204,12 +184,9 @@ void PRG::get_next_output_token()
} }
} }
} }
} } else {
else output_byte_ = file_.get8();
{ if(file_.eof()) {
output_byte_ = static_cast<uint8_t>(fgetc(file_));
if(feof(file_))
{
output_byte_ = check_digit_; output_byte_ = check_digit_;
} }
} }
@ -218,16 +195,14 @@ void PRG::get_next_output_token()
} }
} }
switch(bit_offset) switch(bit_offset) {
{
case 0: case 0:
output_token_ = WordMarker; output_token_ = WordMarker;
break; break;
default: // i.e. 18 default: // i.e. 18
output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero; output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero;
break; break;
case 9: case 9: {
{
uint8_t parity = output_byte_; uint8_t parity = output_byte_;
parity ^= (parity >> 4); parity ^= (parity >> 4);
parity ^= (parity >> 2); parity ^= (parity >> 2);

View File

@ -19,7 +19,7 @@ namespace Tape {
/*! /*!
Provides a @c Tape containing a .PRG, which is a direct local file. 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: public:
/*! /*!
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. 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(); bool is_at_end();
private: private:
FileHolder file_;
Pulse virtual_get_next_pulse(); Pulse virtual_get_next_pulse();
void virtual_reset(); void virtual_reset();

View File

@ -11,15 +11,15 @@
using namespace Storage::Tape; using namespace Storage::Tape;
ZX80O81P::ZX80O81P(const char *file_name) : ZX80O81P::ZX80O81P(const char *file_name) {
Storage::FileHolder(file_name) { Storage::FileHolder file(file_name);
// Grab the actual file contents // Grab the actual file contents
data_.resize(static_cast<size_t>(file_stats_.st_size)); data_.resize(static_cast<size_t>(file.stats().st_size));
fread(data_.data(), 1, static_cast<size_t>(file_stats_.st_size), file_); file.read(data_.data(), static_cast<size_t>(file.stats().st_size));
// If it's a ZX81 file, prepend a file name. // If it's a ZX81 file, prepend a file name.
std::string type = extension(); std::string type = file.extension();
platform_type_ = TargetPlatform::ZX80; platform_type_ = TargetPlatform::ZX80;
if(type == "p" || type == "81") { if(type == "p" || type == "81") {
// TODO, maybe: prefix a proper file name; this is leaving the file nameless. // 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; platform_type_ = TargetPlatform::ZX81;
} }
std::shared_ptr<::Storage::Data::ZX8081::File> file = Storage::Data::ZX8081::FileFromData(data_); std::shared_ptr<::Storage::Data::ZX8081::File> zx_file = Storage::Data::ZX8081::FileFromData(data_);
if(!file) throw ErrorNotZX80O81P; if(!zx_file) throw ErrorNotZX80O81P;
// then rewind and start again // then rewind and start again
virtual_reset(); virtual_reset();

View File

@ -23,7 +23,7 @@ namespace Tape {
/*! /*!
Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture. 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: public:
/*! /*!
Constructs a @c ZX80O containing content from the file with name @c file_name. Constructs a @c ZX80O containing content from the file with name @c file_name.