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:
commit
fd10c42433
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 §or = track->sectors.back();
|
||||
|
||||
// Track, side, sector, size and two FDC8272-esque status bytes are stored
|
||||
// per sector, in both regular and extended DSK files.
|
||||
sector.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 §or: 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 §or : track->sectors) {
|
||||
sectors.push_back(§or);
|
||||
}
|
||||
|
||||
// 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 §or_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 §or = track->sectors.back();
|
||||
|
||||
sector.address = source_sector.second.address;
|
||||
sector.size = source_sector.second.size;
|
||||
sector.has_data_crc_error = source_sector.second.has_data_crc_error;
|
||||
sector.has_header_crc_error = source_sector.second.has_header_crc_error;
|
||||
sector.is_deleted = source_sector.second.is_deleted;
|
||||
sector.samples = std::move(source_sector.second.samples);
|
||||
|
||||
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 §or: 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 §or: track->sectors) {
|
||||
output.put8(sector.address.track);
|
||||
output.put8(sector.address.side);
|
||||
output.put8(sector.address.sector);
|
||||
output.put8(sector.size);
|
||||
output.put8(sector.fdc_status1);
|
||||
output.put8(sector.fdc_status2);
|
||||
|
||||
size_t data_size = 0;
|
||||
for(auto &sample: sector.samples) {
|
||||
data_size += sample.size();
|
||||
}
|
||||
output.put16le(static_cast<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 §or: track->sectors) {
|
||||
for(auto &sample: sector.samples) {
|
||||
output.write(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next 256-byte boundary.
|
||||
distance = (256 - output.tell()&255)&255;
|
||||
output.putn(static_cast<size_t>(distance), 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool CPCDSK::get_is_read_only() {
|
||||
return is_read_only_;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class FMEncoder: public Encoder {
|
||||
|
||||
template<class T> std::shared_ptr<Storage::Disk::Track>
|
||||
GetTrackWithSectors(
|
||||
const std::vector<Sector> §ors,
|
||||
const std::vector<const Sector *> §ors,
|
||||
size_t post_index_address_mark_bytes, uint8_t post_index_address_mark_value,
|
||||
size_t pre_address_mark_bytes,
|
||||
size_t post_address_mark_bytes, uint8_t post_address_mark_value,
|
||||
@ -137,38 +137,39 @@ template<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 §or : sectors) {
|
||||
for(const Sector *sector : sectors) {
|
||||
// gap
|
||||
for(size_t c = 0; c < pre_address_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// sector header
|
||||
shifter.add_ID_address_mark();
|
||||
shifter.add_byte(sector.address.track);
|
||||
shifter.add_byte(sector.address.side);
|
||||
shifter.add_byte(sector.address.sector);
|
||||
shifter.add_byte(sector.size);
|
||||
shifter.add_crc(sector.has_header_crc_error);
|
||||
shifter.add_byte(sector->address.track);
|
||||
shifter.add_byte(sector->address.side);
|
||||
shifter.add_byte(sector->address.sector);
|
||||
shifter.add_byte(sector->size);
|
||||
shifter.add_crc(sector->has_header_crc_error);
|
||||
|
||||
// gap
|
||||
for(size_t c = 0; c < post_address_mark_bytes; c++) shifter.add_byte(post_address_mark_value);
|
||||
for(size_t c = 0; c < pre_data_mark_bytes; c++) shifter.add_byte(0x00);
|
||||
|
||||
// data, if attached
|
||||
if(!sector.data.empty()) {
|
||||
if(sector.is_deleted)
|
||||
// TODO: allow for weak/fuzzy data.
|
||||
if(!sector->samples.empty()) {
|
||||
if(sector->is_deleted)
|
||||
shifter.add_deleted_data_address_mark();
|
||||
else
|
||||
shifter.add_data_address_mark();
|
||||
|
||||
size_t c = 0;
|
||||
size_t declared_length = static_cast<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> §ors) {
|
||||
std::vector<const Sector *> pointers;
|
||||
for(const Sector §or: sectors) {
|
||||
pointers.push_back(§or);
|
||||
}
|
||||
return pointers;
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Disk::Track> Storage::Encodings::MFM::GetFMTrackWithSectors(const std::vector<Sector> §ors, 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 *> §ors, 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> §ors, 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 *> §ors, size_t sector_gap_length, uint8_t sector_gap_filler_byte) {
|
||||
return GetTrackWithSectors<MFMEncoder>(
|
||||
sectors,
|
||||
50, 0x4e,
|
||||
|
@ -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> §ors, 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 *> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
/*!
|
||||
Converts a vector of sectors into a properly-encoded FM track.
|
||||
@ -37,6 +38,7 @@ std::shared_ptr<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> §ors, 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 *> §ors, size_t sector_gap_length = DefaultSectorGapLength, uint8_t sector_gap_filler_byte = 0x4e);
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
|
@ -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 ){}
|
||||
|
@ -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)));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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. 1–8
|
||||
output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero;
|
||||
break;
|
||||
case 9:
|
||||
{
|
||||
case 9: {
|
||||
uint8_t parity = output_byte_;
|
||||
parity ^= (parity >> 4);
|
||||
parity ^= (parity >> 2);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user