1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Switches FileHolder to have a usage much closer to FILE *.

Thereby opens a route for file format implementations such as that appearing for CPC DSK that create an in-memory copy and perform a full rewrite.
This commit is contained in:
Thomas Harte 2017-11-02 22:32:00 -04:00
parent f807a6b608
commit e384c50580
28 changed files with 597 additions and 445 deletions

View File

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

View File

@ -8,23 +8,31 @@
#include "CPCDSK.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../Encodings/MFM/SegmentParser.hpp"
#include "../../Track/TrackSerialiser.hpp"
#include <iostream>
using namespace Storage::Disk;
CPCDSK::CPCDSK(const char *file_name) :
Storage::FileHolder(file_name), is_extended_(false) {
if(!check_signature("MV - CPC", 8)) {
is_extended_(false) {
FileHolder file(file_name);
is_read_only_ = file.get_is_known_read_only();
if(!file.check_signature("MV - CPC")) {
is_extended_ = true;
fseek(file_, 0, SEEK_SET);
if(!check_signature("EXTENDED", 8))
file.seek(0, SEEK_SET);
if(!file.check_signature("EXTENDED"))
throw ErrorNotCPCDSK;
}
// Don't really care about about the creator; skip.
fseek(file_, 0x30, SEEK_SET);
head_position_count_ = fgetc(file_);
head_count_ = fgetc(file_);
file.seek(0x30, SEEK_SET);
head_position_count_ = file.get8();
head_count_ = file.get8();
// Used only for non-extended disks.
long size_of_a_track = 0;
@ -34,37 +42,37 @@ CPCDSK::CPCDSK(const char *file_name) :
if(is_extended_) {
// Skip two unused bytes and grab the track size table.
fseek(file_, 2, SEEK_CUR);
file.seek(2, SEEK_CUR);
for(int c = 0; c < head_position_count_ * head_count_; c++) {
track_sizes.push_back(static_cast<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.
fseek(file_, file_offset + 16, SEEK_SET);
file.seek(file_offset + 16, SEEK_SET);
tracks_.emplace_back(new Track);
Track *track = tracks_.back().get();
// Track and side are stored, being a byte each.
track->track = static_cast<uint8_t>(fgetc(file_));
track->side = static_cast<uint8_t>(fgetc(file_));
track->track = file.get8();
track->side = file.get8();
// If this is an extended disk image then John Elliott's extension provides some greater
// data rate and encoding context. Otherwise the next two bytes have no defined meaning.
if(is_extended_) {
switch(fgetc(file_)) {
switch(file.get8()) {
default: track->data_rate = Track::DataRate::Unknown; break;
case 1: track->data_rate = Track::DataRate::SingleOrDoubleDensity; break;
case 2: track->data_rate = Track::DataRate::HighDensity; break;
case 3: track->data_rate = Track::DataRate::ExtendedDensity; break;
}
switch(fgetc(file_)) {
switch(file.get8()) {
default: track->data_encoding = Track::DataEncoding::Unknown; break;
case 1: track->data_encoding = Track::DataEncoding::FM; break;
case 2: track->data_encoding = Track::Track::DataEncoding::MFM; break;
@ -72,15 +80,15 @@ CPCDSK::CPCDSK(const char *file_name) :
} else {
track->data_rate = Track::DataRate::Unknown;
track->data_encoding = Track::DataEncoding::Unknown;
fseek(file_, 2, SEEK_CUR);
file.seek(2, SEEK_CUR);
}
// Sector size, number of sectors, gap 3 length and the filler byte are then common
// between both variants of DSK.
track->sector_length = static_cast<uint8_t>(fgetc(file_));
size_t number_of_sectors = static_cast<size_t>(fgetc(file_));
track->gap3_length = static_cast<uint8_t>(fgetc(file_));
track->filler_byte = static_cast<uint8_t>(fgetc(file_));
track->sector_length = file.get8();
size_t number_of_sectors = file.get8();
track->gap3_length = file.get8();
track->filler_byte = file.get8();
// Sector information begins immediately after the track information table.
while(number_of_sectors--) {
@ -89,12 +97,12 @@ CPCDSK::CPCDSK(const char *file_name) :
// Track, side, sector, size and two FDC8272-esque status bytes are stored
// per sector, in both regular and extended DSK files.
sector.address.track = static_cast<uint8_t>(fgetc(file_));
sector.address.side = static_cast<uint8_t>(fgetc(file_));
sector.address.sector = static_cast<uint8_t>(fgetc(file_));
sector.size = static_cast<uint8_t>(fgetc(file_));
sector.fdc_status1 = static_cast<uint8_t>(fgetc(file_));
sector.fdc_status2 = static_cast<uint8_t>(fgetc(file_));
sector.address.track = file.get8();
sector.address.side = file.get8();
sector.address.sector = file.get8();
sector.size = file.get8();
sector.fdc_status1 = file.get8();
sector.fdc_status2 = file.get8();
if(sector.fdc_status2 & 0x20) {
// The CRC failed in the data field.
@ -131,7 +139,7 @@ CPCDSK::CPCDSK(const char *file_name) :
// sector was weak or fuzzy and that multiple samplings are provided. If the greater size
// is not an exact multiple then my reading of the documentation is that this is an invalid
// disk image.
size_t declared_data_size = fgetc16le();
size_t declared_data_size = file.get16le();
if(declared_data_size != stored_data_size) {
if(declared_data_size > data_size) {
number_of_samplings = declared_data_size / data_size;
@ -145,7 +153,7 @@ CPCDSK::CPCDSK(const char *file_name) :
// In a regular DSK, these two bytes are unused, and a special case is applied that ostensibly 8kb
// sectors are abbreviated to only 0x1800 bytes.
if(data_size == 0x2000) stored_data_size = 0x1800;
fseek(file_, 2, SEEK_CUR);
file.seek(2, SEEK_CUR);
}
// As per the weak/fuzzy sector extension, multiple samplings may be stored here.
@ -158,10 +166,10 @@ CPCDSK::CPCDSK(const char *file_name) :
}
// Sector contents are at offset 0x100 into the track.
fseek(file_, file_offset + 0x100, SEEK_SET);
file.seek(file_offset + 0x100, SEEK_SET);
for(auto &sector: track->sectors) {
for(auto &data : sector.samples) {
fread(data.data(), 1, data.size(), file_);
file.read(data.data(), data.size());
}
}
} else {
@ -185,9 +193,13 @@ int CPCDSK::get_head_count() {
return head_count_;
}
size_t CPCDSK::index_for_track(::Storage::Disk::Track::Address address) {
return static_cast<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);
// Return a nullptr if out of range or not provided.
if(chronological_track >= tracks_.size()) return nullptr;
@ -203,3 +215,56 @@ std::shared_ptr<Track> CPCDSK::get_track_at_position(::Storage::Disk::Track::Add
// TODO: FM encoding, data rate?
return Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors, track->gap3_length, track->filler_byte);
}
void CPCDSK::set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) {
// Patch changed tracks into the disk image.
for(auto &pair: tracks) {
// Assume MFM for now; with extensions DSK can contain FM tracks.
const bool is_double_density = true;
std::map<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);
// Find slot for track, making it if neccessary.
size_t chronological_track = index_for_track(pair.first);
if(chronological_track >= tracks_.size()) {
tracks_.resize(chronological_track+1);
}
// Get the track, or create it if necessary.
Track *track = tracks_[chronological_track].get();
if(!track) {
track = new Track;
track->track = static_cast<uint8_t>(pair.first.position);
track->side = static_cast<uint8_t>(pair.first.head);
track->data_rate = Track::DataRate::SingleOrDoubleDensity;
track->data_encoding = Track::DataEncoding::MFM;
track->sector_length = 2;
track->gap3_length = 78;
track->filler_byte = 0xe5;
tracks_[chronological_track] = std::unique_ptr<Track>(track);
}
// Store sectors.
track->sectors.clear();
for(auto &source_sector: sectors) {
track->sectors.emplace_back();
Track::Sector &sector = track->sectors.back();
sector.address = source_sector.second.address;
sector.size = source_sector.second.size;
sector.has_data_crc_error = source_sector.second.has_data_crc_error;
sector.has_header_crc_error = source_sector.second.has_header_crc_error;
sector.is_deleted = source_sector.second.is_deleted;
sector.samples = std::move(source_sector.second.samples);
}
}
// Rewrite the entire disk image, in extended form.
}
bool CPCDSK::get_is_read_only() {
return is_read_only_;
}

View File

@ -21,7 +21,7 @@ namespace Disk {
/*!
Provies a @c Disk containing an Amstrad CPC-stype disk image some arrangement of sectors with status bits.
*/
class CPCDSK: public DiskImage, public Storage::FileHolder {
class CPCDSK: public DiskImage {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@ -38,7 +38,9 @@ class CPCDSK: public DiskImage, public Storage::FileHolder {
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;
using DiskImage::get_is_read_only;
bool get_is_read_only() override;
void set_tracks(const std::map<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:
@ -63,10 +65,12 @@ class CPCDSK: public DiskImage, public Storage::FileHolder {
std::vector<Sector> sectors;
};
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_;
bool is_read_only_;
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ namespace Disk {
/*!
Provies a @c Disk containing an Oric MFM-stype disk image a stream of the MFM data bits with clocks omitted.
*/
class OricMFMDSK: public DiskImage, public Storage::FileHolder {
class OricMFMDSK: public DiskImage {
public:
/*!
Construct an @c OricMFMDSK containing content from the file with name @c file_name.
@ -34,12 +34,13 @@ class OricMFMDSK: public DiskImage, public Storage::FileHolder {
// implemented to satisfy @c Disk
int get_head_position_count() override;
int get_head_count() override;
using Storage::FileHolder::get_is_read_only;
bool get_is_read_only() override;
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
private:
std::mutex file_access_mutex_;
Storage::FileHolder file_;
long get_file_offset_for_position(Track::Address address);
uint32_t head_count_;

View File

@ -21,13 +21,13 @@ SSD::SSD(const char *file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats_.st_size & 255) throw ErrorNotSSD;
if(file_stats_.st_size < 512) throw ErrorNotSSD;
if(file_stats_.st_size > 800*256) throw ErrorNotSSD;
if(file_.stats().st_size & 255) throw ErrorNotSSD;
if(file_.stats().st_size < 512) throw ErrorNotSSD;
if(file_.stats().st_size > 800*256) throw ErrorNotSSD;
// this has two heads if the suffix is .dsd, one if it's .ssd
head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
track_count_ = static_cast<int>(file_stats_.st_size / (256 * 10));
track_count_ = static_cast<int>(file_.stats().st_size / (256 * 10));
if(track_count_ < 40) track_count_ = 40;
else if(track_count_ < 80) track_count_ = 80;

View File

@ -16,19 +16,122 @@ FileHolder::~FileHolder() {
if(file_) fclose(file_);
}
FileHolder::FileHolder(const std::string &file_name) : file_(nullptr), name_(file_name) {
FileHolder::FileHolder(const std::string &file_name, FileMode ideal_mode)
: name_(file_name) {
stat(file_name.c_str(), &file_stats_);
is_read_only_ = false;
file_ = fopen(file_name.c_str(), "rb+");
if(!file_) {
is_read_only_ = true;
file_ = fopen(file_name.c_str(), "rb");
switch(ideal_mode) {
case FileMode::ReadWrite:
file_ = fopen(file_name.c_str(), "rb+");
if(file_) break;
is_read_only_ = true;
// deliberate fallthrough...
case FileMode::Read:
file_ = fopen(file_name.c_str(), "rb");
break;
case FileMode::Rewrite:
file_ = fopen(file_name.c_str(), "w");
break;
}
if(!file_) throw ErrorCantOpen;
}
uint32_t FileHolder::get32le() {
uint32_t result = (uint32_t)fgetc(file_);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)(fgetc(file_) << 16);
result |= (uint32_t)(fgetc(file_) << 24);
return result;
}
uint32_t FileHolder::get32be() {
uint32_t result = (uint32_t)(fgetc(file_) << 24);
result |= (uint32_t)(fgetc(file_) << 16);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)fgetc(file_);
return result;
}
uint32_t FileHolder::get24le() {
uint32_t result = (uint32_t)fgetc(file_);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)(fgetc(file_) << 16);
return result;
}
uint32_t FileHolder::get24be() {
uint32_t result = (uint32_t)(fgetc(file_) << 16);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)fgetc(file_);
return result;
}
uint16_t FileHolder::get16le() {
uint16_t result = static_cast<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_));
}
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];
@ -37,67 +140,35 @@ bool FileHolder::check_signature(const char *signature, size_t length) {
return true;
}
uint32_t FileHolder::fgetc32le() {
uint32_t result = (uint32_t)fgetc(file_);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)(fgetc(file_) << 16);
result |= (uint32_t)(fgetc(file_) << 24);
std::string FileHolder::extension() {
size_t pointer = name_.size() - 1;
while(pointer > 0 && name_[pointer] != '.') pointer--;
if(name_[pointer] == '.') pointer++;
return result;
std::string extension = name_.substr(pointer);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
uint32_t FileHolder::fgetc24le() {
uint32_t result = (uint32_t)fgetc(file_);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)(fgetc(file_) << 16);
return result;
void FileHolder::ensure_is_at_least_length(long length) {
fseek(file_, 0, SEEK_END);
long bytes_to_write = length - ftell(file_);
if(bytes_to_write > 0) {
uint8_t *empty = new uint8_t[static_cast<size_t>(bytes_to_write)];
memset(empty, 0, static_cast<size_t>(bytes_to_write));
fwrite(empty, sizeof(uint8_t), static_cast<size_t>(bytes_to_write), file_);
delete[] empty;
}
}
uint16_t FileHolder::fgetc16le() {
uint16_t result = static_cast<uint16_t>(fgetc(file_));
result |= static_cast<uint16_t>(fgetc(file_) << 8);
return result;
}
uint32_t FileHolder::fgetc32be() {
uint32_t result = (uint32_t)(fgetc(file_) << 24);
result |= (uint32_t)(fgetc(file_) << 16);
result |= (uint32_t)(fgetc(file_) << 8);
result |= (uint32_t)fgetc(file_);
return result;
}
uint16_t FileHolder::fgetc16be() {
uint16_t result = static_cast<uint16_t>(fgetc(file_) << 8);
result |= static_cast<uint16_t>(fgetc(file_));
return result;
}
bool FileHolder::get_is_read_only() {
bool FileHolder::get_is_known_read_only() {
return is_read_only_;
}
void FileHolder::ensure_file_is_at_least_length(long length) {
fseek(file_, 0, SEEK_END);
long bytes_to_write = length - ftell(file_);
if(bytes_to_write > 0) {
uint8_t *empty = new uint8_t[static_cast<size_t>(bytes_to_write)];
memset(empty, 0, static_cast<size_t>(bytes_to_write));
fwrite(empty, sizeof(uint8_t), static_cast<size_t>(bytes_to_write), file_);
delete[] empty;
}
struct stat &FileHolder::stats() {
return file_stats_;
}
std::string FileHolder::extension() {
size_t pointer = name_.size() - 1;
while(pointer > 0 && name_[pointer] != '.') pointer--;
if(name_[pointer] == '.') pointer++;
std::string extension = name_.substr(pointer);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
std::mutex &FileHolder::get_file_access_mutex() {
return file_access_mutex_;
}

View File

@ -14,84 +14,76 @@
#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();
FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);
/*!
Reads @c length bytes from the file and compares them to the first
@c length bytes of @c signature. If @c length is 0, it is computed
as the length of @c signature up to and including the terminating null.
@returns @c true if the bytes read match the signature; @c false otherwise.
*/
bool check_signature(const char *signature, size_t length);
/*!
Performs @c fgetc four times on @c file_, casting each result to a @c uint32_t
Performs @c get8 four times on @c file, casting each result to a @c uint32_t
and returning the four assembled in little endian order.
*/
uint32_t fgetc32le();
uint32_t get32le();
/*!
Performs @c fgetc three times on @c file_, casting each result to a @c uint32_t
and returning the three assembled in little endian order.
*/
uint32_t fgetc24le();
/*!
Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t
and returning the two assembled in little endian order.
*/
uint16_t fgetc16le();
/*!
Performs @c fgetc four times on @c file_, casting each result to a @c uint32_t
Performs @c get8 four times on @c file, casting each result to a @c uint32_t
and returning the four assembled in big endian order.
*/
uint32_t fgetc32be();
uint32_t get32be();
/*!
Performs @c fgetc two times on @c file_, casting each result to a @c uint32_t
Performs @c get8 three times on @c file, casting each result to a @c uint32_t
and returning the three assembled in little endian order.
*/
uint32_t get24le();
/*!
Performs @c get8 three times on @c file, casting each result to a @c uint32_t
and returning the three assembled in big endian order.
*/
uint32_t get24be();
/*!
Performs @c get8 two times on @c file, casting each result to a @c uint32_t
and returning the two assembled in little endian order.
*/
uint16_t get16le();
/*!
Performs @c get8 two times on @c file, casting each result to a @c uint32_t
and returning the two assembled in big endian order.
*/
uint16_t fgetc16be();
uint16_t get16be();
/*!
Determines and returns the file extension everything from the final character
back to the first dot. The string is converted to lowercase before being returned.
*/
std::string extension();
/*!
Ensures the file is at least @c length bytes long, appending 0s until it is
if necessary.
*/
void ensure_file_is_at_least_length(long length);
/*!
@returns @c true if this file is read-only; @c false otherwise.
*/
bool get_is_read_only();
/*! Reads a single byte from @c file */
uint8_t get8();
std::vector<uint8_t> read(size_t size);
size_t read(uint8_t *buffer, size_t size);
size_t write(const std::vector<uint8_t> &);
size_t write(const uint8_t *buffer, size_t size);
void seek(long offset, int whence);
long tell();
void flush();
bool eof();
class BitStream {
public:
BitStream(FILE *f, bool lsb_first) :
file_(f),
lsb_first_(lsb_first),
next_value_(0),
bits_remaining_(0) {}
uint8_t get_bits(int q) {
uint8_t result = 0;
while(q--) {
@ -101,6 +93,13 @@ class FileHolder {
}
private:
BitStream(FILE *file, bool lsb_first) :
file_(file),
lsb_first_(lsb_first),
next_value_(0),
bits_remaining_(0) {}
friend FileHolder;
FILE *file_;
bool lsb_first_;
uint8_t next_value_;
@ -126,14 +125,53 @@ class FileHolder {
return bit;
}
};
/*!
Obtains a BitStream for reading from the file from the current reading cursor.
*/
BitStream get_bitstream(bool lsb_first);
FILE *file_;
struct stat file_stats_;
std::mutex file_access_mutex_;
/*!
Reads @c length bytes from the file and compares them to the first
@c length bytes of @c signature. If @c length is 0, it is computed
as the length of @c signature not including the terminating null.
@returns @c true if the bytes read match the signature; @c false otherwise.
*/
bool check_signature(const char *signature, size_t length = 0);
/*!
Determines and returns the file extension everything from the final character
back to the first dot. The string is converted to lowercase before being returned.
*/
std::string extension();
/*!
Ensures the file is at least @c length bytes long, appending 0s until it is
if necessary.
*/
void ensure_is_at_least_length(long length);
/*!
@returns @c true if this file is read-only; @c false otherwise.
*/
bool get_is_known_read_only();
/*!
@returns the stat struct describing this file.
*/
struct stat &stats();
std::mutex &get_file_access_mutex();
const std::string name_;
private:
bool is_read_only_;
FILE *file_ = nullptr;
const std::string name_;
struct stat file_stats_;
bool is_read_only_ = false;
std::mutex file_access_mutex_;
};
}

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ namespace Tape {
/*!
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings.
*/
class CommodoreTAP: public Tape, public Storage::FileHolder {
class CommodoreTAP: public Tape {
public:
/*!
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
@ -36,6 +36,7 @@ class CommodoreTAP: public Tape, public Storage::FileHolder {
bool is_at_end();
private:
Storage::FileHolder file_;
void virtual_reset();
Pulse virtual_get_next_pulse();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ namespace Tape {
/*!
Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture.
*/
class ZX80O81P: public Tape, public Storage::FileHolder, public TargetPlatform::TypeDistinguisher {
class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher {
public:
/*!
Constructs a @c ZX80O containing content from the file with name @c file_name.