1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #79 from TomHarte/OricDSK

Creates a base class for file format implementations which seeks to collect boilerplate stuff
This commit is contained in:
Thomas Harte 2016-11-21 20:51:48 +08:00 committed by GitHub
commit fc1afe9351
23 changed files with 608 additions and 430 deletions

View File

@ -53,6 +53,8 @@
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A12551DD55862007A2231 /* Disassembler6502.cpp */; };
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */; };
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
@ -499,6 +501,10 @@
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
4B5A12551DD55862007A2231 /* Disassembler6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disassembler6502.cpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.cpp; sourceTree = "<group>"; };
4B5A12561DD55862007A2231 /* Disassembler6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disassembler6502.hpp; path = ../../StaticAnalyser/Disassembler/Disassembler6502.hpp; sourceTree = "<group>"; };
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricMFMDSK.cpp; sourceTree = "<group>"; };
4B5FADBC1DE31D1500AEC565 /* OricMFMDSK.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricMFMDSK.hpp; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
@ -1190,6 +1196,8 @@
4B8805F81DCFF6CD003085B1 /* Data */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */,
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */,
);
name = Storage;
path = ../../Storage;
@ -1275,14 +1283,16 @@
4BAB62B21D327F7E00DF5BA0 /* Formats */ = {
isa = PBXGroup;
children = (
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
4B4C836E1D4F623200CD541F /* D64.cpp */,
4B4C836F1D4F623200CD541F /* D64.hpp */,
4BF829611D8F536B001BAE39 /* SSD.cpp */,
4BF829621D8F536B001BAE39 /* SSD.hpp */,
4BD69F921D98760000243FE1 /* AcornADF.cpp */,
4BD69F931D98760000243FE1 /* AcornADF.hpp */,
4B4C836E1D4F623200CD541F /* D64.cpp */,
4B4C836F1D4F623200CD541F /* D64.hpp */,
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
4B5FADBB1DE31D1500AEC565 /* OricMFMDSK.cpp */,
4B5FADBC1DE31D1500AEC565 /* OricMFMDSK.hpp */,
4BF829611D8F536B001BAE39 /* SSD.cpp */,
4BF829621D8F536B001BAE39 /* SSD.hpp */,
);
path = Formats;
sourceTree = "<group>";
@ -2328,11 +2338,13 @@
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,

View File

@ -49,7 +49,7 @@
<string>rom</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC ROM Image</string>
<string>ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
@ -146,6 +146,18 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dsk</string>
</array>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -24,6 +24,7 @@
#include "../Storage/Disk/Formats/AcornADF.hpp"
#include "../Storage/Disk/Formats/D64.hpp"
#include "../Storage/Disk/Formats/G64.hpp"
#include "../Storage/Disk/Formats/OricMFMDSK.hpp"
#include "../Storage/Disk/Formats/SSD.hpp"
// Tapes
@ -88,6 +89,7 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
Format("dsk", disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
// PRG

View File

@ -18,35 +18,25 @@ namespace {
using namespace Storage::Disk;
AcornADF::AcornADF(const char *file_name) : _file(nullptr)
AcornADF::AcornADF(const char *file_name) :
Storage::FileHolder(file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
if(file_stats.st_size % bytes_per_sector) throw ErrorNotAcornADF;
if(file_stats.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorCantOpen;
if(file_stats_.st_size % bytes_per_sector) throw ErrorNotAcornADF;
if(file_stats_.st_size < 7 * bytes_per_sector) throw ErrorNotAcornADF;
// check that the initial directory's 'Hugo's are present
fseek(_file, 513, SEEK_SET);
fseek(file_, 513, SEEK_SET);
uint8_t bytes[4];
fread(bytes, 1, 4, _file);
fread(bytes, 1, 4, file_);
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);
fseek(file_, 0x6fb, SEEK_SET);
fread(bytes, 1, 4, file_);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
}
AcornADF::~AcornADF()
{
if(_file) fclose(_file);
}
unsigned int AcornADF::get_head_position_count()
{
return 80;
@ -63,7 +53,7 @@ std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsign
if(head >= 2) return track;
long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_track;
fseek(_file, file_offset, SEEK_SET);
fseek(file_, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < sectors_per_track; sector++)
@ -74,8 +64,8 @@ std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsign
new_sector.sector = (uint8_t)sector;
new_sector.data.resize(bytes_per_sector);
fread(&new_sector.data[0], 1, bytes_per_sector, _file);
if(feof(_file))
fread(&new_sector.data[0], 1, bytes_per_sector, file_);
if(feof(file_))
break;
sectors.push_back(std::move(new_sector));

View File

@ -10,6 +10,7 @@
#define AcornADF_hpp
#include "../Disk.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Disk {
@ -17,7 +18,7 @@ namespace Disk {
/*!
Provies a @c Disk containing an ADF disk image a decoded sector dump of an Acorn ADFS disk.
*/
class AcornADF: public Disk {
class AcornADF: public Disk, public Storage::FileHolder {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@ -26,10 +27,8 @@ class AcornADF: public Disk {
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
*/
AcornADF(const char *file_name);
~AcornADF();
enum {
ErrorCantOpen,
ErrorNotAcornADF,
};
@ -37,9 +36,6 @@ class AcornADF: public Disk {
unsigned int get_head_position_count();
unsigned int get_head_count();
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
};
}

View File

@ -15,42 +15,30 @@
using namespace Storage::Disk;
D64::D64(const char *file_name)
D64::D64(const char *file_name) :
Storage::FileHolder(file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
// 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;
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorNotD64;
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
_disk_id = 0;
disk_id_ = 0;
while(*file_name)
{
_disk_id ^= file_name[0];
_disk_id = (uint16_t)((_disk_id << 2) ^ (_disk_id >> 13));
disk_id_ ^= file_name[0];
disk_id_ = (uint16_t)((disk_id_ << 2) ^ (disk_id_ >> 13));
file_name++;
}
}
D64::~D64()
{
if(_file) fclose(_file);
}
unsigned int D64::get_head_position_count()
{
return _number_of_tracks*2;
return number_of_tracks_*2;
}
std::shared_ptr<Track> D64::get_track_at_position(unsigned int head, unsigned int position)
@ -75,7 +63,7 @@ std::shared_ptr<Track> D64::get_track_at_position(unsigned int head, unsigned in
}
// seek to start of data
fseek(_file, offset_to_track * 256, SEEK_SET);
fseek(file_, offset_to_track * 256, SEEK_SET);
// build up a PCM sampling of the GCR version of this track
@ -114,14 +102,14 @@ std::shared_ptr<Track> D64::get_track_at_position(unsigned int head, unsigned in
uint8_t sector_number = (uint8_t)(sector); // sectors count from 0
uint8_t track_number = (uint8_t)((position >> 1) + 1); // tracks count from 1
uint8_t checksum = (uint8_t)(sector_number ^ track_number ^ _disk_id ^ (_disk_id >> 8));
uint8_t checksum = (uint8_t)(sector_number ^ track_number ^ disk_id_ ^ (disk_id_ >> 8));
uint8_t header_start[4] = {
0x08, checksum, sector_number, track_number
};
Encodings::CommodoreGCR::encode_block(&sector_data[3], header_start);
uint8_t header_end[4] = {
(uint8_t)(_disk_id & 0xff), (uint8_t)(_disk_id >> 8), 0, 0
(uint8_t)(disk_id_ & 0xff), (uint8_t)(disk_id_ >> 8), 0, 0
};
Encodings::CommodoreGCR::encode_block(&sector_data[8], header_end);
@ -134,7 +122,7 @@ std::shared_ptr<Track> D64::get_track_at_position(unsigned int head, unsigned in
// get the actual contents
uint8_t source_data[256];
fread(source_data, 1, 256, _file);
fread(source_data, 1, 256, file_);
// compute the latest checksum
checksum = 0;

View File

@ -10,6 +10,7 @@
#define D64_hpp
#include "../Disk.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Disk {
@ -17,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 Disk {
class D64: public Disk, public Storage::FileHolder {
public:
/*!
Construct a @c D64 containing content from the file with name @c file_name.
@ -26,10 +27,8 @@ class D64: public Disk {
@throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image.
*/
D64(const char *file_name);
~D64();
enum {
ErrorCantOpen,
ErrorNotD64,
};
@ -38,9 +37,8 @@ class D64: public Disk {
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
unsigned int _number_of_tracks;
uint16_t _disk_id;
unsigned int number_of_tracks_;
uint16_t disk_id_;
};
}

View File

@ -14,44 +14,30 @@
using namespace Storage::Disk;
G64::G64(const char *file_name)
G64::G64(const char *file_name) :
Storage::FileHolder(file_name)
{
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorCantOpen;
// read and check the file signature
char signature[8];
if(fread(signature, 1, 8, _file) != 8)
throw ErrorNotG64;
if(memcmp(signature, "GCR-1541", 8))
if(!check_signature("GCR-1541", 8))
throw ErrorNotG64;
// check the version number
int version = fgetc(_file);
int version = fgetc(file_);
if(version != 0)
{
throw ErrorUnknownVersion;
}
// get the number of tracks and track size
_number_of_tracks = (uint8_t)fgetc(_file);
_maximum_track_size = (uint16_t)fgetc(_file);
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
}
G64::~G64()
{
if(_file) fclose(_file);
number_of_tracks_ = (uint8_t)fgetc(file_);
maximum_track_size_ = fgetc16le();
}
unsigned int G64::get_head_position_count()
{
// give at least 84 tracks, to yield the normal geometry but,
// if there are more, shove them in
return _number_of_tracks > 84 ? _number_of_tracks : 84;
return number_of_tracks_ > 84 ? number_of_tracks_ : 84;
}
std::shared_ptr<Track> G64::get_track_at_position(unsigned int head, unsigned int position)
@ -60,55 +46,48 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int head, unsigned in
// if there's definitely no track here, return the empty track
// (TODO: should be supplying one with an index hole?)
if(position >= _number_of_tracks) return resulting_track;
if(position >= number_of_tracks_) return resulting_track;
if(head >= 1) return resulting_track;
// seek to this track's entry in the track table
fseek(_file, (long)((position * 4) + 0xc), SEEK_SET);
fseek(file_, (long)((position * 4) + 0xc), SEEK_SET);
// read the track offset
uint32_t track_offset;
track_offset = (uint32_t)fgetc(_file);
track_offset |= (uint32_t)fgetc(_file) << 8;
track_offset |= (uint32_t)fgetc(_file) << 16;
track_offset |= (uint32_t)fgetc(_file) << 24;
track_offset = fgetc32le();
// 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, (int)track_offset, SEEK_SET);
fseek(file_, (int)track_offset, SEEK_SET);
// get the real track length
uint16_t track_length;
track_length = (uint16_t)fgetc(_file);
track_length |= (uint16_t)fgetc(_file) << 8;
track_length = fgetc16le();
// grab the byte contents of this track
std::vector<uint8_t> track_contents(track_length);
fread(&track_contents[0], 1, track_length, _file);
fread(&track_contents[0], 1, track_length, file_);
// seek to this track's entry in the speed zone table
fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET);
fseek(file_, (long)((position * 4) + 0x15c), SEEK_SET);
// read the speed zone offsrt
uint32_t speed_zone_offset;
speed_zone_offset = (uint32_t)fgetc(_file);
speed_zone_offset |= (uint32_t)fgetc(_file) << 8;
speed_zone_offset |= (uint32_t)fgetc(_file) << 16;
speed_zone_offset |= (uint32_t)fgetc(_file) << 24;
speed_zone_offset = fgetc32le();
// 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, (int)speed_zone_offset, SEEK_SET);
fseek(file_, (int)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);
fread(speed_zone_contents, 1, speed_zone_length, file_);
// divide track into appropriately timed PCMSegments
std::vector<PCMSegment> segments;

View File

@ -10,6 +10,7 @@
#define G64_hpp
#include "../Disk.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Disk {
@ -17,7 +18,7 @@ namespace Disk {
/*!
Provies a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream.
*/
class G64: public Disk {
class G64: public Disk, public Storage::FileHolder {
public:
/*!
Construct a @c G64 containing content from the file with name @c file_name.
@ -27,7 +28,6 @@ class G64: public Disk {
@throws ErrorUnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
*/
G64(const char *file_name);
~G64();
enum {
ErrorCantOpen,
@ -40,10 +40,8 @@ class G64: public Disk {
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
uint8_t _number_of_tracks;
uint16_t _maximum_track_size;
uint8_t number_of_tracks_;
uint16_t maximum_track_size_;
};
}

View File

@ -0,0 +1,59 @@
//
// OricMFMDSK.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "OricMFMDSK.hpp"
#include "../PCMTrack.hpp"
using namespace Storage::Disk;
OricMFMDSK::OricMFMDSK(const char *file_name) :
Storage::FileHolder(file_name)
{
if(!check_signature("MFM_DISK", 8))
throw ErrorNotOricMFMDSK;
head_count_ = fgetc32le();
track_count_ = fgetc32le();
geometry_type_ = fgetc32le();
if(geometry_type_ > 1)
throw ErrorNotOricMFMDSK;
}
unsigned int OricMFMDSK::get_head_position_count()
{
return track_count_;
}
unsigned int OricMFMDSK::get_head_count()
{
return head_count_;
}
std::shared_ptr<Track> OricMFMDSK::get_track_at_position(unsigned int head, unsigned int position)
{
long offset = 0;
switch(geometry_type_)
{
case 0:
offset = (head * track_count_) + position;
break;
case 1:
offset = (position * track_count_ * head_count_) + head;
break;
}
fseek(file_, (offset * 6400) + 256, SEEK_SET);
PCMSegment segment;
segment.number_of_bits = 6250*8;
segment.data.resize(6250);
fread(segment.data.data(), 1, 6250, file_);
std::shared_ptr<PCMTrack> track(new PCMTrack(segment));
return track;
}

View File

@ -0,0 +1,49 @@
//
// OricMFMDSK.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef OricMFMDSK_hpp
#define OricMFMDSK_hpp
#include "../Disk.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing an Oric MFM-stype disk image an MFM bit stream.
*/
class OricMFMDSK: public Disk, public Storage::FileHolder {
public:
/*!
Construct an @c AcornADF containing content from the file with name @c file_name.
@throws ErrorCantOpen if this file can't be opened.
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
*/
OricMFMDSK(const char *file_name);
enum {
ErrorNotOricMFMDSK,
};
// implemented to satisfy @c Disk
unsigned int get_head_position_count();
unsigned int get_head_count();
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
uint32_t head_count_;
uint32_t track_count_;
uint32_t geometry_type_;
};
}
}
#endif /* OricMFMDSK_hpp */

View File

@ -13,51 +13,40 @@
using namespace Storage::Disk;
SSD::SSD(const char *file_name) : _file(nullptr)
SSD::SSD(const char *file_name) :
Storage::FileHolder(file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
// 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;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorCantOpen;
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 = (unsigned int)(file_stats.st_size / (256 * 10));
if(_track_count < 40) _track_count = 40;
else if(_track_count < 80) _track_count = 80;
}
SSD::~SSD()
{
if(_file) fclose(_file);
head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
track_count_ = (unsigned int)(file_stats_.st_size / (256 * 10));
if(track_count_ < 40) track_count_ = 40;
else if(track_count_ < 80) track_count_ = 80;
}
unsigned int SSD::get_head_position_count()
{
return _track_count;
return track_count_;
}
unsigned int SSD::get_head_count()
{
return _head_count;
return head_count_;
}
std::shared_ptr<Track> SSD::get_track_at_position(unsigned int head, unsigned int position)
{
std::shared_ptr<Track> track;
if(head >= _head_count) return track;
long file_offset = (position * _head_count + head) * 256 * 10;
fseek(_file, file_offset, SEEK_SET);
if(head >= head_count_) return track;
long file_offset = (position * head_count_ + head) * 256 * 10;
fseek(file_, file_offset, SEEK_SET);
std::vector<Storage::Encodings::MFM::Sector> sectors;
for(int sector = 0; sector < 10; sector++)
@ -68,8 +57,8 @@ std::shared_ptr<Track> SSD::get_track_at_position(unsigned int head, unsigned in
new_sector.sector = (uint8_t)sector;
new_sector.data.resize(256);
fread(&new_sector.data[0], 1, 256, _file);
if(feof(_file))
fread(&new_sector.data[0], 1, 256, file_);
if(feof(file_))
break;
sectors.push_back(std::move(new_sector));

View File

@ -10,6 +10,7 @@
#define SSD_hpp
#include "../Disk.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace Disk {
@ -17,7 +18,7 @@ namespace Disk {
/*!
Provies a @c Disk containing a DSD or SSD disk image a decoded sector dump of an Acorn DFS disk.
*/
class SSD: public Disk {
class SSD: public Disk, public Storage::FileHolder {
public:
/*!
Construct an @c SSD containing content from the file with name @c file_name.
@ -26,10 +27,8 @@ class SSD: public Disk {
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
*/
SSD(const char *file_name);
~SSD();
enum {
ErrorCantOpen,
ErrorNotSSD,
};
@ -39,9 +38,8 @@ class SSD: public Disk {
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
private:
FILE *_file;
unsigned int _head_count;
unsigned int _track_count;
unsigned int head_count_;
unsigned int track_count_;
};
}

81
Storage/FileHolder.cpp Normal file
View File

@ -0,0 +1,81 @@
//
// FileHolder.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "FileHolder.hpp"
#include <cstring>
using namespace Storage;
FileHolder::~FileHolder()
{
if(file_) fclose(file_);
}
FileHolder::FileHolder(const char *file_name) : file_(nullptr)
{
stat(file_name, &file_stats_);
file_ = fopen(file_name, "rb");
if(!file_) throw ErrorCantOpen;
}
bool FileHolder::check_signature(const char *signature, size_t length)
{
if(!length) length = strlen(signature)+1;
// 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;
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);
return result;
}
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;
}
uint16_t FileHolder::fgetc16le()
{
uint16_t result = (uint16_t)fgetc(file_);
result |= (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 = (uint16_t)(fgetc(file_) << 8);
result |= (uint16_t)fgetc(file_);
return result;
}

74
Storage/FileHolder.hpp Normal file
View File

@ -0,0 +1,74 @@
//
// FileHolder.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef FileHolder_hpp
#define FileHolder_hpp
#include <sys/stat.h>
#include <cstdio>
#include <cstdint>
namespace Storage {
class FileHolder {
public:
enum {
ErrorCantOpen = -1
};
virtual ~FileHolder();
protected:
FileHolder(const char *file_name);
/*!
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
and returning the four assembled in little endian order.
*/
uint32_t fgetc32le();
/*!
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
and returning the four assembled in big endian order.
*/
uint32_t fgetc32be();
/*!
Performs @c fgetc 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();
FILE *file_;
struct stat file_stats_;
};
}
#endif /* FileHolder_hpp */

View File

@ -12,100 +12,82 @@
using namespace Storage::Tape;
CommodoreTAP::CommodoreTAP(const char *file_name) : _is_at_end(false)
CommodoreTAP::CommodoreTAP(const char *file_name) :
is_at_end_(false),
Storage::FileHolder(file_name)
{
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorNotCommodoreTAP;
// read and check the file signature
char signature[12];
if(fread(signature, 1, 12, _file) != 12)
throw ErrorNotCommodoreTAP;
if(memcmp(signature, "C64-TAPE-RAW", 12))
if(!check_signature("C64-TAPE-RAW", 12))
throw ErrorNotCommodoreTAP;
// check the file version
int version = fgetc(_file);
int version = fgetc(file_);
switch(version)
{
case 0: _updated_layout = false; break;
case 1: _updated_layout = true; break;
case 0: updated_layout_ = false; break;
case 1: updated_layout_ = true; break;
default: throw ErrorNotCommodoreTAP;
}
// skip reserved bytes
fseek(_file, 3, SEEK_CUR);
fseek(file_, 3, SEEK_CUR);
// read file size
_file_size = (uint32_t)fgetc(_file);
_file_size |= (uint32_t)(fgetc(_file) << 8);
_file_size |= (uint32_t)(fgetc(_file) << 16);
_file_size |= (uint32_t)(fgetc(_file) << 24);
file_size_ = fgetc32le();
// 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
// a high pulse has just been distributed to imply that the next thing
// that needs to happen is a length check
_current_pulse.length.clock_rate = 985248 * 2;
_current_pulse.type = Pulse::High;
}
CommodoreTAP::~CommodoreTAP()
{
fclose(_file);
current_pulse_.length.clock_rate = 985248 * 2;
current_pulse_.type = Pulse::High;
}
void CommodoreTAP::virtual_reset()
{
fseek(_file, 0x14, SEEK_SET);
_current_pulse.type = Pulse::High;
_is_at_end = false;
fseek(file_, 0x14, SEEK_SET);
current_pulse_.type = Pulse::High;
is_at_end_ = false;
}
bool CommodoreTAP::is_at_end()
{
return _is_at_end;
return is_at_end_;
}
Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse()
{
if(_is_at_end)
if(is_at_end_)
{
return _current_pulse;
return current_pulse_;
}
if(_current_pulse.type == Pulse::High)
if(current_pulse_.type == Pulse::High)
{
uint32_t next_length;
uint8_t next_byte = (uint8_t)fgetc(_file);
if(!_updated_layout || next_byte > 0)
uint8_t next_byte = (uint8_t)fgetc(file_);
if(!updated_layout_ || next_byte > 0)
{
next_length = (uint32_t)next_byte << 3;
}
else
{
next_length = (uint32_t)fgetc(_file);
next_length |= (uint32_t)(fgetc(_file) << 8);
next_length |= (uint32_t)(fgetc(_file) << 16);
next_length = fgetc24le();
}
if(feof(_file))
if(feof(file_))
{
_is_at_end = true;
_current_pulse.length.length = _current_pulse.length.clock_rate;
_current_pulse.type = Pulse::Zero;
is_at_end_ = true;
current_pulse_.length.length = current_pulse_.length.clock_rate;
current_pulse_.type = Pulse::Zero;
}
else
{
_current_pulse.length.length = next_length;
_current_pulse.type = Pulse::Low;
current_pulse_.length.length = next_length;
current_pulse_.type = Pulse::Low;
}
}
else
_current_pulse.type = Pulse::High;
current_pulse_.type = Pulse::High;
return _current_pulse;
return current_pulse_;
}

View File

@ -10,7 +10,8 @@
#define CommodoreTAP_hpp
#include "../Tape.hpp"
#include <stdint.h>
#include "../../FileHolder.hpp"
#include <cstdint>
namespace Storage {
namespace Tape {
@ -18,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 {
class CommodoreTAP: public Tape, public Storage::FileHolder {
public:
/*!
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
@ -26,7 +27,6 @@ class CommodoreTAP: public Tape {
@throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP.
*/
CommodoreTAP(const char *file_name);
~CommodoreTAP();
enum {
ErrorNotCommodoreTAP
@ -39,12 +39,11 @@ class CommodoreTAP: public Tape {
void virtual_reset();
Pulse virtual_get_next_pulse();
FILE *_file;
bool _updated_layout;
uint32_t _file_size;
bool updated_layout_;
uint32_t file_size_;
Pulse _current_pulse;
bool _is_at_end;
Pulse current_pulse_;
bool is_at_end_;
};
}

View File

@ -12,68 +12,51 @@
using namespace Storage::Tape;
OricTAP::OricTAP(const char *file_name) : _file(NULL)
OricTAP::OricTAP(const char *file_name) :
Storage::FileHolder(file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
_file_length = (size_t)file_stats.st_size;
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorNotOricTAP;
// read and check the file signature
uint8_t signature[4];
if(fread(signature, 1, 4, _file) != 4)
throw ErrorNotOricTAP;
if(signature[0] != 0x16 || signature[1] != 0x16 || signature[2] != 0x16 || signature[3] != 0x24)
// check the file signature
if(!check_signature("\x16\x16\x16\x24", 4))
throw ErrorNotOricTAP;
// then rewind and start again
virtual_reset();
}
OricTAP::~OricTAP()
{
if(_file) fclose(_file);
}
void OricTAP::virtual_reset()
{
fseek(_file, 0, SEEK_SET);
_bit_count = 13;
_phase = _next_phase = LeadIn;
_phase_counter = 0;
_pulse_counter = 0;
fseek(file_, 0, SEEK_SET);
bit_count_ = 13;
phase_ = next_phase_ = LeadIn;
phase_counter_ = 0;
pulse_counter_ = 0;
}
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(bit_count_ == 13)
{
if(_next_phase != _phase)
if(next_phase_ != phase_)
{
_phase = _next_phase;
_phase_counter = 0;
phase_ = next_phase_;
phase_counter_ = 0;
}
_bit_count = 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
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(fgetc(file_) != 0x16) break;
}
_next_phase = Header;
next_phase_ = Header;
}
break;
@ -86,44 +69,44 @@ 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 = (uint8_t)fgetc(_file);
next_byte = (uint8_t)fgetc(file_);
if(_phase_counter == 4) _data_end_address = (uint16_t)(next_byte << 8);
if(_phase_counter == 5) _data_end_address |= next_byte;
if(_phase_counter == 6) _data_start_address = (uint16_t)(next_byte << 8);
if(_phase_counter == 7) _data_start_address |= next_byte;
if(phase_counter_ == 4) data_end_address_ = (uint16_t)(next_byte << 8);
if(phase_counter_ == 5) data_end_address_ |= next_byte;
if(phase_counter_ == 6) data_start_address_ = (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;
next_phase_ = Gap;
}
if(feof(_file))
if(feof(file_))
{
_next_phase = End;
next_phase_ = End;
}
_phase_counter++;
phase_counter_++;
break;
case Gap:
_phase_counter++;
if(_phase_counter == 8)
phase_counter_++;
if(phase_counter_ == 8)
{
_next_phase = Data;
next_phase_ = Data;
}
break;
case Data:
next_byte = (uint8_t)fgetc(_file);
_phase_counter++;
if(_phase_counter >= (_data_end_address - _data_start_address)+1)
next_byte = (uint8_t)fgetc(file_);
phase_counter_++;
if(phase_counter_ >= (data_end_address_ - data_start_address_)+1)
{
if(next_byte == 0x16)
{
_next_phase = LeadIn;
next_phase_ = LeadIn;
}
else if(feof(_file))
else if(feof(file_))
{
_next_phase = End;
next_phase_ = End;
}
}
break;
@ -136,7 +119,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
parity ^= (parity >> 4);
parity ^= (parity >> 2);
parity ^= (parity >> 1);
_current_value = (uint16_t)(((uint16_t)next_byte << 1) | ((parity&1) << 9) | (7 << 10));
current_value_ = (uint16_t)(((uint16_t)next_byte << 1) | ((parity&1) << 9) | (7 << 10));
}
// In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz.
@ -146,7 +129,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;
@ -154,13 +137,13 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
return pulse;
case Gap:
_bit_count = 13;
pulse.type = (_phase_counter&1) ? Pulse::Low : Pulse::High;
bit_count_ = 13;
pulse.type = (phase_counter_&1) ? Pulse::Low : Pulse::High;
pulse.length.length = 100;
return pulse;
default:
next_bit = _current_value & 1;
next_bit = current_value_ & 1;
break;
}
@ -170,20 +153,20 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
}
else
{
pulse.length.length = _pulse_counter ? 2 : 1;
pulse.length.length = pulse_counter_ ? 2 : 1;
}
pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO
pulse.type = pulse_counter_ ? Pulse::High : Pulse::Low; // TODO
_pulse_counter ^= 1;
if(!_pulse_counter)
pulse_counter_ ^= 1;
if(!pulse_counter_)
{
_current_value >>= 1;
_bit_count++;
current_value_ >>= 1;
bit_count_++;
}
return pulse;
}
bool OricTAP::is_at_end()
{
return _phase == End;
return phase_ == End;
}

View File

@ -10,7 +10,8 @@
#define OricTAP_hpp
#include "../Tape.hpp"
#include <stdint.h>
#include "../../FileHolder.hpp"
#include <cstdint>
namespace Storage {
namespace Tape {
@ -18,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 {
class OricTAP: public Tape, public Storage::FileHolder {
public:
/*!
Constructs an @c OricTAP containing content from the file with name @c file_name.
@ -26,7 +27,6 @@ class OricTAP: public Tape {
@throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP.
*/
OricTAP(const char *file_name);
~OricTAP();
enum {
ErrorNotOricTAP
@ -39,19 +39,16 @@ class OricTAP: public Tape {
void virtual_reset();
Pulse virtual_get_next_pulse();
FILE *_file;
size_t _file_length;
// byte serialisation and output
uint16_t _current_value;
int _bit_count;
int _pulse_counter;
uint16_t current_value_;
int bit_count_;
int pulse_counter_;
enum Phase {
LeadIn, Header, Data, Gap, End
} _phase, _next_phase;
int _phase_counter;
uint16_t _data_end_address, _data_start_address;
} phase_, next_phase_;
int phase_counter_;
uint16_t data_end_address_, data_start_address_;
};
}

View File

@ -48,32 +48,25 @@
using namespace Storage::Tape;
PRG::PRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80)
PRG::PRG(const char *file_name) :
bit_phase_(3),
file_phase_(FilePhaseLeadIn),
phase_offset_(0),
copy_mask_(0x80),
Storage::FileHolder(file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
// 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;
_file = fopen(file_name, "rb");
if(!_file) throw ErrorBadFormat;
load_address_ = fgetc16le();
length_ = (uint16_t)(file_stats_.st_size - 2);
_load_address = (uint16_t)fgetc(_file);
_load_address |= (uint16_t)fgetc(_file) << 8;
_length = (uint16_t)(file_stats.st_size - 2);
if (_load_address + _length >= 65536)
if (load_address_ + length_ >= 65536)
throw ErrorBadFormat;
}
PRG::~PRG()
{
if(_file) fclose(_file);
}
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
{
// these are all microseconds per pole
@ -82,19 +75,19 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
static const unsigned int one_length = 247;
static const unsigned int marker_length = 328;
_bitPhase = (_bitPhase+1)&3;
if(!_bitPhase) get_next_output_token();
bit_phase_ = (bit_phase_+1)&3;
if(!bit_phase_) get_next_output_token();
Tape::Pulse pulse;
pulse.length.clock_rate = 1000000;
pulse.type = (_bitPhase&1) ? Tape::Pulse::High : Tape::Pulse::Low;
switch(_outputToken)
pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low;
switch(output_token_)
{
case Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break;
case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break;
case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break;
case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break;
case WordMarker: pulse.length.length = (bit_phase_&2) ? one_length : marker_length; break;
case EndOfBlock: pulse.length.length = (bit_phase_&2) ? zero_length : marker_length; break;
case Silence: pulse.type = Tape::Pulse::Zero; pulse.length.length = 5000; break;
}
return pulse;
@ -102,16 +95,16 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
void PRG::virtual_reset()
{
_bitPhase = 3;
fseek(_file, 2, SEEK_SET);
_filePhase = FilePhaseLeadIn;
_phaseOffset = 0;
_copy_mask = 0x80;
bit_phase_ = 3;
fseek(file_, 2, SEEK_SET);
file_phase_ = FilePhaseLeadIn;
phase_offset_ = 0;
copy_mask_ = 0x80;
}
bool PRG::is_at_end()
{
return _filePhase == FilePhaseAtEnd;
return file_phase_ == FilePhaseAtEnd;
}
void PRG::get_next_output_token()
@ -121,54 +114,54 @@ void PRG::get_next_output_token()
static const int leadin_length = 20000;
static const int block_leadin_length = 5000;
if(_filePhase == FilePhaseHeaderDataGap || _filePhase == FilePhaseAtEnd)
if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd)
{
_outputToken = Silence;
if(_filePhase != FilePhaseAtEnd) _filePhase = FilePhaseData;
output_token_ = Silence;
if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData;
return;
}
// 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(_filePhase == FilePhaseLeadIn || _phaseOffset < block_leadin_length)
if(file_phase_ == FilePhaseLeadIn || phase_offset_ < block_leadin_length)
{
_outputToken = Leader;
_phaseOffset++;
if(_filePhase == FilePhaseLeadIn && _phaseOffset == leadin_length)
output_token_ = Leader;
phase_offset_++;
if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length)
{
_phaseOffset = 0;
_filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
phase_offset_ = 0;
file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
}
return;
}
// determine whether a new byte needs to be queued up
int block_offset = _phaseOffset - block_leadin_length;
int block_offset = phase_offset_ - block_leadin_length;
int bit_offset = block_offset % 10;
int byte_offset = block_offset / 10;
_phaseOffset++;
phase_offset_++;
if(!bit_offset &&
(
(_filePhase == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
feof(_file)
(file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
feof(file_)
)
)
{
_outputToken = EndOfBlock;
_phaseOffset = 0;
output_token_ = EndOfBlock;
phase_offset_ = 0;
switch(_filePhase)
switch(file_phase_)
{
default: break;
case FilePhaseHeader:
_copy_mask ^= 0x80;
if(_copy_mask) _filePhase = FilePhaseHeaderDataGap;
copy_mask_ ^= 0x80;
if(copy_mask_) file_phase_ = FilePhaseHeaderDataGap;
break;
case FilePhaseData:
_copy_mask ^= 0x80;
fseek(_file, 2, SEEK_SET);
if(_copy_mask) _filePhase = FilePhaseAtEnd;
copy_mask_ ^= 0x80;
fseek(file_, 2, SEEK_SET);
if(copy_mask_) file_phase_ = FilePhaseAtEnd;
break;
}
return;
@ -179,34 +172,34 @@ void PRG::get_next_output_token()
// the first nine bytes are countdown; the high bit is set if this is a header
if(byte_offset < countdown_bytes)
{
_output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask;
output_byte_ = (uint8_t)(countdown_bytes - byte_offset) | copy_mask_;
}
else
{
if(_filePhase == FilePhaseHeader)
if(file_phase_ == FilePhaseHeader)
{
if(byte_offset == countdown_bytes + block_length)
{
_output_byte = _check_digit;
output_byte_ = check_digit_;
}
else
{
if(byte_offset == countdown_bytes) _check_digit = 0;
if(_filePhase == FilePhaseHeader)
if(byte_offset == countdown_bytes) check_digit_ = 0;
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;
case 3: _output_byte = (_load_address + _length) & 0xff; break;
case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break;
case 0: output_byte_ = 0x03; break;
case 1: output_byte_ = load_address_ & 0xff; break;
case 2: output_byte_ = (load_address_ >> 8)&0xff; break;
case 3: output_byte_ = (load_address_ + length_) & 0xff; break;
case 4: output_byte_ = ((load_address_ + length_) >> 8) & 0xff; break;
case 5: _output_byte = 0x50; break; // P
case 6: _output_byte = 0x52; break; // R
case 7: _output_byte = 0x47; break; // G
case 5: output_byte_ = 0x50; break; // P
case 6: output_byte_ = 0x52; break; // R
case 7: output_byte_ = 0x47; break; // G
default:
_output_byte = 0x20;
output_byte_ = 0x20;
break;
}
}
@ -214,32 +207,32 @@ void PRG::get_next_output_token()
}
else
{
_output_byte = (uint8_t)fgetc(_file);
if(feof(_file))
output_byte_ = (uint8_t)fgetc(file_);
if(feof(file_))
{
_output_byte = _check_digit;
output_byte_ = check_digit_;
}
}
_check_digit ^= _output_byte;
check_digit_ ^= output_byte_;
}
}
switch(bit_offset)
{
case 0:
_outputToken = WordMarker;
output_token_ = WordMarker;
break;
default: // i.e. 18
_outputToken = (_output_byte & (1 << (bit_offset - 1))) ? One : Zero;
output_token_ = (output_byte_ & (1 << (bit_offset - 1))) ? One : Zero;
break;
case 9:
{
uint8_t parity = _output_byte;
uint8_t parity = output_byte_;
parity ^= (parity >> 4);
parity ^= (parity >> 2);
parity ^= (parity >> 1);
_outputToken = (parity&1) ? Zero : One;
output_token_ = (parity&1) ? Zero : One;
}
break;
}

View File

@ -10,7 +10,8 @@
#define Storage_Tape_PRG_hpp
#include "../Tape.hpp"
#include <stdint.h>
#include "../../FileHolder.hpp"
#include <cstdint>
namespace Storage {
namespace Tape {
@ -18,7 +19,7 @@ namespace Tape {
/*!
Provides a @c Tape containing a .PRG, which is a direct local file.
*/
class PRG: public Tape {
class PRG: public Tape, public Storage::FileHolder {
public:
/*!
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type.
@ -28,7 +29,6 @@ class PRG: public Tape {
@throws ErrorBadFormat if this file could not be opened and recognised as the specified type.
*/
PRG(const char *file_name);
~PRG();
enum {
ErrorBadFormat
@ -41,9 +41,8 @@ class PRG: public Tape {
Pulse virtual_get_next_pulse();
void virtual_reset();
FILE *_file;
uint16_t _load_address;
uint16_t _length;
uint16_t load_address_;
uint16_t length_;
enum FilePhase {
FilePhaseLeadIn,
@ -51,10 +50,10 @@ class PRG: public Tape {
FilePhaseHeaderDataGap,
FilePhaseData,
FilePhaseAtEnd
} _filePhase;
int _phaseOffset;
} file_phase_;
int phase_offset_;
int _bitPhase;
int bit_phase_;
enum OutputToken {
Leader,
Zero,
@ -62,11 +61,11 @@ class PRG: public Tape {
WordMarker,
EndOfBlock,
Silence
} _outputToken;
} output_token_;
void get_next_output_token();
uint8_t _output_byte;
uint8_t _check_digit;
uint8_t _copy_mask;
uint8_t output_byte_;
uint8_t check_digit_;
uint8_t copy_mask_;
};
}

View File

@ -74,22 +74,22 @@ static int gzget32(gzFile file)
using namespace Storage::Tape;
UEF::UEF(const char *file_name) :
_time_base(1200),
_is_at_end(false),
_pulse_pointer(0),
_is_300_baud(false)
time_base_(1200),
is_at_end_(false),
pulse_pointer_(0),
is_300_baud_(false)
{
_file = gzopen(file_name, "rb");
file_ = gzopen(file_name, "rb");
char identifier[10];
int bytes_read = gzread(_file, identifier, 10);
int bytes_read = gzread(file_, identifier, 10);
if(bytes_read < 10 || strcmp(identifier, "UEF File!"))
{
throw ErrorNotUEF;
}
uint8_t version[2];
gzread(_file, version, 2);
gzread(file_, version, 2);
if(version[1] > 0 || version[0] > 10)
{
@ -101,41 +101,41 @@ UEF::UEF(const char *file_name) :
UEF::~UEF()
{
gzclose(_file);
gzclose(file_);
}
#pragma mark - Public methods
void UEF::virtual_reset()
{
gzseek(_file, 12, SEEK_SET);
_is_at_end = false;
gzseek(file_, 12, SEEK_SET);
is_at_end_ = false;
parse_next_tape_chunk();
}
bool UEF::is_at_end()
{
return _is_at_end;
return is_at_end_;
}
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
{
Pulse next_pulse;
if(_is_at_end)
if(is_at_end_)
{
next_pulse.type = Pulse::Zero;
next_pulse.length.length = _time_base * 4;
next_pulse.length.clock_rate = _time_base * 4;
next_pulse.length.length = time_base_ * 4;
next_pulse.length.clock_rate = time_base_ * 4;
return next_pulse;
}
next_pulse = _queued_pulses[_pulse_pointer];
_pulse_pointer++;
if(_pulse_pointer == _queued_pulses.size())
next_pulse = queued_pulses_[pulse_pointer_];
pulse_pointer_++;
if(pulse_pointer_ == queued_pulses_.size())
{
_queued_pulses.clear();
_pulse_pointer = 0;
queued_pulses_.clear();
pulse_pointer_ = 0;
parse_next_tape_chunk();
}
return next_pulse;
@ -145,18 +145,18 @@ Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
void UEF::parse_next_tape_chunk()
{
while(!_queued_pulses.size())
while(queued_pulses_.empty())
{
// read chunk details
uint16_t chunk_id = (uint16_t)gzget16(_file);
uint32_t chunk_length = (uint32_t)gzget32(_file);
uint16_t chunk_id = (uint16_t)gzget16(file_);
uint32_t chunk_length = (uint32_t)gzget32(file_);
// figure out where the next chunk will start
z_off_t start_of_next_chunk = gztell(_file) + chunk_length;
z_off_t start_of_next_chunk = gztell(file_) + chunk_length;
if(gzeof(_file))
if(gzeof(file_))
{
_is_at_end = true;
is_at_end_ = true;
return;
}
@ -176,15 +176,15 @@ void UEF::parse_next_tape_chunk()
case 0x0113: // change of base rate
{
// TODO: something smarter than just converting this to an int
float new_time_base = gzgetfloat(_file);
_time_base = (unsigned int)roundf(new_time_base);
float new_time_base = gzgetfloat(file_);
time_base_ = (unsigned int)roundf(new_time_base);
}
break;
case 0x0117:
{
int baud_rate = gzget16(_file);
_is_300_baud = (baud_rate == 300);
int baud_rate = gzget16(file_);
is_300_baud_ = (baud_rate == 300);
}
break;
@ -193,7 +193,7 @@ void UEF::parse_next_tape_chunk()
break;
}
gzseek(_file, start_of_next_chunk, SEEK_SET);
gzseek(file_, start_of_next_chunk, SEEK_SET);
}
}
@ -203,17 +203,17 @@ void UEF::queue_implicit_bit_pattern(uint32_t length)
{
while(length--)
{
queue_implicit_byte(gzget8(_file));
queue_implicit_byte(gzget8(file_));
}
}
void UEF::queue_explicit_bit_pattern(uint32_t length)
{
size_t length_in_bits = (length << 3) - (size_t)gzget8(_file);
size_t length_in_bits = (length << 3) - (size_t)gzget8(file_);
uint8_t current_byte = 0;
for(size_t bit = 0; bit < length_in_bits; bit++)
{
if(!(bit&7)) current_byte = gzget8(_file);
if(!(bit&7)) current_byte = gzget8(file_);
queue_bit(current_byte&1);
current_byte >>= 1;
}
@ -222,30 +222,30 @@ void UEF::queue_explicit_bit_pattern(uint32_t length)
void UEF::queue_integer_gap()
{
Time duration;
duration.length = (unsigned int)gzget16(_file);
duration.clock_rate = _time_base;
_queued_pulses.emplace_back(Pulse::Zero, duration);
duration.length = (unsigned int)gzget16(file_);
duration.clock_rate = time_base_;
queued_pulses_.emplace_back(Pulse::Zero, duration);
}
void UEF::queue_floating_point_gap()
{
float length = gzgetfloat(_file);
float length = gzgetfloat(file_);
Time duration;
duration.length = (unsigned int)(length * 4000000);
duration.clock_rate = 4000000;
_queued_pulses.emplace_back(Pulse::Zero, duration);
queued_pulses_.emplace_back(Pulse::Zero, duration);
}
void UEF::queue_carrier_tone()
{
unsigned int number_of_cycles = (unsigned int)gzget16(_file);
unsigned int number_of_cycles = (unsigned int)gzget16(file_);
while(number_of_cycles--) queue_bit(1);
}
void UEF::queue_carrier_tone_with_dummy()
{
unsigned int pre_cycles = (unsigned int)gzget16(_file);
unsigned int post_cycles = (unsigned int)gzget16(_file);
unsigned int pre_cycles = (unsigned int)gzget16(file_);
unsigned int post_cycles = (unsigned int)gzget16(file_);
while(pre_cycles--) queue_bit(1);
queue_implicit_byte(0xaa);
while(post_cycles--) queue_bit(1);
@ -253,33 +253,33 @@ void UEF::queue_carrier_tone_with_dummy()
void UEF::queue_security_cycles()
{
int number_of_cycles = gzget24(_file);
bool first_is_pulse = gzget8(_file) == 'P';
bool last_is_pulse = gzget8(_file) == 'P';
int number_of_cycles = gzget24(file_);
bool first_is_pulse = gzget8(file_) == 'P';
bool last_is_pulse = gzget8(file_) == 'P';
uint8_t current_byte = 0;
for(int cycle = 0; cycle < number_of_cycles; cycle++)
{
if(!(cycle&7)) current_byte = gzget8(_file);
if(!(cycle&7)) current_byte = gzget8(file_);
int bit = (current_byte >> 7);
current_byte <<= 1;
Time duration;
duration.length = bit ? 1 : 2;
duration.clock_rate = _time_base * 4;
duration.clock_rate = time_base_ * 4;
if(!cycle && first_is_pulse)
{
_queued_pulses.emplace_back(Pulse::High, duration);
queued_pulses_.emplace_back(Pulse::High, duration);
}
else if(cycle == number_of_cycles-1 && last_is_pulse)
{
_queued_pulses.emplace_back(Pulse::Low, duration);
queued_pulses_.emplace_back(Pulse::Low, duration);
}
else
{
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
queued_pulses_.emplace_back(Pulse::Low, duration);
queued_pulses_.emplace_back(Pulse::High, duration);
}
}
}
@ -288,9 +288,9 @@ void UEF::queue_defined_data(uint32_t length)
{
if(length < 3) return;
int bits_per_packet = gzget8(_file);
char parity_type = (char)gzget8(_file);
int number_of_stop_bits = gzget8(_file);
int bits_per_packet = gzget8(file_);
char parity_type = (char)gzget8(file_);
int number_of_stop_bits = gzget8(file_);
bool has_extra_stop_wave = (number_of_stop_bits < 0);
number_of_stop_bits = abs(number_of_stop_bits);
@ -298,7 +298,7 @@ void UEF::queue_defined_data(uint32_t length)
length -= 3;
while(length--)
{
uint8_t byte = gzget8(_file);
uint8_t byte = gzget8(file_);
uint8_t parity_value = byte;
parity_value ^= (parity_value >> 4);
@ -326,9 +326,9 @@ void UEF::queue_defined_data(uint32_t length)
{
Time duration;
duration.length = 1;
duration.clock_rate = _time_base * 4;
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
duration.clock_rate = time_base_ * 4;
queued_pulses_.emplace_back(Pulse::Low, duration);
queued_pulses_.emplace_back(Pulse::High, duration);
}
}
}
@ -351,7 +351,7 @@ void UEF::queue_bit(int bit)
{
int number_of_cycles;
Time duration;
duration.clock_rate = _time_base * 4;
duration.clock_rate = time_base_ * 4;
if(bit)
{
@ -366,11 +366,11 @@ void UEF::queue_bit(int bit)
number_of_cycles = 1;
}
if(_is_300_baud) number_of_cycles *= 4;
if(is_300_baud_) number_of_cycles *= 4;
while(number_of_cycles--)
{
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
queued_pulses_.emplace_back(Pulse::Low, duration);
queued_pulses_.emplace_back(Pulse::High, duration);
}
}

View File

@ -41,13 +41,13 @@ class UEF : public Tape {
void virtual_reset();
Pulse virtual_get_next_pulse();
gzFile _file;
unsigned int _time_base;
bool _is_at_end;
bool _is_300_baud;
gzFile file_;
unsigned int time_base_;
bool is_at_end_;
bool is_300_baud_;
std::vector<Pulse> _queued_pulses;
size_t _pulse_pointer;
std::vector<Pulse> queued_pulses_;
size_t pulse_pointer_;
void parse_next_tape_chunk();