1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +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);
if(!names || !details) return nullptr;
if(names->data.size() != 256 || details->data.size() != 256) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
if(names->samples[0].size() != 256 || details->samples[0].size() != 256) return nullptr;
uint8_t final_file_offset = details->data[5];
uint8_t final_file_offset = details->samples[0][5];
if(final_file_offset&7) return nullptr;
if(final_file_offset < 8) return nullptr;
char disk_name[13];
snprintf(disk_name, 13, "%.8s%.4s", &names->data[0], &details->data[0]);
snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]);
catalogue->name = disk_name;
switch((details->data[6] >> 4)&3) {
switch((details->samples[0][6] >> 4)&3) {
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
@ -45,14 +46,14 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
for(size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->data[file_offset + 7] & 0x7f, &names->data[file_offset]);
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
new_file.name = name;
new_file.load_address = (uint32_t)(details->data[file_offset] | (details->data[file_offset+1] << 8) | ((details->data[file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->data[file_offset+2] | (details->data[file_offset+3] << 8) | ((details->data[file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->data[file_offset + 7] & 0x80);
new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
long data_length = static_cast<long>(details->data[file_offset+4] | (details->data[file_offset+5] << 8) | ((details->data[file_offset+6]&0x30) << 12));
int start_sector = details->data[file_offset+7] | ((details->data[file_offset+6]&0x03) << 8);
long data_length = static_cast<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->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
new_file.data.reserve(static_cast<size_t>(data_length));
if(start_sector < 2) continue;
@ -65,7 +66,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
new_file.data.insert(new_file.data.end(), next_sector->data.begin(), next_sector->data.begin() + length_from_sector);
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_front(new_file);
@ -85,7 +86,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::sh
for(uint8_t c = 2; c < 7; c++) {
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->data.begin(), sector->data.end());
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}
// Quick sanity checks.
@ -93,7 +94,7 @@ std::unique_ptr<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[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
switch(free_space_map_second_half->data[0xfd]) {
switch(free_space_map_second_half->samples[0][0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;

View File

@ -153,12 +153,12 @@ static void InspectCatalogue(
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr) {
if(boot_sector != nullptr && !boot_sector->samples.empty()) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.
bool matched = true;
for(size_t c = 1; c < 64; c++) {
if(boot_sector->data[c] != boot_sector->data[0]) {
if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) {
matched = false;
break;
}

View File

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

View File

@ -8,32 +8,181 @@
#include "CPCDSK.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../Encodings/MFM/SegmentParser.hpp"
#include "../../Track/TrackSerialiser.hpp"
#include <iostream>
using namespace Storage::Disk;
CPCDSK::CPCDSK(const char *file_name) :
Storage::FileHolder(file_name), is_extended_(false) {
if(!check_signature("MV - CPC", 8)) {
file_name_(file_name),
is_extended_(false) {
FileHolder file(file_name);
is_read_only_ = file.get_is_known_read_only();
if(!file.check_signature("MV - CPC")) {
is_extended_ = true;
fseek(file_, 0, SEEK_SET);
if(!check_signature("EXTENDED", 8))
file.seek(0, SEEK_SET);
if(!file.check_signature("EXTENDED"))
throw ErrorNotCPCDSK;
}
// Don't really care about about the creator; skip.
fseek(file_, 0x30, SEEK_SET);
head_position_count_ = fgetc(file_);
head_count_ = fgetc(file_);
file.seek(0x30, SEEK_SET);
head_position_count_ = file.get8();
head_count_ = file.get8();
// Used only for non-extended disks.
long size_of_a_track = 0;
// Used only for extended disks.
std::vector<size_t> track_sizes;
if(is_extended_) {
// Skip two unused bytes and grab the track size table.
fseek(file_, 2, SEEK_CUR);
file.seek(2, SEEK_CUR);
for(int c = 0; c < head_position_count_ * head_count_; c++) {
track_sizes_.push_back(static_cast<size_t>(fgetc(file_) << 8));
track_sizes.push_back(static_cast<size_t>(file.get8()) << 8);
}
} else {
size_of_a_track_ = fgetc16le();
size_of_a_track = file.get16le();
}
long file_offset = 0x100;
for(size_t c = 0; c < static_cast<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_;
}
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.
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.
long file_offset = 0x100;
if(is_extended_) {
// Tracks are a variable size in the original DSK file format.
// Return a nullptr if out of range or not provided.
if(chronological_track >= tracks_.size()) return nullptr;
Track *track = tracks_[chronological_track].get();
if(!track) return nullptr;
// Check that there is anything stored for this track.
if(!track_sizes_[chronological_track]) {
return nullptr;
}
// Sum the lengths of all tracks prior to the interesting one to get a file offset.
size_t t = 0;
while(t < chronological_track && t < track_sizes_.size()) {
file_offset += track_sizes_[t];
t++;
}
} else {
// Tracks are a fixed size in the original DSK file format.
file_offset += size_of_a_track_ * static_cast<long>(chronological_track);
std::vector<const Storage::Encodings::MFM::Sector *> sectors;
for(auto &sector : track->sectors) {
sectors.push_back(&sector);
}
// Find the track, and skip the unused part of track information.
fseek(file_, file_offset + 16, SEEK_SET);
// TODO: FM encoding, data rate?
return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte);
}
// Grab the track information.
fseek(file_, 5, SEEK_CUR); // skip track number, side number, sector size — each is given per sector
int number_of_sectors = fgetc(file_);
uint8_t gap3_length = static_cast<uint8_t>(fgetc(file_));
uint8_t filler_byte = static_cast<uint8_t>(fgetc(file_));
void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) {
// Patch changed tracks into the disk image.
for(auto &pair: tracks) {
// Assume MFM for now; with extensions DSK can contain FM tracks.
const bool is_double_density = true;
std::map<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
struct SectorInfo {
uint8_t track;
uint8_t side;
uint8_t sector;
uint8_t length;
uint8_t status1;
uint8_t status2;
size_t actual_length;
};
std::vector<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;
// Find slot for track, making it if neccessary.
size_t chronological_track = index_for_track(pair.first);
if(chronological_track >= tracks_.size()) {
tracks_.resize(chronological_track+1);
head_position_count_ = pair.first.position;
}
new_sector.data.resize(data_size);
fread(new_sector.data.data(), sizeof(uint8_t), data_size, file_);
if(sector_info.status2 & 0x20) {
// The CRC failed in the data field.
new_sector.has_data_crc_error = true;
} else {
if(sector_info.status1 & 0x20) {
// The CRC failed in the ID field.
new_sector.has_header_crc_error = true;
// Get the track, or create it if necessary.
Track *track = tracks_[chronological_track].get();
if(!track) {
track = new Track;
track->track = static_cast<uint8_t>(pair.first.position);
track->side = static_cast<uint8_t>(pair.first.head);
track->data_rate = Track::DataRate::SingleOrDoubleDensity;
track->data_encoding = Track::DataEncoding::MFM;
track->sector_length = 2;
track->gap3_length = 78;
track->filler_byte = 0xe5;
tracks_[chronological_track] = std::unique_ptr<Track>(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) {
// This sector is marked as deleted.
new_sector.is_deleted = true;
}
if(sector_info.status2 & 0x01) {
// Data field wasn't found.
new_sector.data.clear();
}
sectors.push_back(std::move(new_sector));
// Round upward and output.
track_size += (256 - (track_size & 255)) & 255;
output.put8(static_cast<uint8_t>(track_size >> 8));
}
// 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,
// and Simon Owen's weak/random sectors, subject to adding some logic to pick a potential
// FM/MFM encoding that can produce specified weak values.
// Output each track.
for(size_t index = 0; index < static_cast<size_t>(head_position_count_ * head_count_); ++index) {
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 "../../../FileHolder.hpp"
#include "../../Encodings/MFM/Sector.hpp"
#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.
*/
class CPCDSK: public DiskImage, public Storage::FileHolder {
class CPCDSK: public DiskImage {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@ -37,19 +38,40 @@ class CPCDSK: public DiskImage, public Storage::FileHolder {
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;
using DiskImage::get_is_read_only;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
bool get_is_read_only() 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:
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_position_count_;
bool is_extended_;
// Used only for non-extended disks.
long size_of_a_track_;
// Used only for extended disks.
std::vector<size_t> track_sizes_;
bool is_read_only_;
};
}

View File

@ -17,13 +17,13 @@
using namespace Storage::Disk;
D64::D64(const char *file_name) :
Storage::FileHolder(file_name) {
file_(file_name) {
// in D64, this is it for validation without imposing potential false-negative tests — check that
// the file size appears to be correct. Stone-age stuff.
if(file_stats_.st_size != 174848 && file_stats_.st_size != 196608)
if(file_.stats().st_size != 174848 && file_.stats().st_size != 196608)
throw ErrorNotD64;
number_of_tracks_ = (file_stats_.st_size == 174848) ? 35 : 40;
number_of_tracks_ = (file_.stats().st_size == 174848) ? 35 : 40;
// then, ostensibly, this is a valid file. Hmmm. Pick a disk ID as a function of the file_name,
// being the most stable thing available
@ -59,7 +59,7 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
}
// seek to start of data
fseek(file_, offset_to_track * 256, SEEK_SET);
file_.seek(offset_to_track * 256, SEEK_SET);
// build up a PCM sampling of the GCR version of this track
@ -117,7 +117,7 @@ std::shared_ptr<Track> D64::get_track_at_position(Track::Address address) {
// get the actual contents
uint8_t source_data[256];
fread(source_data, 1, 256, file_);
file_.read(source_data, sizeof(source_data));
// compute the latest checksum
checksum = 0;

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.
*/
class D64: public DiskImage, public Storage::FileHolder {
class D64: public DiskImage {
public:
/*!
Construct a @c D64 containing content from the file with name @c file_name.
@ -38,6 +38,7 @@ class D64: public DiskImage, public Storage::FileHolder {
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private:
Storage::FileHolder file_;
int number_of_tracks_;
uint16_t disk_id_;
};

View File

@ -15,17 +15,17 @@
using namespace Storage::Disk;
G64::G64(const char *file_name) :
Storage::FileHolder(file_name) {
file_(file_name) {
// read and check the file signature
if(!check_signature("GCR-1541", 8)) throw ErrorNotG64;
if(!file_.check_signature("GCR-1541")) throw ErrorNotG64;
// check the version number
int version = fgetc(file_);
int version = file_.get8();
if(version != 0) throw ErrorUnknownVersion;
// get the number of tracks and track size
number_of_tracks_ = static_cast<uint8_t>(fgetc(file_));
maximum_track_size_ = fgetc16le();
number_of_tracks_ = file_.get8();
maximum_track_size_ = file_.get16le();
}
int G64::get_head_position_count() {
@ -43,43 +43,43 @@ std::shared_ptr<Track> G64::get_track_at_position(Track::Address address) {
if(address.head >= 1) return resulting_track;
// seek to this track's entry in the track table
fseek(file_, static_cast<long>((address.position * 4) + 0xc), SEEK_SET);
file_.seek(static_cast<long>((address.position * 4) + 0xc), SEEK_SET);
// read the track offset
uint32_t track_offset;
track_offset = fgetc32le();
track_offset = file_.get32le();
// if the track offset is zero, this track doesn't exist, so...
if(!track_offset) return resulting_track;
// seek to the track start
fseek(file_, static_cast<long>(track_offset), SEEK_SET);
file_.seek(static_cast<long>(track_offset), SEEK_SET);
// get the real track length
uint16_t track_length;
track_length = fgetc16le();
track_length = file_.get16le();
// grab the byte contents of this track
std::vector<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
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
uint32_t speed_zone_offset;
speed_zone_offset = fgetc32le();
speed_zone_offset = file_.get32le();
// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
if(speed_zone_offset > 3) {
// seek to start of speed zone
fseek(file_, static_cast<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;
// read the speed zone bytes
uint8_t speed_zone_contents[speed_zone_length];
fread(speed_zone_contents, 1, speed_zone_length, file_);
file_.read(speed_zone_contents, speed_zone_length);
// divide track into appropriately timed PCMSegments
std::vector<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.
*/
class G64: public DiskImage, public Storage::FileHolder {
class G64: public DiskImage {
public:
/*!
Construct a @c G64 containing content from the file with name @c file_name.
@ -41,6 +41,7 @@ class G64: public DiskImage, public Storage::FileHolder {
using DiskImage::get_is_read_only;
private:
Storage::FileHolder file_;
uint8_t number_of_tracks_;
uint16_t maximum_track_size_;
};

View File

@ -15,15 +15,15 @@
using namespace Storage::Disk;
HFE::HFE(const char *file_name) :
Storage::FileHolder(file_name) {
if(!check_signature("HXCPICFE", 8)) throw ErrorNotHFE;
file_(file_name) {
if(!file_.check_signature("HXCPICFE")) throw ErrorNotHFE;
if(fgetc(file_)) throw ErrorNotHFE;
track_count_ = fgetc(file_);
head_count_ = fgetc(file_);
if(file_.get8()) throw ErrorNotHFE;
track_count_ = file_.get8();
head_count_ = file_.get8();
fseek(file_, 7, SEEK_CUR);
track_list_offset_ = static_cast<long>(fgetc16le() << 9);
file_.seek(7, SEEK_CUR);
track_list_offset_ = static_cast<long>(file_.get16le()) << 9;
}
HFE::~HFE() {
@ -47,13 +47,13 @@ int HFE::get_head_count() {
uint16_t HFE::seek_track(Track::Address address) {
// Get track position and length from the lookup table; data is then always interleaved
// based on an assumption of two heads.
fseek(file_, track_list_offset_ + address.position * 4, SEEK_SET);
file_.seek(track_list_offset_ + address.position * 4, SEEK_SET);
long track_offset = static_cast<long>(fgetc16le() << 9);
uint16_t track_length = fgetc16le();
long track_offset = static_cast<long>(file_.get16le()) << 9;
uint16_t track_length = file_.get16le();
fseek(file_, track_offset, SEEK_SET);
if(address.head) fseek(file_, 256, SEEK_CUR);
file_.seek(track_offset, SEEK_SET);
if(address.head) file_.seek(256, SEEK_CUR);
return track_length / 2;
}
@ -61,7 +61,7 @@ uint16_t HFE::seek_track(Track::Address address) {
std::shared_ptr<Track> HFE::get_track_at_position(Track::Address address) {
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);
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;
while(c < track_length) {
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;
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) {
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);
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;
while(c < data_length) {
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;
fseek(file_, 256, SEEK_CUR);
file_.seek(256, SEEK_CUR);
}
lock_guard.unlock();
}
}
bool HFE::get_is_read_only() {
return file_.get_is_known_read_only();
}

View File

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

View File

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

View File

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

View File

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

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
// and not ungainly large
if(file_stats_.st_size & 255) throw ErrorNotSSD;
if(file_stats_.st_size < 512) throw ErrorNotSSD;
if(file_stats_.st_size > 800*256) throw ErrorNotSSD;
if(file_.stats().st_size & 255) throw ErrorNotSSD;
if(file_.stats().st_size < 512) throw ErrorNotSSD;
if(file_.stats().st_size > 800*256) throw ErrorNotSSD;
// this has two heads if the suffix is .dsd, one if it's .ssd
head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
track_count_ = static_cast<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;
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++;
new_sector.size = size;
new_sector.data.insert(new_sector.data.begin(), source + source_pointer, source + source_pointer + byte_size);
new_sector.samples.emplace_back();
new_sector.samples[0].insert(new_sector.samples[0].begin(), source + source_pointer, source + source_pointer + byte_size);
source_pointer += byte_size;
}
@ -53,6 +54,7 @@ void Storage::Disk::decode_sectors(Track &track, uint8_t *const destination, uin
if(pair.second.address.sector > last_sector) continue;
if(pair.second.address.sector < first_sector) continue;
if(pair.second.size != sector_size) continue;
memcpy(&destination[pair.second.address.sector * byte_size], pair.second.data.data(), std::min(pair.second.data.size(), byte_size));
if(pair.second.samples.empty()) continue;
memcpy(&destination[pair.second.address.sector * byte_size], pair.second.samples[0].data(), std::min(pair.second.samples[0].size(), byte_size));
}
}

View File

@ -119,7 +119,7 @@ class FMEncoder: public Encoder {
template<class T> std::shared_ptr<Storage::Disk::Track>
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 pre_address_mark_bytes,
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);
// add sectors
for(const Sector &sector : sectors) {
for(const Sector *sector : sectors) {
// gap
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
// sector header
shifter.add_ID_address_mark();
shifter.add_byte(sector.address.track);
shifter.add_byte(sector.address.side);
shifter.add_byte(sector.address.sector);
shifter.add_byte(sector.size);
shifter.add_crc(sector.has_header_crc_error);
shifter.add_byte(sector->address.track);
shifter.add_byte(sector->address.side);
shifter.add_byte(sector->address.sector);
shifter.add_byte(sector->size);
shifter.add_crc(sector->has_header_crc_error);
// gap
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
// data, if attached
if(!sector.data.empty()) {
if(sector.is_deleted)
// TODO: allow for weak/fuzzy data.
if(!sector->samples.empty()) {
if(sector->is_deleted)
shifter.add_deleted_data_address_mark();
else
shifter.add_data_address_mark();
size_t c = 0;
size_t declared_length = static_cast<size_t>(128 << sector.size);
for(c = 0; c < sector.data.size() && c < declared_length; c++) {
shifter.add_byte(sector.data[c]);
size_t declared_length = static_cast<size_t>(128 << sector->size);
for(c = 0; c < sector->samples[0].size() && c < declared_length; c++) {
shifter.add_byte(sector->samples[0][c]);
}
for(; c < declared_length; c++) {
shifter.add_byte(0x00);
}
shifter.add_crc(sector.has_data_crc_error);
shifter.add_crc(sector->has_data_crc_error);
}
// gap
@ -202,7 +203,26 @@ void Encoder::add_crc(bool incorrectly) {
const size_t Storage::Encodings::MFM::DefaultSectorGapLength = std::numeric_limits<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) {
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>(
sectors,
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) {
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>(
sectors,
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.
*/
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.
@ -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.
*/
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 {
public:

View File

@ -34,7 +34,9 @@ struct Sector {
Address address;
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_header_crc_error = false;
@ -45,7 +47,7 @@ struct Sector {
Sector(const Sector &&rhs) noexcept :
address(rhs.address),
size(rhs.size),
data(std::move(rhs.data)),
samples(std::move(rhs.samples)),
has_data_crc_error(rhs.has_data_crc_error),
has_header_crc_error(rhs.has_header_crc_error),
is_deleted(rhs.is_deleted ){}

View File

@ -62,7 +62,8 @@ std::map<size_t, Storage::Encodings::MFM::Sector> Storage::Encodings::MFM::secto
shifter.set_should_obey_syncs(true);
break;
default:
new_sector->data.push_back(shifter.get_byte());
if(new_sector->samples.empty()) new_sector->samples.emplace_back();
new_sector->samples[0].push_back(shifter.get_byte());
++position;
if(position == size + 4) {
result.insert(std::make_pair(start_location, std::move(*new_sector)));

View File

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

View File

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

View File

@ -14,84 +14,119 @@
#include <cstdint>
#include <mutex>
#include <string>
#include <vector>
namespace Storage {
class FileHolder {
class FileHolder final {
public:
enum {
ErrorCantOpen = -1
};
enum class FileMode {
ReadWrite,
Read,
Rewrite
};
virtual ~FileHolder();
protected:
FileHolder(const std::string &file_name);
~FileHolder();
/*!
Reads @c length bytes from the file and compares them to the first
@c length bytes of @c signature. If @c length is 0, it is computed
as the length of @c signature up to and including the terminating null.
@returns @c true if the bytes read match the signature; @c false otherwise.
Attempts to open the file indicated by @c file_name. @c ideal_mode nominates how the file would
most ideally be opened. It can be one of:
ReadWrite attempt to open this file for random access reading and writing. If that fails,
will attept to open in Read mode.
Read attempts to open this file for reading only.
Rewrite opens the file for rewriting none of the original content is preserved; whatever
the caller outputs will replace the existing file.
@raises ErrorCantOpen if the file cannot be opened.
*/
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.
*/
uint32_t fgetc32le();
uint32_t get32le();
/*!
Performs @c fgetc three times on @c file_, casting each result to a @c uint32_t
and returning the three assembled in little endian order.
*/
uint32_t fgetc24le();
/*!
Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t
and returning the two assembled in little endian order.
*/
uint16_t fgetc16le();
/*!
Performs @c fgetc four times on @c file_, casting each result to a @c uint32_t
Performs @c get8 four times on @c file, casting each result to a @c uint32_t
and returning the four assembled in big endian order.
*/
uint32_t fgetc32be();
uint32_t get32be();
/*!
Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t
Performs @c get8 three times on @c file, casting each result to a @c uint32_t
and returning the three assembled in little endian order.
*/
uint32_t get24le();
/*!
Performs @c get8 three times on @c file, casting each result to a @c uint32_t
and returning the three assembled in big endian order.
*/
uint32_t get24be();
/*!
Performs @c get8 two times on @c file, casting each result to a @c uint32_t
and returning the two assembled in little endian order.
*/
uint16_t get16le();
/*!
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.
*/
uint16_t fgetc16be();
uint16_t get16be();
/*!
Determines and returns the file extension everything from the final character
back to the first dot. The string is converted to lowercase before being returned.
Writes @c value using two successive @c put8s, in big endian order.
*/
std::string extension();
void put16be(uint16_t value);
/*!
Ensures the file is at least @c length bytes long, appending 0s until it is
if necessary.
*/
void ensure_file_is_at_least_length(long length);
/*! Reads a single byte from @c file. */
uint8_t get8();
/*!
@returns @c true if this file is read-only; @c false otherwise.
*/
bool get_is_read_only();
/*! Writes a single byte from @c file. */
void put8(uint8_t value);
/*! Writes @c value a total of @c repeats times. */
void putn(size_t repeats, uint8_t value);
/*! Reads @c size bytes and returns them as a vector. */
std::vector<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 {
public:
BitStream(FILE *f, bool lsb_first) :
file_(f),
lsb_first_(lsb_first),
next_value_(0),
bits_remaining_(0) {}
uint8_t get_bits(int q) {
uint8_t result = 0;
while(q--) {
@ -101,6 +136,13 @@ class FileHolder {
}
private:
BitStream(FILE *file, bool lsb_first) :
file_(file),
lsb_first_(lsb_first),
next_value_(0),
bits_remaining_(0) {}
friend FileHolder;
FILE *file_;
bool lsb_first_;
uint8_t next_value_;
@ -126,14 +168,56 @@ class FileHolder {
return bit;
}
};
/*!
Obtains a BitStream for reading from the file from the current reading cursor.
*/
BitStream get_bitstream(bool lsb_first);
FILE *file_;
struct stat file_stats_;
std::mutex file_access_mutex_;
/*!
Reads @c length bytes from the file and compares them to the first
@c length bytes of @c signature. If @c length is 0, it is computed
as the length of @c signature not including the terminating null.
@returns @c true if the bytes read match the signature; @c false otherwise.
*/
bool check_signature(const char *signature, size_t length = 0);
/*!
Determines and returns the file extension everything from the final character
back to the first dot. The string is converted to lowercase before being returned.
*/
std::string extension();
/*!
Ensures the file is at least @c length bytes long, appending 0s until it is
if necessary.
*/
void ensure_is_at_least_length(long length);
/*!
@returns @c true if 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:
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;
CSW::CSW(const char *file_name) :
Storage::FileHolder(file_name),
file_(file_name),
source_data_pointer_(0) {
if(file_stats_.st_size < 0x20) throw ErrorNotCSW;
if(file_.stats().st_size < 0x20) throw ErrorNotCSW;
// Check signature.
char identifier[22];
char signature[] = "Compressed Square Wave";
fread(identifier, 1, strlen(signature), file_);
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotCSW;
if(!file_.check_signature("Compressed Square Wave")) {
throw ErrorNotCSW;
}
// Check terminating byte.
if(fgetc(file_) != 0x1a) throw ErrorNotCSW;
if(file_.get8() != 0x1a) throw ErrorNotCSW;
// Get version file number.
uint8_t major_version = static_cast<uint8_t>(fgetc(file_));
uint8_t minor_version = static_cast<uint8_t>(fgetc(file_));
uint8_t major_version = file_.get8();
uint8_t minor_version = file_.get8();
// Reject if this is an unknown version.
if(major_version > 2 || !major_version || minor_version > 1) throw ErrorNotCSW;
@ -34,28 +33,28 @@ CSW::CSW(const char *file_name) :
// The header now diverges based on version.
uint32_t number_of_waves = 0;
if(major_version == 1) {
pulse_.length.clock_rate = fgetc16le();
pulse_.length.clock_rate = file_.get16le();
if(fgetc(file_) != 1) throw ErrorNotCSW;
if(file_.get8() != 1) throw ErrorNotCSW;
compression_type_ = RLE;
pulse_.type = (fgetc(file_) & 1) ? Pulse::High : Pulse::Low;
pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low;
fseek(file_, 0x20, SEEK_SET);
file_.seek(0x20, SEEK_SET);
} else {
pulse_.length.clock_rate = fgetc32le();
number_of_waves = fgetc32le();
switch(fgetc(file_)) {
pulse_.length.clock_rate = file_.get32le();
number_of_waves = file_.get32le();
switch(file_.get8()) {
case 1: compression_type_ = RLE; break;
case 2: compression_type_ = ZRLE; break;
default: throw ErrorNotCSW;
}
pulse_.type = (fgetc(file_) & 1) ? Pulse::High : Pulse::Low;
uint8_t extension_length = static_cast<uint8_t>(fgetc(file_));
pulse_.type = (file_.get8() & 1) ? Pulse::High : Pulse::Low;
uint8_t extension_length = file_.get8();
if(file_stats_.st_size < 0x34 + extension_length) throw ErrorNotCSW;
fseek(file_, 0x34 + extension_length, SEEK_SET);
if(file_.stats().st_size < 0x34 + extension_length) throw ErrorNotCSW;
file_.seek(0x34 + extension_length, SEEK_SET);
}
if(compression_type_ == ZRLE) {
@ -65,9 +64,9 @@ CSW::CSW(const char *file_name) :
source_data_.resize(static_cast<size_t>(number_of_waves) * 5);
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);
fread(file_data.data(), sizeof(uint8_t), remaining_data, file_);
file_.read(file_data.data(), remaining_data);
// uncompress will tell how many compressed bytes there actually were, so use its
// modification of output_length to throw away all the memory that isn't actually
@ -76,7 +75,7 @@ CSW::CSW(const char *file_name) :
uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size());
source_data_.resize(static_cast<size_t>(output_length));
} else {
rle_start_ = ftell(file_);
rle_start_ = file_.tell();
}
invert_pulse();
@ -84,7 +83,7 @@ CSW::CSW(const char *file_name) :
uint8_t CSW::get_next_byte() {
switch(compression_type_) {
case RLE: return static_cast<uint8_t>(fgetc(file_));
case RLE: return file_.get8();
case ZRLE: {
if(source_data_pointer_ == source_data_.size()) return 0xff;
uint8_t result = source_data_[source_data_pointer_];
@ -96,7 +95,7 @@ uint8_t CSW::get_next_byte() {
uint32_t CSW::get_next_int32le() {
switch(compression_type_) {
case RLE: return fgetc32le();
case RLE: return file_.get32le();
case ZRLE: {
if(source_data_pointer_ > source_data_.size() - 4) return 0xffff;
uint32_t result = (uint32_t)(
@ -116,14 +115,14 @@ void CSW::invert_pulse() {
bool CSW::is_at_end() {
switch(compression_type_) {
case RLE: return (bool)feof(file_);
case RLE: return file_.eof();
case ZRLE: return source_data_pointer_ == source_data_.size();
}
}
void CSW::virtual_reset() {
switch(compression_type_) {
case RLE: fseek(file_, rle_start_, SEEK_SET); break;
case RLE: file_.seek(rle_start_, SEEK_SET); break;
case ZRLE: source_data_pointer_ = 0; break;
}
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ namespace Tape {
/*!
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling.
*/
class TZX: public PulseQueuedTape, public Storage::FileHolder {
class TZX: public PulseQueuedTape {
public:
/*!
Constructs a @c TZX containing content from the file with name @c file_name.
@ -32,6 +32,8 @@ class TZX: public PulseQueuedTape, public Storage::FileHolder {
};
private:
Storage::FileHolder file_;
void virtual_reset();
void get_next_pulses();

View File

@ -53,22 +53,21 @@ PRG::PRG(const char *file_name) :
file_phase_(FilePhaseLeadIn),
phase_offset_(0),
copy_mask_(0x80),
Storage::FileHolder(file_name)
file_(file_name)
{
// There's really no way to validate other than that if this file is larger than 64kb,
// of if load address + length > 65536 then it's broken.
if(file_stats_.st_size >= 65538 || file_stats_.st_size < 3)
if(file_.stats().st_size >= 65538 || file_.stats().st_size < 3)
throw ErrorBadFormat;
load_address_ = fgetc16le();
length_ = static_cast<uint16_t>(file_stats_.st_size - 2);
load_address_ = file_.get16le();
length_ = static_cast<uint16_t>(file_.stats().st_size - 2);
if (load_address_ + length_ >= 65536)
throw ErrorBadFormat;
}
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
{
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() {
// these are all microseconds per pole
static const unsigned int leader_zero_length = 179;
static const unsigned int zero_length = 169;
@ -81,8 +80,7 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
Tape::Pulse pulse;
pulse.length.clock_rate = 1000000;
pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low;
switch(output_token_)
{
switch(output_token_) {
case Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break;
@ -93,29 +91,25 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
return pulse;
}
void PRG::virtual_reset()
{
void PRG::virtual_reset() {
bit_phase_ = 3;
fseek(file_, 2, SEEK_SET);
file_.seek(2, SEEK_SET);
file_phase_ = FilePhaseLeadIn;
phase_offset_ = 0;
copy_mask_ = 0x80;
}
bool PRG::is_at_end()
{
bool PRG::is_at_end() {
return file_phase_ == FilePhaseAtEnd;
}
void PRG::get_next_output_token()
{
void PRG::get_next_output_token() {
static const int block_length = 192; // not counting the checksum
static const int countdown_bytes = 9;
static const int leadin_length = 20000;
static const int block_leadin_length = 5000;
if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd)
{
if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd) {
output_token_ = Silence;
if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData;
return;
@ -123,12 +117,10 @@ void PRG::get_next_output_token()
// the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000
// before doing whatever it should be doing
if(file_phase_ == FilePhaseLeadIn || phase_offset_ < block_leadin_length)
{
if(file_phase_ == FilePhaseLeadIn || phase_offset_ < block_leadin_length) {
output_token_ = Leader;
phase_offset_++;
if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length)
{
if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length) {
phase_offset_ = 0;
file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
}
@ -144,15 +136,13 @@ void PRG::get_next_output_token()
if(!bit_offset &&
(
(file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
feof(file_)
file_.eof()
)
)
{
) {
output_token_ = EndOfBlock;
phase_offset_ = 0;
switch(file_phase_)
{
switch(file_phase_) {
default: break;
case FilePhaseHeader:
copy_mask_ ^= 0x80;
@ -160,35 +150,25 @@ void PRG::get_next_output_token()
break;
case FilePhaseData:
copy_mask_ ^= 0x80;
fseek(file_, 2, SEEK_SET);
file_.seek(2, SEEK_SET);
if(copy_mask_) file_phase_ = FilePhaseAtEnd;
break;
}
return;
}
if(bit_offset == 0)
{
if(bit_offset == 0) {
// the first nine bytes are countdown; the high bit is set if this is a header
if(byte_offset < countdown_bytes)
{
if(byte_offset < countdown_bytes) {
output_byte_ = static_cast<uint8_t>(countdown_bytes - byte_offset) | copy_mask_;
}
else
{
if(file_phase_ == FilePhaseHeader)
{
if(byte_offset == countdown_bytes + block_length)
{
} else {
if(file_phase_ == FilePhaseHeader) {
if(byte_offset == countdown_bytes + block_length) {
output_byte_ = check_digit_;
}
else
{
} else {
if(byte_offset == countdown_bytes) check_digit_ = 0;
if(file_phase_ == FilePhaseHeader)
{
switch(byte_offset - countdown_bytes)
{
if(file_phase_ == FilePhaseHeader) {
switch(byte_offset - countdown_bytes) {
case 0: output_byte_ = 0x03; break;
case 1: output_byte_ = load_address_ & 0xff; break;
case 2: output_byte_ = (load_address_ >> 8)&0xff; break;
@ -204,12 +184,9 @@ void PRG::get_next_output_token()
}
}
}
}
else
{
output_byte_ = static_cast<uint8_t>(fgetc(file_));
if(feof(file_))
{
} else {
output_byte_ = file_.get8();
if(file_.eof()) {
output_byte_ = check_digit_;
}
}
@ -218,16 +195,14 @@ void PRG::get_next_output_token()
}
}
switch(bit_offset)
{
switch(bit_offset) {
case 0:
output_token_ = WordMarker;
break;
default: // i.e. 18
output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero;
break;
case 9:
{
case 9: {
uint8_t parity = output_byte_;
parity ^= (parity >> 4);
parity ^= (parity >> 2);

View File

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

View File

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

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.
*/
class ZX80O81P: public Tape, public Storage::FileHolder, public TargetPlatform::TypeDistinguisher {
class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher {
public:
/*!
Constructs a @c ZX80O containing content from the file with name @c file_name.