mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +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 */; };
|
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
||||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
||||||
4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A12551DD55862007A2231 /* Disassembler6502.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 */; };
|
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
|
||||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
|
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
|
||||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
|
||||||
@ -1190,6 +1196,8 @@
|
|||||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||||
|
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */,
|
||||||
|
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */,
|
||||||
);
|
);
|
||||||
name = Storage;
|
name = Storage;
|
||||||
path = ../../Storage;
|
path = ../../Storage;
|
||||||
@ -1275,14 +1283,16 @@
|
|||||||
4BAB62B21D327F7E00DF5BA0 /* Formats */ = {
|
4BAB62B21D327F7E00DF5BA0 /* Formats */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
|
|
||||||
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
|
|
||||||
4B4C836E1D4F623200CD541F /* D64.cpp */,
|
|
||||||
4B4C836F1D4F623200CD541F /* D64.hpp */,
|
|
||||||
4BF829611D8F536B001BAE39 /* SSD.cpp */,
|
|
||||||
4BF829621D8F536B001BAE39 /* SSD.hpp */,
|
|
||||||
4BD69F921D98760000243FE1 /* AcornADF.cpp */,
|
4BD69F921D98760000243FE1 /* AcornADF.cpp */,
|
||||||
4BD69F931D98760000243FE1 /* AcornADF.hpp */,
|
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;
|
path = Formats;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2328,11 +2338,13 @@
|
|||||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||||
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */,
|
||||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||||
|
4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */,
|
||||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
|
||||||
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
|
||||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||||
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
|
||||||
|
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */,
|
||||||
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
|
||||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
|
||||||
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<string>rom</string>
|
<string>rom</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Electron/BBC ROM Image</string>
|
<string>ROM Image</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
@ -146,6 +146,18 @@
|
|||||||
<key>NSDocumentClass</key>
|
<key>NSDocumentClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
</dict>
|
</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>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "../Storage/Disk/Formats/AcornADF.hpp"
|
#include "../Storage/Disk/Formats/AcornADF.hpp"
|
||||||
#include "../Storage/Disk/Formats/D64.hpp"
|
#include "../Storage/Disk/Formats/D64.hpp"
|
||||||
#include "../Storage/Disk/Formats/G64.hpp"
|
#include "../Storage/Disk/Formats/G64.hpp"
|
||||||
|
#include "../Storage/Disk/Formats/OricMFMDSK.hpp"
|
||||||
#include "../Storage/Disk/Formats/SSD.hpp"
|
#include "../Storage/Disk/Formats/SSD.hpp"
|
||||||
|
|
||||||
// Tapes
|
// Tapes
|
||||||
@ -88,6 +89,7 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
|||||||
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
|
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
|
||||||
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
|
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
|
||||||
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
|
Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD
|
||||||
|
Format("dsk", disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK
|
||||||
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
|
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
|
||||||
|
|
||||||
// PRG
|
// PRG
|
||||||
|
@ -18,35 +18,25 @@ namespace {
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
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
|
// very loose validation: the file needs to be a multiple of 256 bytes
|
||||||
// and not ungainly large
|
// and not ungainly large
|
||||||
if(file_stats.st_size % bytes_per_sector) throw ErrorNotAcornADF;
|
if(file_stats_.st_size % bytes_per_sector) throw ErrorNotAcornADF;
|
||||||
if(file_stats.st_size < 7 * 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;
|
|
||||||
|
|
||||||
// check that the initial directory's 'Hugo's are present
|
// check that the initial directory's 'Hugo's are present
|
||||||
fseek(_file, 513, SEEK_SET);
|
fseek(file_, 513, SEEK_SET);
|
||||||
uint8_t bytes[4];
|
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;
|
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw ErrorNotAcornADF;
|
||||||
|
|
||||||
fseek(_file, 0x6fb, SEEK_SET);
|
fseek(file_, 0x6fb, SEEK_SET);
|
||||||
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;
|
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()
|
unsigned int AcornADF::get_head_position_count()
|
||||||
{
|
{
|
||||||
return 80;
|
return 80;
|
||||||
@ -63,7 +53,7 @@ std::shared_ptr<Track> AcornADF::get_track_at_position(unsigned int head, unsign
|
|||||||
|
|
||||||
if(head >= 2) return track;
|
if(head >= 2) return track;
|
||||||
long file_offset = (position * 1 + head) * bytes_per_sector * sectors_per_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;
|
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||||
for(int sector = 0; sector < sectors_per_track; sector++)
|
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.sector = (uint8_t)sector;
|
||||||
|
|
||||||
new_sector.data.resize(bytes_per_sector);
|
new_sector.data.resize(bytes_per_sector);
|
||||||
fread(&new_sector.data[0], 1, bytes_per_sector, _file);
|
fread(&new_sector.data[0], 1, bytes_per_sector, file_);
|
||||||
if(feof(_file))
|
if(feof(file_))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
sectors.push_back(std::move(new_sector));
|
sectors.push_back(std::move(new_sector));
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define AcornADF_hpp
|
#define AcornADF_hpp
|
||||||
|
|
||||||
#include "../Disk.hpp"
|
#include "../Disk.hpp"
|
||||||
|
#include "../../FileHolder.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Disk {
|
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.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Construct an @c AcornADF containing content from the file with name @c file_name.
|
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.
|
@throws ErrorNotAcornADF if the file doesn't appear to contain an Acorn .ADF format image.
|
||||||
*/
|
*/
|
||||||
AcornADF(const char *file_name);
|
AcornADF(const char *file_name);
|
||||||
~AcornADF();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorCantOpen,
|
|
||||||
ErrorNotAcornADF,
|
ErrorNotAcornADF,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,9 +36,6 @@ class AcornADF: public Disk {
|
|||||||
unsigned int get_head_position_count();
|
unsigned int get_head_position_count();
|
||||||
unsigned int get_head_count();
|
unsigned int get_head_count();
|
||||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
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;
|
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
|
// 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.
|
// 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;
|
throw ErrorNotD64;
|
||||||
|
|
||||||
_number_of_tracks = (file_stats.st_size == 174848) ? 35 : 40;
|
number_of_tracks_ = (file_stats_.st_size == 174848) ? 35 : 40;
|
||||||
|
|
||||||
_file = fopen(file_name, "rb");
|
|
||||||
|
|
||||||
if(!_file)
|
|
||||||
throw ErrorNotD64;
|
|
||||||
|
|
||||||
// then, ostensibly, this is a valid file. Hmmm. Pick a disk ID as a function of the file_name,
|
// 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
|
// being the most stable thing available
|
||||||
_disk_id = 0;
|
disk_id_ = 0;
|
||||||
while(*file_name)
|
while(*file_name)
|
||||||
{
|
{
|
||||||
_disk_id ^= file_name[0];
|
disk_id_ ^= file_name[0];
|
||||||
_disk_id = (uint16_t)((_disk_id << 2) ^ (_disk_id >> 13));
|
disk_id_ = (uint16_t)((disk_id_ << 2) ^ (disk_id_ >> 13));
|
||||||
file_name++;
|
file_name++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
D64::~D64()
|
|
||||||
{
|
|
||||||
if(_file) fclose(_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int D64::get_head_position_count()
|
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)
|
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
|
// 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
|
// 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 sector_number = (uint8_t)(sector); // sectors count from 0
|
||||||
uint8_t track_number = (uint8_t)((position >> 1) + 1); // tracks count from 1
|
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] = {
|
uint8_t header_start[4] = {
|
||||||
0x08, checksum, sector_number, track_number
|
0x08, checksum, sector_number, track_number
|
||||||
};
|
};
|
||||||
Encodings::CommodoreGCR::encode_block(§or_data[3], header_start);
|
Encodings::CommodoreGCR::encode_block(§or_data[3], header_start);
|
||||||
|
|
||||||
uint8_t header_end[4] = {
|
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);
|
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
|
// get the actual contents
|
||||||
uint8_t source_data[256];
|
uint8_t source_data[256];
|
||||||
fread(source_data, 1, 256, _file);
|
fread(source_data, 1, 256, file_);
|
||||||
|
|
||||||
// compute the latest checksum
|
// compute the latest checksum
|
||||||
checksum = 0;
|
checksum = 0;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define D64_hpp
|
#define D64_hpp
|
||||||
|
|
||||||
#include "../Disk.hpp"
|
#include "../Disk.hpp"
|
||||||
|
#include "../../FileHolder.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Disk {
|
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.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Construct a @c D64 containing content from the file with name @c file_name.
|
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.
|
@throws ErrorNotD64 if the file doesn't appear to contain a .D64 format image.
|
||||||
*/
|
*/
|
||||||
D64(const char *file_name);
|
D64(const char *file_name);
|
||||||
~D64();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorCantOpen,
|
|
||||||
ErrorNotD64,
|
ErrorNotD64,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,9 +37,8 @@ class D64: public Disk {
|
|||||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FILE *_file;
|
unsigned int number_of_tracks_;
|
||||||
unsigned int _number_of_tracks;
|
uint16_t disk_id_;
|
||||||
uint16_t _disk_id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,44 +14,30 @@
|
|||||||
|
|
||||||
using namespace Storage::Disk;
|
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
|
// read and check the file signature
|
||||||
char signature[8];
|
if(!check_signature("GCR-1541", 8))
|
||||||
if(fread(signature, 1, 8, _file) != 8)
|
|
||||||
throw ErrorNotG64;
|
|
||||||
|
|
||||||
if(memcmp(signature, "GCR-1541", 8))
|
|
||||||
throw ErrorNotG64;
|
throw ErrorNotG64;
|
||||||
|
|
||||||
// check the version number
|
// check the version number
|
||||||
int version = fgetc(_file);
|
int version = fgetc(file_);
|
||||||
if(version != 0)
|
if(version != 0)
|
||||||
{
|
{
|
||||||
throw ErrorUnknownVersion;
|
throw ErrorUnknownVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the number of tracks and track size
|
// get the number of tracks and track size
|
||||||
_number_of_tracks = (uint8_t)fgetc(_file);
|
number_of_tracks_ = (uint8_t)fgetc(file_);
|
||||||
_maximum_track_size = (uint16_t)fgetc(_file);
|
maximum_track_size_ = fgetc16le();
|
||||||
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
G64::~G64()
|
|
||||||
{
|
|
||||||
if(_file) fclose(_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int G64::get_head_position_count()
|
unsigned int G64::get_head_position_count()
|
||||||
{
|
{
|
||||||
// give at least 84 tracks, to yield the normal geometry but,
|
// give at least 84 tracks, to yield the normal geometry but,
|
||||||
// if there are more, shove them in
|
// 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)
|
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
|
// if there's definitely no track here, return the empty track
|
||||||
// (TODO: should be supplying one with an index hole?)
|
// (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;
|
if(head >= 1) return resulting_track;
|
||||||
|
|
||||||
// seek to this track's entry in the track table
|
// 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
|
// read the track offset
|
||||||
uint32_t track_offset;
|
uint32_t track_offset;
|
||||||
track_offset = (uint32_t)fgetc(_file);
|
track_offset = fgetc32le();
|
||||||
track_offset |= (uint32_t)fgetc(_file) << 8;
|
|
||||||
track_offset |= (uint32_t)fgetc(_file) << 16;
|
|
||||||
track_offset |= (uint32_t)fgetc(_file) << 24;
|
|
||||||
|
|
||||||
// if the track offset is zero, this track doesn't exist, so...
|
// if the track offset is zero, this track doesn't exist, so...
|
||||||
if(!track_offset) return resulting_track;
|
if(!track_offset) return resulting_track;
|
||||||
|
|
||||||
// seek to the track start
|
// seek to the track start
|
||||||
fseek(_file, (int)track_offset, SEEK_SET);
|
fseek(file_, (int)track_offset, SEEK_SET);
|
||||||
|
|
||||||
// get the real track length
|
// get the real track length
|
||||||
uint16_t track_length;
|
uint16_t track_length;
|
||||||
track_length = (uint16_t)fgetc(_file);
|
track_length = fgetc16le();
|
||||||
track_length |= (uint16_t)fgetc(_file) << 8;
|
|
||||||
|
|
||||||
// grab the byte contents of this track
|
// grab the byte contents of this track
|
||||||
std::vector<uint8_t> track_contents(track_length);
|
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
|
// 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
|
// read the speed zone offsrt
|
||||||
uint32_t speed_zone_offset;
|
uint32_t speed_zone_offset;
|
||||||
speed_zone_offset = (uint32_t)fgetc(_file);
|
speed_zone_offset = fgetc32le();
|
||||||
speed_zone_offset |= (uint32_t)fgetc(_file) << 8;
|
|
||||||
speed_zone_offset |= (uint32_t)fgetc(_file) << 16;
|
|
||||||
speed_zone_offset |= (uint32_t)fgetc(_file) << 24;
|
|
||||||
|
|
||||||
// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
|
// 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)
|
if(speed_zone_offset > 3)
|
||||||
{
|
{
|
||||||
// seek to start of speed zone
|
// 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;
|
uint16_t speed_zone_length = (track_length + 3) >> 2;
|
||||||
|
|
||||||
// read the speed zone bytes
|
// read the speed zone bytes
|
||||||
uint8_t speed_zone_contents[speed_zone_length];
|
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
|
// divide track into appropriately timed PCMSegments
|
||||||
std::vector<PCMSegment> segments;
|
std::vector<PCMSegment> segments;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define G64_hpp
|
#define G64_hpp
|
||||||
|
|
||||||
#include "../Disk.hpp"
|
#include "../Disk.hpp"
|
||||||
|
#include "../../FileHolder.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Disk {
|
namespace Disk {
|
||||||
@ -17,7 +18,7 @@ namespace Disk {
|
|||||||
/*!
|
/*!
|
||||||
Provies a @c Disk containing a G64 disk image — a raw but perfectly-clocked GCR stream.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Construct a @c G64 containing content from the file with name @c file_name.
|
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.
|
@throws ErrorUnknownVersion if this file appears to be a .G64 but has an unrecognised version number.
|
||||||
*/
|
*/
|
||||||
G64(const char *file_name);
|
G64(const char *file_name);
|
||||||
~G64();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorCantOpen,
|
ErrorCantOpen,
|
||||||
@ -40,10 +40,8 @@ class G64: public Disk {
|
|||||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||||
|
|
||||||
private:
|
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;
|
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
|
// very loose validation: the file needs to be a multiple of 256 bytes
|
||||||
// and not ungainly large
|
// and not ungainly large
|
||||||
|
|
||||||
if(file_stats.st_size & 255) throw ErrorNotSSD;
|
if(file_stats_.st_size & 255) throw ErrorNotSSD;
|
||||||
if(file_stats.st_size < 512) throw ErrorNotSSD;
|
if(file_stats_.st_size < 512) throw ErrorNotSSD;
|
||||||
if(file_stats.st_size > 800*256) throw ErrorNotSSD;
|
if(file_stats_.st_size > 800*256) throw ErrorNotSSD;
|
||||||
|
|
||||||
_file = fopen(file_name, "rb");
|
|
||||||
|
|
||||||
if(!_file) throw ErrorCantOpen;
|
|
||||||
|
|
||||||
// this has two heads if the suffix is .dsd, one if it's .ssd
|
// 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;
|
head_count_ = (tolower(file_name[strlen(file_name) - 3]) == 'd') ? 2 : 1;
|
||||||
_track_count = (unsigned int)(file_stats.st_size / (256 * 10));
|
track_count_ = (unsigned int)(file_stats_.st_size / (256 * 10));
|
||||||
if(_track_count < 40) _track_count = 40;
|
if(track_count_ < 40) track_count_ = 40;
|
||||||
else if(_track_count < 80) _track_count = 80;
|
else if(track_count_ < 80) track_count_ = 80;
|
||||||
}
|
|
||||||
|
|
||||||
SSD::~SSD()
|
|
||||||
{
|
|
||||||
if(_file) fclose(_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int SSD::get_head_position_count()
|
unsigned int SSD::get_head_position_count()
|
||||||
{
|
{
|
||||||
return _track_count;
|
return track_count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int SSD::get_head_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> SSD::get_track_at_position(unsigned int head, unsigned int position)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Track> track;
|
std::shared_ptr<Track> track;
|
||||||
|
|
||||||
if(head >= _head_count) return track;
|
if(head >= head_count_) return track;
|
||||||
long file_offset = (position * _head_count + head) * 256 * 10;
|
long file_offset = (position * head_count_ + head) * 256 * 10;
|
||||||
fseek(_file, file_offset, SEEK_SET);
|
fseek(file_, file_offset, SEEK_SET);
|
||||||
|
|
||||||
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||||
for(int sector = 0; sector < 10; sector++)
|
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.sector = (uint8_t)sector;
|
||||||
|
|
||||||
new_sector.data.resize(256);
|
new_sector.data.resize(256);
|
||||||
fread(&new_sector.data[0], 1, 256, _file);
|
fread(&new_sector.data[0], 1, 256, file_);
|
||||||
if(feof(_file))
|
if(feof(file_))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
sectors.push_back(std::move(new_sector));
|
sectors.push_back(std::move(new_sector));
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define SSD_hpp
|
#define SSD_hpp
|
||||||
|
|
||||||
#include "../Disk.hpp"
|
#include "../Disk.hpp"
|
||||||
|
#include "../../FileHolder.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Disk {
|
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.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Construct an @c SSD containing content from the file with name @c file_name.
|
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.
|
@throws ErrorNotSSD if the file doesn't appear to contain a .SSD format image.
|
||||||
*/
|
*/
|
||||||
SSD(const char *file_name);
|
SSD(const char *file_name);
|
||||||
~SSD();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorCantOpen,
|
|
||||||
ErrorNotSSD,
|
ErrorNotSSD,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,9 +38,8 @@ class SSD: public Disk {
|
|||||||
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
std::shared_ptr<Track> get_track_at_position(unsigned int head, unsigned int position);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FILE *_file;
|
unsigned int head_count_;
|
||||||
unsigned int _head_count;
|
unsigned int track_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;
|
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(!check_signature("C64-TAPE-RAW", 12))
|
||||||
|
|
||||||
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))
|
|
||||||
throw ErrorNotCommodoreTAP;
|
throw ErrorNotCommodoreTAP;
|
||||||
|
|
||||||
// check the file version
|
// check the file version
|
||||||
int version = fgetc(_file);
|
int version = fgetc(file_);
|
||||||
switch(version)
|
switch(version)
|
||||||
{
|
{
|
||||||
case 0: _updated_layout = false; break;
|
case 0: updated_layout_ = false; break;
|
||||||
case 1: _updated_layout = true; break;
|
case 1: updated_layout_ = true; break;
|
||||||
default: throw ErrorNotCommodoreTAP;
|
default: throw ErrorNotCommodoreTAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip reserved bytes
|
// skip reserved bytes
|
||||||
fseek(_file, 3, SEEK_CUR);
|
fseek(file_, 3, SEEK_CUR);
|
||||||
|
|
||||||
// read file size
|
// read file size
|
||||||
_file_size = (uint32_t)fgetc(_file);
|
file_size_ = fgetc32le();
|
||||||
_file_size |= (uint32_t)(fgetc(_file) << 8);
|
|
||||||
_file_size |= (uint32_t)(fgetc(_file) << 16);
|
|
||||||
_file_size |= (uint32_t)(fgetc(_file) << 24);
|
|
||||||
|
|
||||||
// set up for pulse output at the PAL clock rate, with each high and
|
// 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
|
// 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
|
// a high pulse has just been distributed to imply that the next thing
|
||||||
// that needs to happen is a length check
|
// that needs to happen is a length check
|
||||||
_current_pulse.length.clock_rate = 985248 * 2;
|
current_pulse_.length.clock_rate = 985248 * 2;
|
||||||
_current_pulse.type = Pulse::High;
|
current_pulse_.type = Pulse::High;
|
||||||
}
|
|
||||||
|
|
||||||
CommodoreTAP::~CommodoreTAP()
|
|
||||||
{
|
|
||||||
fclose(_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommodoreTAP::virtual_reset()
|
void CommodoreTAP::virtual_reset()
|
||||||
{
|
{
|
||||||
fseek(_file, 0x14, SEEK_SET);
|
fseek(file_, 0x14, SEEK_SET);
|
||||||
_current_pulse.type = Pulse::High;
|
current_pulse_.type = Pulse::High;
|
||||||
_is_at_end = false;
|
is_at_end_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommodoreTAP::is_at_end()
|
bool CommodoreTAP::is_at_end()
|
||||||
{
|
{
|
||||||
return _is_at_end;
|
return is_at_end_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse()
|
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;
|
uint32_t next_length;
|
||||||
uint8_t next_byte = (uint8_t)fgetc(_file);
|
uint8_t next_byte = (uint8_t)fgetc(file_);
|
||||||
if(!_updated_layout || next_byte > 0)
|
if(!updated_layout_ || next_byte > 0)
|
||||||
{
|
{
|
||||||
next_length = (uint32_t)next_byte << 3;
|
next_length = (uint32_t)next_byte << 3;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
next_length = (uint32_t)fgetc(_file);
|
next_length = fgetc24le();
|
||||||
next_length |= (uint32_t)(fgetc(_file) << 8);
|
|
||||||
next_length |= (uint32_t)(fgetc(_file) << 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(feof(_file))
|
if(feof(file_))
|
||||||
{
|
{
|
||||||
_is_at_end = true;
|
is_at_end_ = true;
|
||||||
_current_pulse.length.length = _current_pulse.length.clock_rate;
|
current_pulse_.length.length = current_pulse_.length.clock_rate;
|
||||||
_current_pulse.type = Pulse::Zero;
|
current_pulse_.type = Pulse::Zero;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_current_pulse.length.length = next_length;
|
current_pulse_.length.length = next_length;
|
||||||
_current_pulse.type = Pulse::Low;
|
current_pulse_.type = Pulse::Low;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
_current_pulse.type = Pulse::High;
|
current_pulse_.type = Pulse::High;
|
||||||
|
|
||||||
return _current_pulse;
|
return current_pulse_;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
#define CommodoreTAP_hpp
|
#define CommodoreTAP_hpp
|
||||||
|
|
||||||
#include "../Tape.hpp"
|
#include "../Tape.hpp"
|
||||||
#include <stdint.h>
|
#include "../../FileHolder.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
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.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
|
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.
|
@throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP.
|
||||||
*/
|
*/
|
||||||
CommodoreTAP(const char *file_name);
|
CommodoreTAP(const char *file_name);
|
||||||
~CommodoreTAP();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorNotCommodoreTAP
|
ErrorNotCommodoreTAP
|
||||||
@ -39,12 +39,11 @@ class CommodoreTAP: public Tape {
|
|||||||
void virtual_reset();
|
void virtual_reset();
|
||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
|
|
||||||
FILE *_file;
|
bool updated_layout_;
|
||||||
bool _updated_layout;
|
uint32_t file_size_;
|
||||||
uint32_t _file_size;
|
|
||||||
|
|
||||||
Pulse _current_pulse;
|
Pulse current_pulse_;
|
||||||
bool _is_at_end;
|
bool is_at_end_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,68 +12,51 @@
|
|||||||
|
|
||||||
using namespace Storage::Tape;
|
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;
|
// check the file signature
|
||||||
stat(file_name, &file_stats);
|
if(!check_signature("\x16\x16\x16\x24", 4))
|
||||||
_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)
|
|
||||||
throw ErrorNotOricTAP;
|
throw ErrorNotOricTAP;
|
||||||
|
|
||||||
// then rewind and start again
|
// then rewind and start again
|
||||||
virtual_reset();
|
virtual_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
OricTAP::~OricTAP()
|
|
||||||
{
|
|
||||||
if(_file) fclose(_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OricTAP::virtual_reset()
|
void OricTAP::virtual_reset()
|
||||||
{
|
{
|
||||||
fseek(_file, 0, SEEK_SET);
|
fseek(file_, 0, SEEK_SET);
|
||||||
_bit_count = 13;
|
bit_count_ = 13;
|
||||||
_phase = _next_phase = LeadIn;
|
phase_ = next_phase_ = LeadIn;
|
||||||
_phase_counter = 0;
|
phase_counter_ = 0;
|
||||||
_pulse_counter = 0;
|
pulse_counter_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tape::Pulse OricTAP::virtual_get_next_pulse()
|
Tape::Pulse OricTAP::virtual_get_next_pulse()
|
||||||
{
|
{
|
||||||
// Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s.
|
// 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_ = next_phase_;
|
||||||
_phase_counter = 0;
|
phase_counter_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_bit_count = 0;
|
bit_count_ = 0;
|
||||||
uint8_t next_byte = 0;
|
uint8_t next_byte = 0;
|
||||||
switch(_phase)
|
switch(phase_)
|
||||||
{
|
{
|
||||||
case LeadIn:
|
case LeadIn:
|
||||||
next_byte = _phase_counter < 258 ? 0x16 : 0x24;
|
next_byte = phase_counter_ < 258 ? 0x16 : 0x24;
|
||||||
_phase_counter++;
|
phase_counter_++;
|
||||||
if(_phase_counter == 259) // 256 artificial bytes plus the three in the file = 259
|
if(phase_counter_ == 259) // 256 artificial bytes plus the three in the file = 259
|
||||||
{
|
{
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
if(fgetc(_file) != 0x16) break;
|
if(fgetc(file_) != 0x16) break;
|
||||||
}
|
}
|
||||||
_next_phase = Header;
|
next_phase_ = Header;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -86,44 +69,44 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
|
|||||||
// [6, 7]: start address of data
|
// [6, 7]: start address of data
|
||||||
// 8: "unused" (on the Oric 1)
|
// 8: "unused" (on the Oric 1)
|
||||||
// [9...]: filename, up to NULL byte
|
// [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_ == 4) data_end_address_ = (uint16_t)(next_byte << 8);
|
||||||
if(_phase_counter == 5) _data_end_address |= next_byte;
|
if(phase_counter_ == 5) data_end_address_ |= next_byte;
|
||||||
if(_phase_counter == 6) _data_start_address = (uint16_t)(next_byte << 8);
|
if(phase_counter_ == 6) data_start_address_ = (uint16_t)(next_byte << 8);
|
||||||
if(_phase_counter == 7) _data_start_address |= next_byte;
|
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;
|
break;
|
||||||
|
|
||||||
case Gap:
|
case Gap:
|
||||||
_phase_counter++;
|
phase_counter_++;
|
||||||
if(_phase_counter == 8)
|
if(phase_counter_ == 8)
|
||||||
{
|
{
|
||||||
_next_phase = Data;
|
next_phase_ = Data;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Data:
|
case Data:
|
||||||
next_byte = (uint8_t)fgetc(_file);
|
next_byte = (uint8_t)fgetc(file_);
|
||||||
_phase_counter++;
|
phase_counter_++;
|
||||||
if(_phase_counter >= (_data_end_address - _data_start_address)+1)
|
if(phase_counter_ >= (data_end_address_ - data_start_address_)+1)
|
||||||
{
|
{
|
||||||
if(next_byte == 0x16)
|
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;
|
break;
|
||||||
@ -136,7 +119,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
|
|||||||
parity ^= (parity >> 4);
|
parity ^= (parity >> 4);
|
||||||
parity ^= (parity >> 2);
|
parity ^= (parity >> 2);
|
||||||
parity ^= (parity >> 1);
|
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.
|
// 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;
|
pulse.length.clock_rate = 4800;
|
||||||
int next_bit;
|
int next_bit;
|
||||||
|
|
||||||
switch(_phase)
|
switch(phase_)
|
||||||
{
|
{
|
||||||
case End:
|
case End:
|
||||||
pulse.type = Pulse::Zero;
|
pulse.type = Pulse::Zero;
|
||||||
@ -154,13 +137,13 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
|
|||||||
return pulse;
|
return pulse;
|
||||||
|
|
||||||
case Gap:
|
case Gap:
|
||||||
_bit_count = 13;
|
bit_count_ = 13;
|
||||||
pulse.type = (_phase_counter&1) ? Pulse::Low : Pulse::High;
|
pulse.type = (phase_counter_&1) ? Pulse::Low : Pulse::High;
|
||||||
pulse.length.length = 100;
|
pulse.length.length = 100;
|
||||||
return pulse;
|
return pulse;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
next_bit = _current_value & 1;
|
next_bit = current_value_ & 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,20 +153,20 @@ Tape::Pulse OricTAP::virtual_get_next_pulse()
|
|||||||
}
|
}
|
||||||
else
|
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;
|
pulse_counter_ ^= 1;
|
||||||
if(!_pulse_counter)
|
if(!pulse_counter_)
|
||||||
{
|
{
|
||||||
_current_value >>= 1;
|
current_value_ >>= 1;
|
||||||
_bit_count++;
|
bit_count_++;
|
||||||
}
|
}
|
||||||
return pulse;
|
return pulse;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OricTAP::is_at_end()
|
bool OricTAP::is_at_end()
|
||||||
{
|
{
|
||||||
return _phase == End;
|
return phase_ == End;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
#define OricTAP_hpp
|
#define OricTAP_hpp
|
||||||
|
|
||||||
#include "../Tape.hpp"
|
#include "../Tape.hpp"
|
||||||
#include <stdint.h>
|
#include "../../FileHolder.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
namespace Tape {
|
||||||
@ -18,7 +19,7 @@ namespace Tape {
|
|||||||
/*!
|
/*!
|
||||||
Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Constructs an @c OricTAP containing content from the file with name @c file_name.
|
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.
|
@throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP.
|
||||||
*/
|
*/
|
||||||
OricTAP(const char *file_name);
|
OricTAP(const char *file_name);
|
||||||
~OricTAP();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorNotOricTAP
|
ErrorNotOricTAP
|
||||||
@ -39,19 +39,16 @@ class OricTAP: public Tape {
|
|||||||
void virtual_reset();
|
void virtual_reset();
|
||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
|
|
||||||
FILE *_file;
|
|
||||||
size_t _file_length;
|
|
||||||
|
|
||||||
// byte serialisation and output
|
// byte serialisation and output
|
||||||
uint16_t _current_value;
|
uint16_t current_value_;
|
||||||
int _bit_count;
|
int bit_count_;
|
||||||
int _pulse_counter;
|
int pulse_counter_;
|
||||||
|
|
||||||
enum Phase {
|
enum Phase {
|
||||||
LeadIn, Header, Data, Gap, End
|
LeadIn, Header, Data, Gap, End
|
||||||
} _phase, _next_phase;
|
} phase_, next_phase_;
|
||||||
int _phase_counter;
|
int phase_counter_;
|
||||||
uint16_t _data_end_address, _data_start_address;
|
uint16_t data_end_address_, data_start_address_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,32 +48,25 @@
|
|||||||
|
|
||||||
using namespace Storage::Tape;
|
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,
|
// 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.
|
// 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;
|
throw ErrorBadFormat;
|
||||||
|
|
||||||
_file = fopen(file_name, "rb");
|
load_address_ = fgetc16le();
|
||||||
if(!_file) throw ErrorBadFormat;
|
length_ = (uint16_t)(file_stats_.st_size - 2);
|
||||||
|
|
||||||
_load_address = (uint16_t)fgetc(_file);
|
if (load_address_ + length_ >= 65536)
|
||||||
_load_address |= (uint16_t)fgetc(_file) << 8;
|
|
||||||
_length = (uint16_t)(file_stats.st_size - 2);
|
|
||||||
|
|
||||||
if (_load_address + _length >= 65536)
|
|
||||||
throw ErrorBadFormat;
|
throw ErrorBadFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRG::~PRG()
|
|
||||||
{
|
|
||||||
if(_file) fclose(_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
|
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
|
||||||
{
|
{
|
||||||
// these are all microseconds per pole
|
// 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 one_length = 247;
|
||||||
static const unsigned int marker_length = 328;
|
static const unsigned int marker_length = 328;
|
||||||
|
|
||||||
_bitPhase = (_bitPhase+1)&3;
|
bit_phase_ = (bit_phase_+1)&3;
|
||||||
if(!_bitPhase) get_next_output_token();
|
if(!bit_phase_) get_next_output_token();
|
||||||
|
|
||||||
Tape::Pulse pulse;
|
Tape::Pulse pulse;
|
||||||
pulse.length.clock_rate = 1000000;
|
pulse.length.clock_rate = 1000000;
|
||||||
pulse.type = (_bitPhase&1) ? Tape::Pulse::High : Tape::Pulse::Low;
|
pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low;
|
||||||
switch(_outputToken)
|
switch(output_token_)
|
||||||
{
|
{
|
||||||
case Leader: pulse.length.length = leader_zero_length; break;
|
case Leader: pulse.length.length = leader_zero_length; break;
|
||||||
case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break;
|
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break;
|
||||||
case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break;
|
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break;
|
||||||
case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break;
|
case WordMarker: pulse.length.length = (bit_phase_&2) ? one_length : marker_length; break;
|
||||||
case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_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;
|
case Silence: pulse.type = Tape::Pulse::Zero; pulse.length.length = 5000; break;
|
||||||
}
|
}
|
||||||
return pulse;
|
return pulse;
|
||||||
@ -102,16 +95,16 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
|
|||||||
|
|
||||||
void PRG::virtual_reset()
|
void PRG::virtual_reset()
|
||||||
{
|
{
|
||||||
_bitPhase = 3;
|
bit_phase_ = 3;
|
||||||
fseek(_file, 2, SEEK_SET);
|
fseek(file_, 2, SEEK_SET);
|
||||||
_filePhase = FilePhaseLeadIn;
|
file_phase_ = FilePhaseLeadIn;
|
||||||
_phaseOffset = 0;
|
phase_offset_ = 0;
|
||||||
_copy_mask = 0x80;
|
copy_mask_ = 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PRG::is_at_end()
|
bool PRG::is_at_end()
|
||||||
{
|
{
|
||||||
return _filePhase == FilePhaseAtEnd;
|
return file_phase_ == FilePhaseAtEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PRG::get_next_output_token()
|
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 leadin_length = 20000;
|
||||||
static const int block_leadin_length = 5000;
|
static const int block_leadin_length = 5000;
|
||||||
|
|
||||||
if(_filePhase == FilePhaseHeaderDataGap || _filePhase == FilePhaseAtEnd)
|
if(file_phase_ == FilePhaseHeaderDataGap || file_phase_ == FilePhaseAtEnd)
|
||||||
{
|
{
|
||||||
_outputToken = Silence;
|
output_token_ = Silence;
|
||||||
if(_filePhase != FilePhaseAtEnd) _filePhase = FilePhaseData;
|
if(file_phase_ != FilePhaseAtEnd) file_phase_ = FilePhaseData;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000
|
// 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
|
// 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;
|
output_token_ = Leader;
|
||||||
_phaseOffset++;
|
phase_offset_++;
|
||||||
if(_filePhase == FilePhaseLeadIn && _phaseOffset == leadin_length)
|
if(file_phase_ == FilePhaseLeadIn && phase_offset_ == leadin_length)
|
||||||
{
|
{
|
||||||
_phaseOffset = 0;
|
phase_offset_ = 0;
|
||||||
_filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
|
file_phase_ = (file_phase_ == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine whether a new byte needs to be queued up
|
// 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 bit_offset = block_offset % 10;
|
||||||
int byte_offset = block_offset / 10;
|
int byte_offset = block_offset / 10;
|
||||||
_phaseOffset++;
|
phase_offset_++;
|
||||||
|
|
||||||
if(!bit_offset &&
|
if(!bit_offset &&
|
||||||
(
|
(
|
||||||
(_filePhase == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
|
(file_phase_ == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) ||
|
||||||
feof(_file)
|
feof(file_)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_outputToken = EndOfBlock;
|
output_token_ = EndOfBlock;
|
||||||
_phaseOffset = 0;
|
phase_offset_ = 0;
|
||||||
|
|
||||||
switch(_filePhase)
|
switch(file_phase_)
|
||||||
{
|
{
|
||||||
default: break;
|
default: break;
|
||||||
case FilePhaseHeader:
|
case FilePhaseHeader:
|
||||||
_copy_mask ^= 0x80;
|
copy_mask_ ^= 0x80;
|
||||||
if(_copy_mask) _filePhase = FilePhaseHeaderDataGap;
|
if(copy_mask_) file_phase_ = FilePhaseHeaderDataGap;
|
||||||
break;
|
break;
|
||||||
case FilePhaseData:
|
case FilePhaseData:
|
||||||
_copy_mask ^= 0x80;
|
copy_mask_ ^= 0x80;
|
||||||
fseek(_file, 2, SEEK_SET);
|
fseek(file_, 2, SEEK_SET);
|
||||||
if(_copy_mask) _filePhase = FilePhaseAtEnd;
|
if(copy_mask_) file_phase_ = FilePhaseAtEnd;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
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
|
// the first nine bytes are countdown; the high bit is set if this is a header
|
||||||
if(byte_offset < countdown_bytes)
|
if(byte_offset < countdown_bytes)
|
||||||
{
|
{
|
||||||
_output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask;
|
output_byte_ = (uint8_t)(countdown_bytes - byte_offset) | copy_mask_;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(_filePhase == FilePhaseHeader)
|
if(file_phase_ == FilePhaseHeader)
|
||||||
{
|
{
|
||||||
if(byte_offset == countdown_bytes + block_length)
|
if(byte_offset == countdown_bytes + block_length)
|
||||||
{
|
{
|
||||||
_output_byte = _check_digit;
|
output_byte_ = check_digit_;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(byte_offset == countdown_bytes) _check_digit = 0;
|
if(byte_offset == countdown_bytes) check_digit_ = 0;
|
||||||
if(_filePhase == FilePhaseHeader)
|
if(file_phase_ == FilePhaseHeader)
|
||||||
{
|
{
|
||||||
switch(byte_offset - countdown_bytes)
|
switch(byte_offset - countdown_bytes)
|
||||||
{
|
{
|
||||||
case 0: _output_byte = 0x03; break;
|
case 0: output_byte_ = 0x03; break;
|
||||||
case 1: _output_byte = _load_address & 0xff; break;
|
case 1: output_byte_ = load_address_ & 0xff; break;
|
||||||
case 2: _output_byte = (_load_address >> 8)&0xff; break;
|
case 2: output_byte_ = (load_address_ >> 8)&0xff; break;
|
||||||
case 3: _output_byte = (_load_address + _length) & 0xff; break;
|
case 3: output_byte_ = (load_address_ + length_) & 0xff; break;
|
||||||
case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break;
|
case 4: output_byte_ = ((load_address_ + length_) >> 8) & 0xff; break;
|
||||||
|
|
||||||
case 5: _output_byte = 0x50; break; // P
|
case 5: output_byte_ = 0x50; break; // P
|
||||||
case 6: _output_byte = 0x52; break; // R
|
case 6: output_byte_ = 0x52; break; // R
|
||||||
case 7: _output_byte = 0x47; break; // G
|
case 7: output_byte_ = 0x47; break; // G
|
||||||
default:
|
default:
|
||||||
_output_byte = 0x20;
|
output_byte_ = 0x20;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,32 +207,32 @@ void PRG::get_next_output_token()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_output_byte = (uint8_t)fgetc(_file);
|
output_byte_ = (uint8_t)fgetc(file_);
|
||||||
if(feof(_file))
|
if(feof(file_))
|
||||||
{
|
{
|
||||||
_output_byte = _check_digit;
|
output_byte_ = check_digit_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_digit ^= _output_byte;
|
check_digit_ ^= output_byte_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(bit_offset)
|
switch(bit_offset)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
_outputToken = WordMarker;
|
output_token_ = WordMarker;
|
||||||
break;
|
break;
|
||||||
default: // i.e. 1–8
|
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;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
{
|
{
|
||||||
uint8_t parity = _output_byte;
|
uint8_t parity = output_byte_;
|
||||||
parity ^= (parity >> 4);
|
parity ^= (parity >> 4);
|
||||||
parity ^= (parity >> 2);
|
parity ^= (parity >> 2);
|
||||||
parity ^= (parity >> 1);
|
parity ^= (parity >> 1);
|
||||||
_outputToken = (parity&1) ? Zero : One;
|
output_token_ = (parity&1) ? Zero : One;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
#define Storage_Tape_PRG_hpp
|
#define Storage_Tape_PRG_hpp
|
||||||
|
|
||||||
#include "../Tape.hpp"
|
#include "../Tape.hpp"
|
||||||
#include <stdint.h>
|
#include "../../FileHolder.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
namespace Tape {
|
||||||
@ -18,7 +19,7 @@ namespace Tape {
|
|||||||
/*!
|
/*!
|
||||||
Provides a @c Tape containing a .PRG, which is a direct local file.
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type.
|
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.
|
@throws ErrorBadFormat if this file could not be opened and recognised as the specified type.
|
||||||
*/
|
*/
|
||||||
PRG(const char *file_name);
|
PRG(const char *file_name);
|
||||||
~PRG();
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ErrorBadFormat
|
ErrorBadFormat
|
||||||
@ -41,9 +41,8 @@ class PRG: public Tape {
|
|||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
void virtual_reset();
|
void virtual_reset();
|
||||||
|
|
||||||
FILE *_file;
|
uint16_t load_address_;
|
||||||
uint16_t _load_address;
|
uint16_t length_;
|
||||||
uint16_t _length;
|
|
||||||
|
|
||||||
enum FilePhase {
|
enum FilePhase {
|
||||||
FilePhaseLeadIn,
|
FilePhaseLeadIn,
|
||||||
@ -51,10 +50,10 @@ class PRG: public Tape {
|
|||||||
FilePhaseHeaderDataGap,
|
FilePhaseHeaderDataGap,
|
||||||
FilePhaseData,
|
FilePhaseData,
|
||||||
FilePhaseAtEnd
|
FilePhaseAtEnd
|
||||||
} _filePhase;
|
} file_phase_;
|
||||||
int _phaseOffset;
|
int phase_offset_;
|
||||||
|
|
||||||
int _bitPhase;
|
int bit_phase_;
|
||||||
enum OutputToken {
|
enum OutputToken {
|
||||||
Leader,
|
Leader,
|
||||||
Zero,
|
Zero,
|
||||||
@ -62,11 +61,11 @@ class PRG: public Tape {
|
|||||||
WordMarker,
|
WordMarker,
|
||||||
EndOfBlock,
|
EndOfBlock,
|
||||||
Silence
|
Silence
|
||||||
} _outputToken;
|
} output_token_;
|
||||||
void get_next_output_token();
|
void get_next_output_token();
|
||||||
uint8_t _output_byte;
|
uint8_t output_byte_;
|
||||||
uint8_t _check_digit;
|
uint8_t check_digit_;
|
||||||
uint8_t _copy_mask;
|
uint8_t copy_mask_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -74,22 +74,22 @@ static int gzget32(gzFile file)
|
|||||||
using namespace Storage::Tape;
|
using namespace Storage::Tape;
|
||||||
|
|
||||||
UEF::UEF(const char *file_name) :
|
UEF::UEF(const char *file_name) :
|
||||||
_time_base(1200),
|
time_base_(1200),
|
||||||
_is_at_end(false),
|
is_at_end_(false),
|
||||||
_pulse_pointer(0),
|
pulse_pointer_(0),
|
||||||
_is_300_baud(false)
|
is_300_baud_(false)
|
||||||
{
|
{
|
||||||
_file = gzopen(file_name, "rb");
|
file_ = gzopen(file_name, "rb");
|
||||||
|
|
||||||
char identifier[10];
|
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!"))
|
if(bytes_read < 10 || strcmp(identifier, "UEF File!"))
|
||||||
{
|
{
|
||||||
throw ErrorNotUEF;
|
throw ErrorNotUEF;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t version[2];
|
uint8_t version[2];
|
||||||
gzread(_file, version, 2);
|
gzread(file_, version, 2);
|
||||||
|
|
||||||
if(version[1] > 0 || version[0] > 10)
|
if(version[1] > 0 || version[0] > 10)
|
||||||
{
|
{
|
||||||
@ -101,41 +101,41 @@ UEF::UEF(const char *file_name) :
|
|||||||
|
|
||||||
UEF::~UEF()
|
UEF::~UEF()
|
||||||
{
|
{
|
||||||
gzclose(_file);
|
gzclose(file_);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Public methods
|
#pragma mark - Public methods
|
||||||
|
|
||||||
void UEF::virtual_reset()
|
void UEF::virtual_reset()
|
||||||
{
|
{
|
||||||
gzseek(_file, 12, SEEK_SET);
|
gzseek(file_, 12, SEEK_SET);
|
||||||
_is_at_end = false;
|
is_at_end_ = false;
|
||||||
parse_next_tape_chunk();
|
parse_next_tape_chunk();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UEF::is_at_end()
|
bool UEF::is_at_end()
|
||||||
{
|
{
|
||||||
return _is_at_end;
|
return is_at_end_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
|
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
|
||||||
{
|
{
|
||||||
Pulse next_pulse;
|
Pulse next_pulse;
|
||||||
|
|
||||||
if(_is_at_end)
|
if(is_at_end_)
|
||||||
{
|
{
|
||||||
next_pulse.type = Pulse::Zero;
|
next_pulse.type = Pulse::Zero;
|
||||||
next_pulse.length.length = _time_base * 4;
|
next_pulse.length.length = time_base_ * 4;
|
||||||
next_pulse.length.clock_rate = _time_base * 4;
|
next_pulse.length.clock_rate = time_base_ * 4;
|
||||||
return next_pulse;
|
return next_pulse;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_pulse = _queued_pulses[_pulse_pointer];
|
next_pulse = queued_pulses_[pulse_pointer_];
|
||||||
_pulse_pointer++;
|
pulse_pointer_++;
|
||||||
if(_pulse_pointer == _queued_pulses.size())
|
if(pulse_pointer_ == queued_pulses_.size())
|
||||||
{
|
{
|
||||||
_queued_pulses.clear();
|
queued_pulses_.clear();
|
||||||
_pulse_pointer = 0;
|
pulse_pointer_ = 0;
|
||||||
parse_next_tape_chunk();
|
parse_next_tape_chunk();
|
||||||
}
|
}
|
||||||
return next_pulse;
|
return next_pulse;
|
||||||
@ -145,18 +145,18 @@ Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
|
|||||||
|
|
||||||
void UEF::parse_next_tape_chunk()
|
void UEF::parse_next_tape_chunk()
|
||||||
{
|
{
|
||||||
while(!_queued_pulses.size())
|
while(queued_pulses_.empty())
|
||||||
{
|
{
|
||||||
// read chunk details
|
// read chunk details
|
||||||
uint16_t chunk_id = (uint16_t)gzget16(_file);
|
uint16_t chunk_id = (uint16_t)gzget16(file_);
|
||||||
uint32_t chunk_length = (uint32_t)gzget32(_file);
|
uint32_t chunk_length = (uint32_t)gzget32(file_);
|
||||||
|
|
||||||
// figure out where the next chunk will start
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,15 +176,15 @@ void UEF::parse_next_tape_chunk()
|
|||||||
case 0x0113: // change of base rate
|
case 0x0113: // change of base rate
|
||||||
{
|
{
|
||||||
// TODO: something smarter than just converting this to an int
|
// TODO: something smarter than just converting this to an int
|
||||||
float new_time_base = gzgetfloat(_file);
|
float new_time_base = gzgetfloat(file_);
|
||||||
_time_base = (unsigned int)roundf(new_time_base);
|
time_base_ = (unsigned int)roundf(new_time_base);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0117:
|
case 0x0117:
|
||||||
{
|
{
|
||||||
int baud_rate = gzget16(_file);
|
int baud_rate = gzget16(file_);
|
||||||
_is_300_baud = (baud_rate == 300);
|
is_300_baud_ = (baud_rate == 300);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ void UEF::parse_next_tape_chunk()
|
|||||||
break;
|
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--)
|
while(length--)
|
||||||
{
|
{
|
||||||
queue_implicit_byte(gzget8(_file));
|
queue_implicit_byte(gzget8(file_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEF::queue_explicit_bit_pattern(uint32_t length)
|
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;
|
uint8_t current_byte = 0;
|
||||||
for(size_t bit = 0; bit < length_in_bits; bit++)
|
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);
|
queue_bit(current_byte&1);
|
||||||
current_byte >>= 1;
|
current_byte >>= 1;
|
||||||
}
|
}
|
||||||
@ -222,30 +222,30 @@ void UEF::queue_explicit_bit_pattern(uint32_t length)
|
|||||||
void UEF::queue_integer_gap()
|
void UEF::queue_integer_gap()
|
||||||
{
|
{
|
||||||
Time duration;
|
Time duration;
|
||||||
duration.length = (unsigned int)gzget16(_file);
|
duration.length = (unsigned int)gzget16(file_);
|
||||||
duration.clock_rate = _time_base;
|
duration.clock_rate = time_base_;
|
||||||
_queued_pulses.emplace_back(Pulse::Zero, duration);
|
queued_pulses_.emplace_back(Pulse::Zero, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEF::queue_floating_point_gap()
|
void UEF::queue_floating_point_gap()
|
||||||
{
|
{
|
||||||
float length = gzgetfloat(_file);
|
float length = gzgetfloat(file_);
|
||||||
Time duration;
|
Time duration;
|
||||||
duration.length = (unsigned int)(length * 4000000);
|
duration.length = (unsigned int)(length * 4000000);
|
||||||
duration.clock_rate = 4000000;
|
duration.clock_rate = 4000000;
|
||||||
_queued_pulses.emplace_back(Pulse::Zero, duration);
|
queued_pulses_.emplace_back(Pulse::Zero, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEF::queue_carrier_tone()
|
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);
|
while(number_of_cycles--) queue_bit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UEF::queue_carrier_tone_with_dummy()
|
void UEF::queue_carrier_tone_with_dummy()
|
||||||
{
|
{
|
||||||
unsigned int pre_cycles = (unsigned int)gzget16(_file);
|
unsigned int pre_cycles = (unsigned int)gzget16(file_);
|
||||||
unsigned int post_cycles = (unsigned int)gzget16(_file);
|
unsigned int post_cycles = (unsigned int)gzget16(file_);
|
||||||
while(pre_cycles--) queue_bit(1);
|
while(pre_cycles--) queue_bit(1);
|
||||||
queue_implicit_byte(0xaa);
|
queue_implicit_byte(0xaa);
|
||||||
while(post_cycles--) queue_bit(1);
|
while(post_cycles--) queue_bit(1);
|
||||||
@ -253,33 +253,33 @@ void UEF::queue_carrier_tone_with_dummy()
|
|||||||
|
|
||||||
void UEF::queue_security_cycles()
|
void UEF::queue_security_cycles()
|
||||||
{
|
{
|
||||||
int number_of_cycles = gzget24(_file);
|
int number_of_cycles = gzget24(file_);
|
||||||
bool first_is_pulse = gzget8(_file) == 'P';
|
bool first_is_pulse = gzget8(file_) == 'P';
|
||||||
bool last_is_pulse = gzget8(_file) == 'P';
|
bool last_is_pulse = gzget8(file_) == 'P';
|
||||||
|
|
||||||
uint8_t current_byte = 0;
|
uint8_t current_byte = 0;
|
||||||
for(int cycle = 0; cycle < number_of_cycles; cycle++)
|
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);
|
int bit = (current_byte >> 7);
|
||||||
current_byte <<= 1;
|
current_byte <<= 1;
|
||||||
|
|
||||||
Time duration;
|
Time duration;
|
||||||
duration.length = bit ? 1 : 2;
|
duration.length = bit ? 1 : 2;
|
||||||
duration.clock_rate = _time_base * 4;
|
duration.clock_rate = time_base_ * 4;
|
||||||
|
|
||||||
if(!cycle && first_is_pulse)
|
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)
|
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
|
else
|
||||||
{
|
{
|
||||||
_queued_pulses.emplace_back(Pulse::Low, duration);
|
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||||
_queued_pulses.emplace_back(Pulse::High, duration);
|
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,9 +288,9 @@ void UEF::queue_defined_data(uint32_t length)
|
|||||||
{
|
{
|
||||||
if(length < 3) return;
|
if(length < 3) return;
|
||||||
|
|
||||||
int bits_per_packet = gzget8(_file);
|
int bits_per_packet = gzget8(file_);
|
||||||
char parity_type = (char)gzget8(_file);
|
char parity_type = (char)gzget8(file_);
|
||||||
int number_of_stop_bits = gzget8(_file);
|
int number_of_stop_bits = gzget8(file_);
|
||||||
|
|
||||||
bool has_extra_stop_wave = (number_of_stop_bits < 0);
|
bool has_extra_stop_wave = (number_of_stop_bits < 0);
|
||||||
number_of_stop_bits = abs(number_of_stop_bits);
|
number_of_stop_bits = abs(number_of_stop_bits);
|
||||||
@ -298,7 +298,7 @@ void UEF::queue_defined_data(uint32_t length)
|
|||||||
length -= 3;
|
length -= 3;
|
||||||
while(length--)
|
while(length--)
|
||||||
{
|
{
|
||||||
uint8_t byte = gzget8(_file);
|
uint8_t byte = gzget8(file_);
|
||||||
|
|
||||||
uint8_t parity_value = byte;
|
uint8_t parity_value = byte;
|
||||||
parity_value ^= (parity_value >> 4);
|
parity_value ^= (parity_value >> 4);
|
||||||
@ -326,9 +326,9 @@ void UEF::queue_defined_data(uint32_t length)
|
|||||||
{
|
{
|
||||||
Time duration;
|
Time duration;
|
||||||
duration.length = 1;
|
duration.length = 1;
|
||||||
duration.clock_rate = _time_base * 4;
|
duration.clock_rate = time_base_ * 4;
|
||||||
_queued_pulses.emplace_back(Pulse::Low, duration);
|
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||||
_queued_pulses.emplace_back(Pulse::High, duration);
|
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,7 +351,7 @@ void UEF::queue_bit(int bit)
|
|||||||
{
|
{
|
||||||
int number_of_cycles;
|
int number_of_cycles;
|
||||||
Time duration;
|
Time duration;
|
||||||
duration.clock_rate = _time_base * 4;
|
duration.clock_rate = time_base_ * 4;
|
||||||
|
|
||||||
if(bit)
|
if(bit)
|
||||||
{
|
{
|
||||||
@ -366,11 +366,11 @@ void UEF::queue_bit(int bit)
|
|||||||
number_of_cycles = 1;
|
number_of_cycles = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_is_300_baud) number_of_cycles *= 4;
|
if(is_300_baud_) number_of_cycles *= 4;
|
||||||
|
|
||||||
while(number_of_cycles--)
|
while(number_of_cycles--)
|
||||||
{
|
{
|
||||||
_queued_pulses.emplace_back(Pulse::Low, duration);
|
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||||
_queued_pulses.emplace_back(Pulse::High, duration);
|
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ class UEF : public Tape {
|
|||||||
void virtual_reset();
|
void virtual_reset();
|
||||||
Pulse virtual_get_next_pulse();
|
Pulse virtual_get_next_pulse();
|
||||||
|
|
||||||
gzFile _file;
|
gzFile file_;
|
||||||
unsigned int _time_base;
|
unsigned int time_base_;
|
||||||
bool _is_at_end;
|
bool is_at_end_;
|
||||||
bool _is_300_baud;
|
bool is_300_baud_;
|
||||||
|
|
||||||
std::vector<Pulse> _queued_pulses;
|
std::vector<Pulse> queued_pulses_;
|
||||||
size_t _pulse_pointer;
|
size_t pulse_pointer_;
|
||||||
|
|
||||||
void parse_next_tape_chunk();
|
void parse_next_tape_chunk();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user