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:
commit
fc1afe9351
@ -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 */,
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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(§or_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(§or_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;
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
59
Storage/Disk/Formats/OricMFMDSK.cpp
Normal file
59
Storage/Disk/Formats/OricMFMDSK.cpp
Normal 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;
|
||||
}
|
49
Storage/Disk/Formats/OricMFMDSK.hpp
Normal file
49
Storage/Disk/Formats/OricMFMDSK.hpp
Normal 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 */
|
@ -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));
|
||||
|
@ -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
81
Storage/FileHolder.cpp
Normal 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
74
Storage/FileHolder.hpp
Normal 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 */
|
@ -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_;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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. 1–8
|
||||
_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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user