1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-22 09:37:21 +00:00

Merge pull request #1458 from TomHarte/Plus4PRGs

Introdice alternative tape timings for the +4.
This commit is contained in:
Thomas Harte 2025-01-20 20:59:33 -05:00 committed by GitHub
commit 083c1b7ca7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
106 changed files with 1291 additions and 865 deletions

View File

@ -19,7 +19,7 @@ public:
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
if(joysticks.size() > index) {
joysticks_.push_back(joysticks[index].get());
}
}

View File

@ -18,6 +18,9 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
}
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
// TODO: copy media afresh for each target machine; media
// generally has mutable state.
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);

View File

@ -65,7 +65,8 @@ AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
auto target8bit = std::make_unique<ElectronTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
@ -76,8 +77,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
// If there are any tapes, attempt to get data from the first.
if(!media.tapes.empty()) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
std::vector<File> files = GetFiles(tape);
tape->reset();
auto serialiser = tape->serialiser();
std::vector<File> files = GetFiles(*serialiser);
// continue if there are any files
if(!files.empty()) {

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Acorn {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -16,7 +16,7 @@
using namespace Analyser::Static::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(
const std::shared_ptr<Storage::Tape::Tape> &tape,
Storage::Tape::TapeSerialiser &serialiser,
Storage::Tape::Acorn::Parser &parser
) {
auto new_chunk = std::make_unique<File::Chunk>();
@ -24,16 +24,16 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
// TODO: move this into the parser
const auto shift = [&] {
shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
};
// find next area of high tone
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
while(!serialiser.is_at_end() && (shift_register != 0x3ff)) {
shift();
}
// find next 0x2a (swallowing stop bit)
while(!tape->is_at_end() && (shift_register != 0x254)) {
while(!serialiser.is_at_end() && (shift_register != 0x254)) {
shift();
}
@ -43,8 +43,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
// read out name
char name[11];
std::size_t name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = char(parser.get_next_byte(tape));
while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = char(parser.get_next_byte(serialiser));
if(!name[name_ptr]) break;
++name_ptr;
}
@ -52,15 +52,15 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
new_chunk->name = name;
// addresses
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
const uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
uint16_t stored_header_crc = uint16_t(parser.get_next_short(serialiser));
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
@ -69,12 +69,12 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
uint16_t stored_data_crc = uint16_t(parser.get_next_short(serialiser));
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
} else {
@ -127,13 +127,13 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
return file;
}
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
Storage::Tape::Acorn::Parser parser;
// populate chunk list
std::deque<File::Chunk> chunk_list;
while(!tape->is_at_end()) {
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
while(!serialiser.is_at_end()) {
std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
if(chunk) {
chunk_list.push_back(*chunk);
}

View File

@ -15,6 +15,6 @@
namespace Analyser::Static::Acorn {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
}

View File

@ -12,10 +12,11 @@
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool is_confident
) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
if(media.disks.empty() && !is_confident) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Amiga {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -185,7 +185,7 @@ bool CheckBootSector(
return false;
}
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
// Limited sophistication here; look for a CPC-style file header, that is
// any Spectrum-esque block with a synchronisation character of 0x2c.
//
@ -194,7 +194,7 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Parser parser(Parser::MachineType::AmstradCPC);
while(true) {
const auto block = parser.find_block(tape);
const auto block = parser.find_block(serialiser);
if(!block) break;
if(block->type == 0x2c) {
@ -210,7 +210,8 @@ bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
TargetList destination;
auto target = std::make_unique<Target>();
@ -221,7 +222,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
if(!media.tapes.empty()) {
bool has_cpc_tape = false;
for(auto &tape: media.tapes) {
has_cpc_tape |= IsAmstradTape(tape);
const auto serialiser = tape->serialiser();
has_cpc_tape |= IsAmstradTape(*serialiser);
}
if(has_cpc_tape) {

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::AmstradCPC {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -12,7 +12,8 @@
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
auto target = std::make_unique<Target>();
target->media = media;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::AppleII {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -12,7 +12,8 @@
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
auto target = std::make_unique<Target>();
target->media = media;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::AppleIIgs {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -191,7 +191,8 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
// TODO: sanity checking; is this image really for an Atari 2600?
auto target = std::make_unique<Target>();

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Atari2600 {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -12,7 +12,8 @@
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::AtariST {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -55,12 +55,17 @@ ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartr
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
const bool is_confident
) {
TargetList targets;
auto target = std::make_unique<Target>(Machine::ColecoVision);
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(is_confident) {
target->media = media;
} else {
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
}
if(!target->media.empty())
targets.push_back(std::move(target));
return targets;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Coleco {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -160,145 +160,199 @@ std::optional<BASICAnalysis> analyse(const File &file) {
return analysis;
}
template <typename TargetT>
void set_loading_command(TargetT &target) {
if(target.media.disks.empty()) {
target.loading_command = "LOAD\"\",1,1\nRUN\n";
} else {
target.loading_command = "LOAD\"*\",8,1\nRUN\n";
}
}
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType
) {
TargetList destination;
auto target = std::make_unique<Target>();
bool obviously_uses_ted(const File &file) {
const auto analysis = analyse(file);
if(!analysis) return false;
// Disassemble.
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
file.data,
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
analysis->machine_code_addresses
);
// If FF3E or FF3F is touched, this is for the +4.
// TODO: probably require a very early touch.
for(const auto address: {0xff3e, 0xff3f}) {
for(const auto &collection: {
disassembly.external_loads,
disassembly.external_stores,
disassembly.external_modifies
}) {
if(collection.find(uint16_t(address)) != collection.end()) {
return true;
}
}
}
return false;
}
struct FileAnalysis {
int device = 0;
std::vector<File> files;
bool is_disk = false;
Analyser::Static::Media media;
};
// Strip out inappropriate cartridges.
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
template <TargetPlatform::IntType platform>
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
FileAnalysis analysis;
// Find all valid Commodore files on disks.
for(auto &disk : media.disks) {
std::vector<File> disk_files = GetFiles(disk);
if(!disk_files.empty()) {
is_disk = true;
files.insert(
files.end(),
analysis.is_disk = true;
analysis.files.insert(
analysis.files.end(),
std::make_move_iterator(disk_files.begin()),
std::make_move_iterator(disk_files.end())
);
target->media.disks.push_back(disk);
if(!device) device = 8;
analysis.media.disks.push_back(disk);
if(!analysis.device) analysis.device = 8;
}
}
// Find all valid Commodore files on tapes.
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
auto serialiser = tape->serialiser();
std::vector<File> tape_files = GetFiles(*serialiser);
if(!tape_files.empty()) {
files.insert(
files.end(),
analysis.files.insert(
analysis.files.end(),
std::make_move_iterator(tape_files.begin()),
std::make_move_iterator(tape_files.end())
);
target->media.tapes.push_back(tape);
if(!device) device = 1;
analysis.media.tapes.push_back(tape);
if(!analysis.device) analysis.device = 1;
}
}
// Inspect discovered files to try to divine machine and memory model.
auto vic_memory_model = Target::MemoryModel::Unexpanded;
return analysis;
}
auto it = files.begin();
while(it != files.end()) {
const auto &file = *it;
std::string loading_command(const FileAnalysis &file_analysis) {
std::ostringstream string_stream;
string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device;
const auto analysis = analyse(file_analysis.files[0]);
if(analysis && !analysis->machine_code_addresses.empty()) {
string_stream << ",1";
}
string_stream << "\nRUN\n";
return string_stream.str();
}
const auto analysis = analyse(file);
if(analysis && !analysis->machine_code_addresses.empty()) {
string_stream << ",1";
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
analyse_starting_address(uint16_t starting_address) {
switch(starting_address) {
case 0x1c01:
// TODO: assume C128.
default:
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
"Unrecognised loading address for Commodore program: %04x", starting_address);
[[fallthrough]];
case 0x1001:
return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
// Disassemble.
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
file.data,
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
analysis->machine_code_addresses
);
case 0x1201: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
case 0x0401: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
case 0x0801: return std::make_pair(TargetPlatform::C64, std::nullopt);
}
}
// If FF3E or FF3F is touched, this is for the +4.
// TODO: probably require a very early touch.
for(const auto address: {0xff3e, 0xff3f}) {
for(const auto &collection: {
disassembly.external_loads,
disassembly.external_stores,
disassembly.external_modifies
}) {
if(collection.find(uint16_t(address)) != collection.end()) {
target->machine = Machine::Plus4; // TODO: use a better target?
}
}
}
template <TargetPlatform::IntType platform>
std::unique_ptr<Analyser::Static::Target> get_target(
const Analyser::Static::Media &media,
const std::string &file_name,
bool is_confident
);
template<>
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
const Analyser::Static::Media &media,
const std::string &,
bool is_confident
) {
auto target = std::make_unique<Plus4Target>();
if(is_confident) {
target->media = media;
set_loading_command(*target);
} else {
const auto files = analyse_files<TargetPlatform::Plus4>(media);
if(!files.files.empty()) {
target->loading_command = loading_command(files);
}
target->media.disks = media.disks;
target->media.tapes = media.tapes;
}
string_stream << "\nRUN\n";
if(it == files.begin()) {
target->loading_command = string_stream.str();
// Attach a 1541 if there are any disks here.
target->has_c1541 = !target->media.disks.empty();
return std::move(target);
}
// Make a guess for the Vic-20, if ultimately selected, based on loading address.
// TODO: probably also discount other machines if starting address isn't 0x1001?
switch(files.front().starting_address) {
default:
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
"Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
[[fallthrough]];
case 0x1001:
vic_memory_model = Target::MemoryModel::Unexpanded;
// TODO: could be Vic-20 or Plus4.
break;
template<>
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
const Analyser::Static::Media &media,
const std::string &file_name,
bool is_confident
) {
auto target = std::make_unique<Vic20Target>();
const auto files = analyse_files<TargetPlatform::Vic20>(media);
if(!files.files.empty()) {
target->loading_command = loading_command(files);
case 0x1201:
vic_memory_model = Target::MemoryModel::ThirtyTwoKB;
target->machine = Machine::Vic20;
break;
case 0x0401:
vic_memory_model = Target::MemoryModel::EightKB;
target->machine = Machine::Vic20;
break;
case 0x0801:
// TODO: assume C64.
break;
case 0x1c01:
// TODO: assume C128.
break;
}
const auto model = analyse_starting_address(files.files[0].starting_address);
if(model.second.has_value()) {
target->set_memory_model(*model.second);
}
}
if(is_confident) {
target->media = media;
set_loading_command(*target);
} else {
// Strip out inappropriate cartridges but retain all tapes and disks.
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
target->media.disks = media.disks;
target->media.tapes = media.tapes;
}
for(const auto &file : files.files) {
// The Vic-20 never has RAM after 0x8000.
if(file.ending_address >= 0x8000) {
target->machine = Machine::Plus4;
return nullptr;
}
target->set_memory_model(vic_memory_model);
++it;
if(obviously_uses_ted(file)) {
return nullptr;
}
}
// Inspect filename for configuration hints.
if(!target->media.empty()) {
// Inspect filename for configuration hints.
using Region = Analyser::Static::Commodore::Vic20Target::Region;
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
// Hint 1: 'ntsc' anywhere in the name implies America.
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
target->region = Region::American;
}
// Potential additional hints: check for TheC64 tags.
// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
auto final_underscore = lowercase_name.find_last_of('_');
if(final_underscore != std::string::npos) {
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
@ -320,10 +374,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
target->region = Analyser::Static::Commodore::Target::Region::American;
target->region = Region::American;
}
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
target->region = Analyser::Static::Commodore::Target::Region::European;
target->region = Region::European;
}
// Unhandled:
@ -334,11 +388,36 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
// RO: this disk image should be treated as read-only.
}
}
}
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();
return std::move(target);
}
destination.push_back(std::move(target));
}
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType platforms,
bool is_confident
) {
TargetList destination;
if(platforms & TargetPlatform::Vic20) {
auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
if(vic20) {
destination.push_back(std::move(vic20));
}
}
if(platforms & TargetPlatform::Plus4) {
auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
if(plus4) {
destination.push_back(std::move(plus4));
}
}
return destination;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Commodore {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -12,15 +12,15 @@
using namespace Analyser::Static::Commodore;
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
Storage::Tape::Commodore::Parser parser;
std::vector<File> file_list;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
while(!tape->is_at_end()) {
while(!serialiser.is_at_end()) {
if(!header) {
header = parser.get_next_header(tape);
header = parser.get_next_header(serialiser);
continue;
}
@ -34,8 +34,8 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
new_file.type = File::DataSequence;
new_file.data.swap(header->data);
while(!tape->is_at_end()) {
header = parser.get_next_header(tape);
while(!serialiser.is_at_end()) {
header = parser.get_next_header(serialiser);
if(!header) continue;
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
@ -45,7 +45,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
case Storage::Tape::Commodore::Header::RelocatableProgram:
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
if(data) {
File &new_file = file_list.emplace_back();
new_file.name = header->name;
@ -58,12 +58,12 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
? File::RelocatableProgram : File::NonRelocatableProgram;
}
header = parser.get_next_header(tape);
header = parser.get_next_header(serialiser);
}
break;
default:
header = parser.get_next_header(tape);
header = parser.get_next_header(serialiser);
break;
}
}

View File

@ -13,6 +13,6 @@
namespace Analyser::Static::Commodore {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
}

View File

@ -15,7 +15,21 @@
namespace Analyser::Static::Commodore {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
// TODO: region, etc.
std::string loading_command;
bool has_c1541 = false;
Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
private:
friend Reflection::StructImpl<Plus4Target>;
void declare_fields() {
DeclareField(has_c1541);
}
};
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
enum class MemoryModel {
Unexpanded,
EightKB,
@ -54,10 +68,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
bool has_c1540 = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Vic20) {}
Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
private:
friend Reflection::StructImpl<Target>;
friend Reflection::StructImpl<Vic20Target>;
void declare_fields() {
DeclareField(enabled_ram.bank0);
DeclareField(enabled_ram.bank1);

View File

@ -50,7 +50,8 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::DiskII {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -29,7 +29,8 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Enterprise {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -19,7 +19,8 @@
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType platforms
TargetPlatform::IntType platforms,
bool
) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
@ -39,7 +40,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
// If the disk image is very small or large, map it to the PC. That's the only option old enough
// to have used 5.25" media.
if(disk->get_maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
}
// Attempt to grab MFM track 0, sector 1: the boot sector.
@ -53,7 +54,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
if(sector_map.empty() || sector_map.size() > 10) {
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
}
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
@ -82,7 +83,7 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
if(
std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
) {
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
}
}
@ -101,5 +102,5 @@ Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
// Being unable to prove that this is a PC disk, throw it to the Enterprise.
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms);
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms, false);
}

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::FAT12 {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -188,7 +188,8 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
TargetList destination;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::MSX {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -29,12 +29,12 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
Storage::Tape::BinaryTapePlayer tape_player(1000000);
tape_player.set_motor_control(true);
tape_player.set_tape(tape);
tape_player.set_tape(tape, TargetPlatform::MSX);
using Parser = Storage::Tape::MSX::Parser;
// Get all recognisable files from the tape.
while(!tape->is_at_end()) {
while(!tape_player.is_at_end()) {
// Try to locate and measure a header.
std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
if(!file_speed) continue;

View File

@ -12,10 +12,11 @@
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool is_confident
) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Macintosh {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -184,7 +184,8 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
auto target = std::make_unique<Target>();
target->confidence = 0.5;
@ -193,8 +194,8 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
int basic11_votes = 0;
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
auto serialiser = tape->serialiser();
std::vector<File> tape_files = GetFiles(*serialiser);
if(!tape_files.empty()) {
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Oric {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -11,57 +11,57 @@
using namespace Analyser::Static::Oric;
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
std::vector<File> files;
Storage::Tape::Oric::Parser parser;
while(!tape->is_at_end()) {
while(!serialiser.is_at_end()) {
// sync to next lead-in, check that it's one of three 0x16s
bool is_fast = parser.sync_and_get_encoding_speed(tape);
bool is_fast = parser.sync_and_get_encoding_speed(serialiser);
int next_bytes[2];
next_bytes[0] = parser.get_next_byte(tape, is_fast);
next_bytes[1] = parser.get_next_byte(tape, is_fast);
next_bytes[0] = parser.get_next_byte(serialiser, is_fast);
next_bytes[1] = parser.get_next_byte(serialiser, is_fast);
if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue;
// get the first byte that isn't a 0x16, check it was a 0x24
int byte = 0x16;
while(!tape->is_at_end() && byte == 0x16) {
byte = parser.get_next_byte(tape, is_fast);
while(!serialiser.is_at_end() && byte == 0x16) {
byte = parser.get_next_byte(serialiser, is_fast);
}
if(byte != 0x24) continue;
// skip two empty bytes
parser.get_next_byte(tape, is_fast);
parser.get_next_byte(tape, is_fast);
parser.get_next_byte(serialiser, is_fast);
parser.get_next_byte(serialiser, is_fast);
// get data and launch types
File new_file;
switch(parser.get_next_byte(tape, is_fast)) {
switch(parser.get_next_byte(serialiser, is_fast)) {
case 0x00: new_file.data_type = File::ProgramType::BASIC; break;
case 0x80: new_file.data_type = File::ProgramType::MachineCode; break;
default: new_file.data_type = File::ProgramType::None; break;
}
switch(parser.get_next_byte(tape, is_fast)) {
switch(parser.get_next_byte(serialiser, is_fast)) {
case 0x80: new_file.launch_type = File::ProgramType::BASIC; break;
case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break;
default: new_file.launch_type = File::ProgramType::None; break;
}
// read end and start addresses
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
new_file.ending_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
new_file.ending_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
new_file.starting_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
new_file.starting_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
// skip an empty byte
parser.get_next_byte(tape, is_fast);
parser.get_next_byte(serialiser, is_fast);
// read file name, up to 16 characters and null terminated
char file_name[17];
int name_pos = 0;
while(name_pos < 16) {
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
file_name[name_pos] = char(parser.get_next_byte(serialiser, is_fast));
if(!file_name[name_pos]) break;
name_pos++;
}
@ -72,11 +72,11 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
new_file.data.reserve(body_length);
for(std::size_t c = 0; c < body_length; c++) {
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
new_file.data.push_back(uint8_t(parser.get_next_byte(serialiser, is_fast)));
}
// only one validation check: was there enough tape?
if(!tape->is_at_end()) {
if(!serialiser.is_at_end()) {
files.push_back(new_file);
}
}

View File

@ -28,6 +28,6 @@ struct File {
std::vector<uint8_t> data;
};
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
}

View File

@ -12,7 +12,8 @@
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::PCCompatible {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -16,7 +16,8 @@
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
if(media.cartridges.empty())
return {};

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::Sega {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -127,9 +127,12 @@ public:
potential_platforms_ |= platforms;
// Check whether the instance itself has any input on target platforms.
TargetPlatform::TypeDistinguisher *const distinguisher =
dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get());
if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type();
TargetPlatform::Distinguisher *const distinguisher =
dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
if(distinguisher) {
was_distinguished = true;
potential_platforms_ &= distinguisher->target_platforms();
}
}
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
@ -160,6 +163,7 @@ public:
}
Media media;
bool was_distinguished = false;
private:
const std::string &file_name_;
@ -209,7 +213,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore, "d64");
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
@ -223,7 +227,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore, "g64");
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
@ -268,10 +272,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
if(accumulator.name_matches("prg")) {
// Try instantiating as a ROM; failing that accept as a tape.
try {
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore8bit, file_name);
} catch(...) {
try {
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore8bit, file_name);
} catch(...) {}
}
}
@ -286,7 +290,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "tap");
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
@ -335,13 +339,25 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// TODO: std::popcount here.
int total_options = 0;
TargetPlatform::IntType mask = 1;
while(mask) {
total_options += bool(potential_platforms & mask);
mask <<= 1;
}
const bool is_confident = total_options == 1;
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
if(!(potential_platforms & platform)) {
return;
}
auto new_targets = evaluator(media, file_name, potential_platforms);
auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
targets.insert(
targets.end(),
std::make_move_iterator(new_targets.begin()),
@ -357,7 +373,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
append(TargetPlatform::AtariST, AtariST::GetTargets);
append(TargetPlatform::Coleco, Coleco::GetTargets);
append(TargetPlatform::Commodore, Commodore::GetTargets);
append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
append(TargetPlatform::DiskII, DiskII::GetTargets);
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
append(TargetPlatform::FAT12, FAT12::GetTargets);
@ -369,13 +385,6 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
// Reset any tapes to their initial position.
for(const auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();
}
}
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
// picked their insertion order carefully.
std::stable_sort(targets.begin(), targets.end(),

View File

@ -14,6 +14,7 @@
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/TargetPlatforms.hpp"
#include "../../Reflection/Struct.hpp"
#include <memory>

View File

@ -14,14 +14,14 @@
#include "Target.hpp"
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
static std::vector<Storage::Data::ZX8081::File> GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
std::vector<Storage::Data::ZX8081::File> files;
Storage::Tape::ZX8081::Parser parser;
while(!tape->is_at_end()) {
std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
if(next_file != nullptr) {
files.push_back(*next_file);
while(!serialiser.is_at_end()) {
const auto next_file = parser.get_next_file(serialiser);
if(next_file) {
files.push_back(std::move(*next_file));
}
}
@ -31,12 +31,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType potential_platforms
TargetPlatform::IntType potential_platforms,
bool
) {
TargetList destination;
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
const auto serialiser = media.tapes.front()->serialiser();
std::vector<Storage::Data::ZX8081::File> files = GetFiles(*serialiser);
if(!files.empty()) {
Target *const target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::ZX8081 {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -18,7 +18,7 @@
namespace {
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
using Parser = Storage::Tape::ZXSpectrum::Parser;
Parser parser(Parser::MachineType::ZXSpectrum);
@ -106,7 +106,8 @@ bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType
TargetPlatform::IntType,
bool
) {
TargetList destination;
auto target = std::make_unique<Target>();
@ -115,7 +116,8 @@ Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
if(!media.tapes.empty()) {
bool has_spectrum_tape = false;
for(auto &tape: media.tapes) {
has_spectrum_tape |= IsSpectrumTape(tape);
auto serialiser = tape->serialiser();
has_spectrum_tape |= IsSpectrumTape(*serialiser);
}
if(has_spectrum_tape) {

View File

@ -14,6 +14,6 @@
namespace Analyser::Static::ZXSpectrum {
TargetList GetTargets(const Media &, const std::string &file_name, TargetPlatform::IntType potential_platforms);
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
}

View File

@ -186,7 +186,7 @@ public:
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_.set_tape(media.tapes.front());
tape_.set_tape(media.tapes.front(), TargetPlatform::AcornElectron);
}
set_use_fast_tape_hack();
@ -454,7 +454,7 @@ public:
int cycles_left_while_plausibly_in_data = 50;
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
while(!tape_.tape()->is_at_end()) {
while(!tape_.serialiser()->is_at_end()) {
tape_.run_for_input_pulse();
--cycles_left_while_plausibly_in_data;
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;

View File

@ -900,7 +900,7 @@ public:
// preceding symbol and be a short way into the pulse that should determine the
// first bit of this byte.
parser.process_pulse(tape_player_.current_pulse());
const auto byte = parser.get_byte(tape_player_.tape());
const auto byte = parser.get_byte(*tape_player_.serialiser());
auto flags = z80_.value_of(CPU::Z80::Register::Flags);
if(byte) {
@ -1125,7 +1125,7 @@ public:
bool insert_media(const Analyser::Static::Media &media) final {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front(), TargetPlatform::AmstradCPC);
set_use_fast_tape_hack();
}

View File

@ -21,6 +21,9 @@
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
@ -171,7 +174,7 @@ class ConcreteMachine:
public Machine,
public Utility::TypeRecipient<CharacterMapper> {
public:
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
ConcreteMachine(const Analyser::Static::Commodore::Plus4Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
interrupts_(*this),
timers_(interrupts_),
@ -212,6 +215,7 @@ public:
c1541_ = std::make_unique<C1540::Machine>(C1540::Personality::C1541, roms);
c1541_->set_serial_bus(serial_bus_);
Serial::attach(serial_port_, serial_bus_);
c1541_->run_for(Cycles(2000000));
}
tape_player_ = std::make_unique<Storage::Tape::BinaryTapePlayer>(clock);
@ -220,9 +224,10 @@ public:
joysticks_.emplace_back(std::make_unique<Joystick>());
insert_media(target.media);
// if(!target.loading_command.empty()) {
// type_string(target.loading_command);
// }
if(!target.loading_command.empty()) {
// Prefix a space as a delaying technique.
type_string(std::string(" ") + target.loading_command);
}
}
~ConcreteMachine() {
@ -296,6 +301,33 @@ public:
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
}
} else if(address < 0xfd00 || address >= 0xff40) {
if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && address == 0xe5fd) {
// TODO:
//
// ; read a dipole from tape (and then RTS)
// ;
// ; if c=1 then error
// ; else if v=1 then short
// ; else if n=0 then long
// ; else word
// ; end
// ; end
// ; end
// Compare with:
//
// dsamp1 *=*+2 ;time constant for x cell sample 07B8
// dsamp2 *=*+2 ;time constant for y cell sample
// zcell *=*+2 ;time constant for z cell verify
// const uint8_t dsamp1 = map_.read(0x7b8);
// const uint8_t dsamp2 = map_.read(0x7b9);
// const uint8_t zcell = map_.read(0x7ba);
//
//
// printf("rddipl: %d / %d / %d\n", dsamp1, dsamp2, zcell);
}
if(is_read(operation)) {
*value = map_.read(address);
} else {
@ -330,6 +362,10 @@ public:
*value = 0xff ^ (play_button_ ? 0x4 :0x0);
break;
case 0xfdd0:
*value = 0xff;
break;
default:
printf("TODO: read @ %04x\n", address);
break;
@ -340,12 +376,25 @@ public:
keyboard_mask_ = *value;
break;
case 0xfdd0: {
// const auto low = address & 3;
// const auto high = (address >> 2) & 3;
// TODO: set up ROMs.
} break;
default:
printf("TODO: write of %02x @ %04x\n", *value, address);
break;
}
}
} else {
const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
const bool is_from_rom =
(rom_is_paged_ && pc >= 0x8000) ||
(pc >= 0x400 && pc < 0x500) ||
(pc >= 0x700 && pc < 0x800);
bool is_hit = true;
if(is_read(operation)) {
switch(address) {
case 0xff00: *value = timers_.read<0>(); break;
@ -371,8 +420,8 @@ public:
const uint8_t joystick_mask =
0xff &
((joystick_mask_ & 0x02) ? 0xff : (joystick(1).mask() | 0x40)) &
((joystick_mask_ & 0x04) ? 0xff : (joystick(0).mask() | 0x80));
((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) &
((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80));
*value = keyboard_input & joystick_mask;
} break;
@ -407,6 +456,7 @@ public:
default:
printf("TODO: TED read at %04x\n", address);
is_hit = false;
}
} else {
switch(address) {
@ -500,8 +550,13 @@ public:
default:
printf("TODO: TED write at %04x\n", address);
is_hit = false;
}
}
if(!is_from_rom) {
printf("%04x\n", pc);
if(is_hit) confidence_.add_hit(); else confidence_.add_miss();
}
}
return length;
@ -538,10 +593,12 @@ private:
map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
rom_is_paged_ = true;
set_use_fast_tape();
}
void page_cpu_ram() {
map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
rom_is_paged_ = false;
set_use_fast_tape();
}
bool rom_is_paged_ = false;
@ -578,7 +635,7 @@ private:
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_->set_tape(media.tapes[0]);
tape_player_->set_tape(media.tapes[0], TargetPlatform::Plus4);
}
if(!media.disks.empty() && c1541_) {
@ -646,10 +703,14 @@ private:
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
bool play_button_ = false;
bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack.
void set_use_fast_tape() {}
bool use_fast_tape_hack_ = false;
void set_use_fast_tape() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_;
}
void update_tape_motor() {
const auto output = io_output_ | ~io_direction_;
tape_player_->set_motor_control(play_button_ && (~output & 0x08));
set_use_fast_tape();
}
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
@ -662,6 +723,13 @@ private:
}
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
// MARK: - Confidence.
Analyser::Dynamic::ConfidenceCounter confidence_;
float get_confidence() final { return confidence_.get_confidence(); }
std::string debug_type() final {
return "Plus4";
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
@ -684,7 +752,7 @@ std::unique_ptr<Machine> Machine::Plus4(
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
) {
using Target = Analyser::Static::Target;
using Target = Analyser::Static::Commodore::Plus4Target;
const Target *const commodore_target = dynamic_cast<const Target *>(target);
return std::make_unique<ConcreteMachine>(*commodore_target, rom_fetcher);
}

View File

@ -32,9 +32,12 @@ public:
pager_(pager),
interrupts_(interrupts)
{
const auto visible_lines = 33 * 8;
const auto centre = eos() - vs_stop() + 104; // i.e. centre on vertical_counter_ = 104.
crt_.set_visible_area(crt_.get_rect_for_area(
311 - 257 - 4,
208 + 8,
centre - (visible_lines / 2),
visible_lines,
int(HorizontalEvent::Begin40Columns) - int(HorizontalEvent::BeginSync) + int(HorizontalEvent::ScheduleCounterReset) + 1 - 8,
int(HorizontalEvent::End40Columns) - int(HorizontalEvent::Begin40Columns) + 16,
4.0f / 3.0f
@ -57,7 +60,7 @@ public:
case 0xff1a: return uint8_t(character_position_reload_ >> 8) | 0xfc;
case 0xff1b: return uint8_t(character_position_reload_);
case 0xff1c: return uint8_t(vertical_counter_ >> 8);
case 0xff1c: return uint8_t(vertical_counter_ >> 8) | 0xfe;
case 0xff1d: return uint8_t(vertical_counter_);
case 0xff1e: return uint8_t(horizontal_counter_ >> 1);
case 0xff1f:
@ -163,7 +166,7 @@ public:
case 0xff1a: load_high10(character_position_reload_); break;
case 0xff1b: load_low8(character_position_reload_); break;
case 0xff1c: vertical_counter_ = (vertical_counter_ & 0x00ff) | ((value & 3) << 8); break;
case 0xff1c: vertical_counter_ = (vertical_counter_ & 0x00ff) | ((value & 1) << 8); break;
case 0xff1d: vertical_counter_ = (vertical_counter_ & 0xff00) | value; break;
case 0xff1e:
// TODO: possibly should be deferred, if falling out of phase?

View File

@ -30,6 +30,7 @@
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include <algorithm>
@ -285,7 +286,7 @@ class ConcreteMachine:
public ClockingHint::Observer,
public Activity::Source {
public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
ConcreteMachine(const Analyser::Static::Commodore::Vic20Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
mos6560_(mos6560_bus_handler_),
user_port_via_(user_port_via_port_handler_),
@ -313,24 +314,25 @@ public:
ROM::Request request(ROM::Name::Vic20BASIC);
ROM::Name kernel, character;
using Region = Analyser::Static::Commodore::Vic20Target::Region;
switch(target.region) {
default:
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishPALKernel;
break;
case Analyser::Static::Commodore::Target::Region::American:
case Region::American:
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishNTSCKernel;
break;
case Analyser::Static::Commodore::Target::Region::Danish:
case Region::Danish:
character = ROM::Name::Vic20DanishCharacters;
kernel = ROM::Name::Vic20DanishKernel;
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
case Region::Japanese:
character = ROM::Name::Vic20JapaneseCharacters;
kernel = ROM::Name::Vic20JapaneseKernel;
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
case Region::Swedish:
character = ROM::Name::Vic20SwedishCharacters;
kernel = ROM::Name::Vic20SwedishKernel;
break;
@ -362,7 +364,7 @@ public:
}
// Determine PAL/NTSC
if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) {
if(target.region == Region::American || target.region == Region::Japanese) {
// NTSC
set_clock_rate(1022727);
mos6560_.set_output_mode(MOS::MOS6560::OutputMode::NTSC);
@ -439,7 +441,7 @@ public:
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front());
tape_->set_tape(media.tapes.front(), TargetPlatform::Vic20);
}
if(!media.disks.empty() && c1540_) {
@ -500,12 +502,20 @@ public:
const uint16_t address,
uint8_t *const value
) {
// run the phase-1 part of this cycle, in which the VIC accesses memory
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
cycles_since_mos6560_update_++;
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
// Run the phase-2 part of the cycle, which is whatever the 6502 said it should be.
const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000;
if(is_read(operation)) {
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
const auto page = processor_read_memory_map_[address >> 10];
uint8_t result;
if(!page) {
if(!is_from_rom) confidence_.add_miss();
result = 0xff;
} else {
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
}
if((address&0xfc00) == 0x9000) {
if(!(address&0x100)) {
update_video();
@ -522,9 +532,9 @@ public:
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
// So cancel that via a double NOP and fill in the next header programmatically.
Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->tape());
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(*tape_->serialiser());
const auto tape_position = tape_->tape()->offset();
const auto tape_position = tape_->serialiser()->offset();
if(header) {
// serialise to wherever b2:b3 points
const uint16_t tape_buffer_pointer = uint16_t(ram_[0xb2]) | uint16_t(ram_[0xb3] << 8);
@ -533,7 +543,7 @@ public:
logger.info().append("Found header");
} else {
// no header found, so pretend this hack never interceded
tape_->tape()->set_offset(tape_position);
tape_->serialiser()->set_offset(tape_position);
hold_tape_ = false;
logger.info().append("Didn't find header");
}
@ -547,8 +557,8 @@ public:
uint8_t x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
if(x == 0xe) {
Storage::Tape::Commodore::Parser parser;
const auto tape_position = tape_->tape()->offset();
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->tape());
const auto tape_position = tape_->serialiser()->offset();
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
if(data) {
uint16_t start_address, end_address;
start_address = uint16_t(ram_[0xc1] | (ram_[0xc2] << 8));
@ -558,8 +568,8 @@ public:
uint8_t *data_ptr = data->data.data();
std::size_t data_left = data->data.size();
while(data_left && start_address != end_address) {
uint8_t *page = processor_write_memory_map_[start_address >> 10];
if(page) page[start_address & 0x3ff] = *data_ptr;
uint8_t *const tape_page = processor_write_memory_map_[start_address >> 10];
if(tape_page) tape_page[start_address & 0x3ff] = *data_ptr;
data_ptr++;
start_address++;
data_left--;
@ -578,7 +588,7 @@ public:
hold_tape_ = true;
logger.info().append("Found data");
} else {
tape_->tape()->set_offset(tape_position);
tape_->serialiser()->set_offset(tape_position);
hold_tape_ = false;
logger.info().append("Didn't find data");
}
@ -586,7 +596,7 @@ public:
}
}
} else {
uint8_t *ram = processor_write_memory_map_[address >> 10];
uint8_t *const ram = processor_write_memory_map_[address >> 10];
if(ram) {
update_video();
ram[address & 0x3ff] = *value;
@ -602,6 +612,8 @@ public:
if(address & 0x10) user_port_via_.write(address, *value);
// The second VIA is selected by bit 5 = 1.
if(address & 0x20) keyboard_via_.write(address, *value);
} else if(!ram) {
if(!is_from_rom) confidence_.add_miss();
}
}
@ -752,6 +764,13 @@ private:
// Disk
std::unique_ptr<::Commodore::C1540::Machine> c1540_;
// MARK: - Confidence.
Analyser::Dynamic::ConfidenceCounter confidence_;
float get_confidence() final { return confidence_.get_confidence(); }
std::string debug_type() final {
return "Vic20";
}
};
}
@ -762,7 +781,7 @@ std::unique_ptr<Machine> Machine::Vic20(
const Analyser::Static::Target *const target,
const ROMMachine::ROMFetcher &rom_fetcher
) {
using Target = Analyser::Static::Commodore::Target;
using Target = Analyser::Static::Commodore::Vic20Target;
const Target *const commodore_target = dynamic_cast<const Target *>(target);
return std::make_unique<Vic20::ConcreteMachine>(*commodore_target, rom_fetcher);
}

View File

@ -431,7 +431,7 @@ class ConcreteMachine:
}
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front(), TargetPlatform::MSX);
}
if(!media.disks.empty()) {

View File

@ -20,7 +20,7 @@ namespace MachineTypes {
*/
struct MediaTarget {
/*!
Requests that the machine insert @c media as a modification to current state
Requests that the machine insert @c media as a modification to current state.
@returns @c true if any media was inserted; @c false otherwise.
*/

View File

@ -168,7 +168,7 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
@returns The next byte from the tape.
*/
uint8_t get_next_byte(bool use_fast_encoding) {
return uint8_t(parser_.get_next_byte(tape(), use_fast_encoding));
return uint8_t(parser_.get_next_byte(*serialiser(), use_fast_encoding));
}
private:
@ -440,7 +440,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
bool inserted = false;
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front(), TargetPlatform::Oric);
inserted = true;
}
@ -469,7 +469,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
use_fast_tape_hack_ &&
operation == CPU::MOS6502::BusOperation::ReadOpcode &&
tape_player_.has_tape() &&
!tape_player_.tape()->is_at_end()) {
!tape_player_.serialiser()->is_at_end()) {
uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]);
m6502_.set_value_of(CPU::MOS6502Esque::A, next_byte);

View File

@ -232,8 +232,8 @@ template<bool is_zx81> class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_) {
const uint64_t prior_offset = tape_player_.tape()->offset();
const int next_byte = parser_.get_next_byte(tape_player_.tape());
const uint64_t prior_offset = tape_player_.serialiser()->offset();
const int next_byte = parser_.get_next_byte(*tape_player_.serialiser());
if(next_byte != -1) {
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = uint8_t(next_byte);
@ -246,7 +246,7 @@ template<bool is_zx81> class ConcreteMachine:
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.tape()->set_offset(prior_offset);
tape_player_.serialiser()->set_offset(prior_offset);
}
}
@ -323,7 +323,7 @@ template<bool is_zx81> class ConcreteMachine:
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front(), is_zx81 ? TargetPlatform::ZX81 : TargetPlatform::ZX80);
}
set_use_fast_tape();

View File

@ -681,7 +681,7 @@ template<Model model> class ConcreteMachine:
bool insert_media(const Analyser::Static::Media &media) override {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front(), TargetPlatform::ZXSpectrum);
set_use_fast_tape();
}
@ -921,7 +921,7 @@ template<Model model> class ConcreteMachine:
if(!(flags & 1)) return false;
const uint8_t block_type = uint8_t(z80_.value_of(Register::ADash));
const auto block = parser.find_block(tape_player_.tape());
const auto block = parser.find_block(*tape_player_.serialiser());
if(!block || block_type != (*block).type) return false;
uint16_t length = z80_.value_of(Register::DE);
@ -930,7 +930,7 @@ template<Model model> class ConcreteMachine:
flags = 0x93;
uint8_t parity = 0x00;
while(length--) {
auto next = parser.get_byte(tape_player_.tape());
auto next = parser.get_byte(*tape_player_.serialiser());
if(!next) {
flags &= ~1;
break;
@ -941,7 +941,7 @@ template<Model model> class ConcreteMachine:
++target;
}
auto stored_parity = parser.get_byte(tape_player_.tape());
auto stored_parity = parser.get_byte(*tape_player_.serialiser());
if(!stored_parity) {
flags &= ~1;
} else {

View File

@ -264,9 +264,9 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
Add(Macintosh);
Add(MSX);
Add(Oric);
AddMapped(Plus4, Commodore);
options.emplace(LongNameForTargetMachine(Analyser::Machine::Plus4), std::make_unique<Analyser::Static::Commodore::Plus4Target>());
Add(PCCompatible);
AddMapped(Vic20, Commodore);
options.emplace(LongNameForTargetMachine(Analyser::Machine::Vic20), std::make_unique<Analyser::Static::Commodore::Vic20Target>());
Add(ZX8081);
Add(ZXSpectrum);

View File

@ -44,13 +44,19 @@ enum class Error {
receive the supplied static analyser result. The machine has been allocated
on the heap. It is the caller's responsibility to delete the class when finished.
*/
std::unique_ptr<DynamicMachine> MachineForTargets(const Analyser::Static::TargetList &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error);
std::unique_ptr<DynamicMachine> MachineForTargets(
const Analyser::Static::TargetList &,
const ::ROMMachine::ROMFetcher &,
Error &);
/*!
Allocates an instance of DynamicMaachine holding the machine described
by @c target. It is the caller's responsibility to delete the class when finished.
*/
std::unique_ptr<DynamicMachine> MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error);
std::unique_ptr<DynamicMachine> MachineForTarget(
const Analyser::Static::Target *,
const ROMMachine::ROMFetcher &,
Machine::Error &);
/*!
Returns a short string name for the machine identified by the target,
@ -90,6 +96,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> AllOptionsByMachineNa
NB: Usually the instances of Target can be dynamic_casted to Reflection::Struct in order to determine available properties.
*/
std::map<std::string, std::unique_ptr<Analyser::Static::Target>> TargetsByMachineName(bool meaningful_without_media_only);
std::map<std::string, std::unique_ptr<Analyser::Static::Target>>
TargetsByMachineName(bool meaningful_without_media_only);
}

View File

@ -163,8 +163,8 @@
- (instancetype)initWithCommodoreTEDModel:(CSMachineCommodoreTEDModel)model {
self = [super init];
if(self) {
using Target = Analyser::Static::Target;
auto target = std::make_unique<Target>(Analyser::Machine::Plus4);
using Target = Analyser::Static::Commodore::Plus4Target;
auto target = std::make_unique<Target>();
_targets.push_back(std::move(target));
}
return self;
@ -330,7 +330,7 @@
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540 {
self = [super init];
if(self) {
using Target = Analyser::Static::Commodore::Target;
using Target = Analyser::Static::Commodore::Vic20Target;
auto target = std::make_unique<Target>();
switch(region) {
case CSMachineVic20RegionDanish: target->region = Target::Region::Danish; break;

View File

@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="389"/>
<rect key="screenRect" x="0.0" y="0.0" width="1470" height="924"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="389"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -49,7 +49,7 @@ Gw
<action selector="cancelCreateMachine:" target="-2" id="lf8-PM-c0m"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="1" verticalHuggingPriority="1" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<textField horizontalHuggingPriority="1" verticalHuggingPriority="1" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<rect key="frame" x="18" y="14" width="405" height="32"/>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
<font key="font" metaFont="system"/>
@ -104,7 +104,7 @@ Gw
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<rect key="frame" x="18" y="348" width="554" height="21"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
@ -118,11 +118,11 @@ Gw
<tabViewItems>
<tabViewItem label="Amiga" identifier="amiga" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="254"/>
<rect key="frame" x="10" y="7" width="400" height="274"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qfH-1l-GXp">
<rect key="frame" x="110" y="210" width="80" height="25"/>
<rect key="frame" x="110" y="230" width="80" height="25"/>
<popUpButtonCell key="cell" type="push" title="512 kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="512" imageScaling="axesIndependently" inset="2" selectedItem="Zev-ku-jDG" id="vdO-VR-mUx">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
@ -135,16 +135,16 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6K-dt-stj">
<rect key="frame" x="18" y="216" width="89" height="16"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P6K-dt-stj">
<rect key="frame" x="18" y="236" width="89" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Chip Memory:" id="FIO-ZR-rsA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YD0-OJ-2bY">
<rect key="frame" x="18" y="186" width="87" height="16"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YD0-OJ-2bY">
<rect key="frame" x="18" y="206" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Fast Memory:" id="Rpz-39-jyt">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -152,7 +152,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="af8-pF-qc9">
<rect key="frame" x="108" y="180" width="72" height="25"/>
<rect key="frame" x="108" y="200" width="72" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="zV7-V8-c7s" id="39D-ms-pf9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
@ -202,7 +202,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c3g-96-b3x">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c3g-96-b3x">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="53v-92-jmf">
<font key="font" metaFont="system"/>
@ -226,7 +226,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
<font key="font" metaFont="system"/>
@ -234,7 +234,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<rect key="frame" x="18" y="186" width="98" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk Controller:" id="kbf-rc-Y4M">
<font key="font" metaFont="system"/>
@ -301,7 +301,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
<font key="font" metaFont="system"/>
@ -309,7 +309,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<rect key="frame" x="18" y="186" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="OLJ-nC-yyj">
<font key="font" metaFont="system"/>
@ -366,7 +366,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fLS-aL-xRV">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fLS-aL-xRV">
<rect key="frame" x="18" y="218" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title=" At present only a 4mb 26-bit Archimedes is available." id="Pof-hj-SZQ">
<font key="font" usesAppearanceFont="YES"/>
@ -388,7 +388,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dKg-qC-BBF">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dKg-qC-BBF">
<rect key="frame" x="18" y="216" width="58" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory:" id="ZBF-0r-RNK">
<font key="font" metaFont="system"/>
@ -426,7 +426,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="274"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Sbs-hE-B7B">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Sbs-hE-B7B">
<rect key="frame" x="18" y="238" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="At present only a 64kb Commodore 16 is available." id="Sh8-Y5-Giz">
<font key="font" usesAppearanceFont="YES"/>
@ -567,7 +567,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<rect key="frame" x="18" y="156" width="43" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
<font key="font" metaFont="system"/>
@ -575,7 +575,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<rect key="frame" x="18" y="216" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
<font key="font" metaFont="system"/>
@ -583,7 +583,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<rect key="frame" x="18" y="126" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
<font key="font" metaFont="system"/>
@ -591,7 +591,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<rect key="frame" x="18" y="96" width="36" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
<font key="font" metaFont="system"/>
@ -599,7 +599,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
<rect key="frame" x="17" y="186" width="47" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="sAw-C9-Sf7">
<font key="font" metaFont="system"/>
@ -645,7 +645,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
<font key="font" metaFont="system"/>
@ -705,7 +705,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="18" y="186" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
<font key="font" metaFont="system"/>
@ -713,7 +713,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFV-RB-7dB">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFV-RB-7dB">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="r7C-zE-gyj">
<font key="font" metaFont="system"/>
@ -768,7 +768,7 @@ Gw
<rect key="frame" x="10" y="7" width="400" height="254"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
<font key="font" metaFont="system"/>
@ -806,7 +806,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<rect key="frame" x="18" y="186" width="92" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk Interface:" id="SFK-hS-tFC">
<font key="font" metaFont="system"/>
@ -848,7 +848,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uhf-1k-ibT">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uhf-1k-ibT">
<rect key="frame" x="18" y="216" width="95" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Video Adaptor:" id="ROV-EU-T3W">
<font key="font" metaFont="system"/>
@ -856,7 +856,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8vF-eu-ClP">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8vF-eu-ClP">
<rect key="frame" x="18" y="186" width="47" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="qXc-wf-5jm">
<font key="font" metaFont="system"/>
@ -928,7 +928,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<rect key="frame" x="18" y="216" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
<font key="font" metaFont="system"/>
@ -936,7 +936,7 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<rect key="frame" x="18" y="186" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
<font key="font" metaFont="system"/>
@ -989,7 +989,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="18" y="216" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
<font key="font" metaFont="system"/>
@ -1037,7 +1037,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="216" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
@ -1078,7 +1078,7 @@ Gw
</menu>
</popUpButtonCell>
</popUpButton>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="216" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>

View File

@ -225,6 +225,8 @@ HEADERS += \
$$SRC/InstructionSets/x86/*.hpp \
\
$$SRC/Machines/*.hpp \
$$SRC/Machines/Acorn/Archimedes/*.hpp \
$$SRC/Machines/Acorn/Electron/*.hpp \
$$SRC/Machines/Amiga/*.hpp \
$$SRC/Machines/AmstradCPC/*.hpp \
$$SRC/Machines/Apple/ADB/*.hpp \
@ -238,7 +240,6 @@ HEADERS += \
$$SRC/Machines/Commodore/1540/Implementation/*.hpp \
$$SRC/Machines/Commodore/Plus4/*.hpp \
$$SRC/Machines/Commodore/Vic-20/*.hpp \
$$SRC/Machines/Electron/*.hpp \
$$SRC/Machines/Enterprise/*.hpp \
$$SRC/Machines/MasterSystem/*.hpp \
$$SRC/Machines/MSX/*.hpp \

View File

@ -1021,6 +1021,7 @@ void MainWindow::startMachine() {
TEST(macintosh);
TEST(msx);
TEST(oric);
TEST(plus4);
TEST(pc);
TEST(spectrum);
TEST(vic20);
@ -1261,8 +1262,15 @@ void MainWindow::start_spectrum() {
launchTarget(std::move(target));
}
void MainWindow::start_plus4() {
using Target = Analyser::Static::Commodore::Plus4Target;
auto target = std::make_unique<Target>();
target->has_c1541 = ui->plus4C1541CheckBox->isChecked();
launchTarget(std::move(target));
}
void MainWindow::start_vic20() {
using Target = Analyser::Static::Commodore::Target;
using Target = Analyser::Static::Commodore::Vic20Target;
auto target = std::make_unique<Target>();
switch(ui->vic20RegionComboBox->currentIndex()) {

View File

@ -106,6 +106,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
void start_macintosh();
void start_msx();
void start_oric();
void start_plus4();
void start_pc();
void start_spectrum();
void start_vic20();

View File

@ -881,6 +881,33 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="plus4Tab">
<attribute name="title">
<string>Plus 4</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="plus4C1541CheckBox">
<property name="text">
<string>Attach C-1541 disk drive</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pcTab">
<attribute name="title">
<string>PC Compatible</string>

View File

@ -10,14 +10,16 @@
using namespace Storage::Data::ZX8081;
static uint16_t short_at(std::size_t address, const std::vector<uint8_t> &data) {
namespace {
uint16_t short_at(std::size_t address, const std::vector<uint8_t> &data) {
return uint16_t(data[address] | (data[address + 1] << 8));
}
static std::shared_ptr<File> ZX80FileFromData(const std::vector<uint8_t> &data) {
static std::optional<File> ZX80FileFromData(const std::vector<uint8_t> &data) {
// Does this look like a ZX80 file?
if(data.size() < 0x28) return nullptr;
if(data.size() < 0x28) return std::nullopt;
// uint16_t next_line_number = short_at(0x2, data);
// uint16_t first_visible_line = short_at(0x13, data);
@ -27,11 +29,11 @@ static std::shared_ptr<File> ZX80FileFromData(const std::vector<uint8_t> &data)
uint16_t display_address = short_at(0xc, data);
// check that the end of file is contained within the supplied data
if(size_t(end_of_file - 0x4000) > data.size()) return nullptr;
if(size_t(end_of_file - 0x4000) > data.size()) return std::nullopt;
// check for the proper ordering of buffers
if(vars > end_of_file) return nullptr;
if(end_of_file > display_address) return nullptr;
if(vars > end_of_file) return std::nullopt;
if(end_of_file > display_address) return std::nullopt;
// TODO: does it make sense to inspect the tokenised BASIC?
// It starts at 0x4028 and proceeds as [16-bit line number] [tokens] [0x76],
@ -39,13 +41,13 @@ static std::shared_ptr<File> ZX80FileFromData(const std::vector<uint8_t> &data)
// TODO: check that the line numbers declared above exist (?)
auto file = std::make_shared<File>();
file->data = data;
file->isZX81 = false;
return file;
File file;
file.data = data;
file.isZX81 = false;
return std::move(file);
}
static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data) {
std::optional<File> ZX81FileFromData(const std::vector<uint8_t> &data) {
// Does this look like a ZX81 file?
// Look for a file name.
@ -57,10 +59,10 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
if(data[data_pointer] & 0x80) break;
data_pointer++;
}
if(!c) return nullptr;
if(!c) return std::nullopt;
data_pointer++;
if(data.size() < data_pointer + 0x405e - 0x4009) return nullptr;
if(data.size() < data_pointer + 0x405e - 0x4009) return std::nullopt;
// if(data[data_pointer]) return nullptr;
@ -69,7 +71,7 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
// uint16_t display_address = short_at(0x400c - 0x4009, data);
// check that the end of file is contained within the supplied data
if(data_pointer + end_of_file - 0x4009 > data.size()) return nullptr;
if(data_pointer + end_of_file - 0x4009 > data.size()) return std::nullopt;
// check for the proper ordering of buffers
// if(vars > end_of_file) return nullptr;
@ -81,15 +83,17 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
// TODO: check that the line numbers declared above exist (?)
auto file = std::make_shared<File>();
file->name = StringFromData(name_data, true);
file->data = data;
file->isZX81 = true;
return file;
File file;
file.name = StringFromData(name_data, true);
file.data = data;
file.isZX81 = true;
return std::move(file);
}
std::shared_ptr<File> Storage::Data::ZX8081::FileFromData(const std::vector<uint8_t> &data) {
std::shared_ptr<Storage::Data::ZX8081::File> result = ZX81FileFromData(data);
}
std::optional<File> Storage::Data::ZX8081::FileFromData(const std::vector<uint8_t> &data) {
const auto result = ZX81FileFromData(data);
if(result) return result;
return ZX80FileFromData(data);
}

View File

@ -9,7 +9,7 @@
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -21,7 +21,7 @@ struct File {
bool isZX81;
};
std::shared_ptr<File> FileFromData(const std::vector<uint8_t> &data);
std::optional<File> FileFromData(const std::vector<uint8_t> &data);
std::wstring StringFromData(const std::vector<uint8_t> &data, bool is_zx81);
std::vector<uint8_t> DataFromString(const std::wstring &string, bool is_zx81);

View File

@ -95,7 +95,7 @@ class DiskImageHolderBase: public Disk {
Implements TargetPlatform::TypeDistinguisher to return either no information whatsoever, if
the underlying image doesn't implement TypeDistinguisher, or else to pass the call along.
*/
template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher {
template <typename T> class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::Distinguisher {
public:
template <typename... Ts> DiskImageHolder(Ts&&... args) :
disk_image_(args...) {}
@ -112,9 +112,9 @@ public:
private:
T disk_image_;
TargetPlatform::Type target_platform_type() final {
if constexpr (std::is_base_of<TargetPlatform::TypeDistinguisher, T>::value) {
return static_cast<TargetPlatform::TypeDistinguisher *>(&disk_image_)->target_platform_type();
TargetPlatform::Type target_platforms() final {
if constexpr (std::is_base_of<TargetPlatform::Distinguisher, T>::value) {
return static_cast<TargetPlatform::Distinguisher *>(&disk_image_)->target_platforms();
} else {
return TargetPlatform::Type(~0);
}

View File

@ -24,7 +24,7 @@ namespace Storage::Disk {
of which is variably clocked (albeit not at flux transition resolution; as a result IPF files tend to be
close in size to more primitive formats).
*/
class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher {
class IPF: public DiskImage, public TargetPlatform::Distinguisher {
public:
/*!
Construct an @c IPF containing content from the file with name @c file_name.
@ -72,7 +72,7 @@ private:
std::map<Track::Address, TrackDescription> tracks_;
bool is_sps_format_ = false;
TargetPlatform::Type target_platform_type() final {
TargetPlatform::Type target_platforms() final {
return TargetPlatform::Type(platform_type_);
}
TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga;

View File

@ -59,9 +59,7 @@ namespace {
const uint8_t ascii_signature[] = TenX(0xea);
}
CAS::CAS(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CAS::Serialiser::Serialiser(const std::string &file_name) {
CAS::CAS(const std::string &file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
enum class Mode {
@ -69,7 +67,7 @@ CAS::Serialiser::Serialiser(const std::string &file_name) {
ASCII,
Binary,
BASIC
} parsing_mode_ = Mode::Seeking;
} parsing_mode = Mode::Seeking;
while(true) {
// Churn through the file until the next header signature is found.
@ -104,15 +102,15 @@ CAS::Serialiser::Serialiser(const std::string &file_name) {
const bool is_basic = !std::memcmp(type.data(), basic_signature, type.size());
const bool is_ascii = !std::memcmp(type.data(), ascii_signature, type.size());
switch(parsing_mode_) {
switch(parsing_mode) {
case Mode::Seeking: {
if(is_ascii || is_binary || is_basic) {
file.seek(header_position + 8, SEEK_SET);
chunks_.emplace_back(!chunks_.empty(), true, file.read(10 + 6));
if(is_ascii) parsing_mode_ = Mode::ASCII;
if(is_binary) parsing_mode_ = Mode::Binary;
if(is_basic) parsing_mode_ = Mode::BASIC;
if(is_ascii) parsing_mode = Mode::ASCII;
if(is_binary) parsing_mode = Mode::Binary;
if(is_basic) parsing_mode = Mode::BASIC;
} else {
// Raw data appears now. Grab its length and keep going.
file.seek(header_position + 8, SEEK_SET);
@ -127,7 +125,7 @@ CAS::Serialiser::Serialiser(const std::string &file_name) {
// Keep reading ASCII in 256-byte segments until a non-ASCII chunk arrives.
if(is_binary || is_basic || is_ascii) {
file.seek(header_position, SEEK_SET);
parsing_mode_ = Mode::Seeking;
parsing_mode = Mode::Seeking;
} else {
file.seek(header_position + 8, SEEK_SET);
chunks_.emplace_back(false, false, file.read(256));
@ -145,7 +143,7 @@ CAS::Serialiser::Serialiser(const std::string &file_name) {
const auto length = end_address - start_address + 1;
chunks_.emplace_back(false, false, file.read(size_t(length) + 6));
parsing_mode_ = Mode::Seeking;
parsing_mode = Mode::Seeking;
} break;
case Mode::BASIC: {
@ -164,12 +162,18 @@ CAS::Serialiser::Serialiser(const std::string &file_name) {
// Create the chunk and return to regular parsing.
file.seek(header_position + 8, SEEK_SET);
chunks_.emplace_back(false, false, file.read(size_t(length)));
parsing_mode_ = Mode::Seeking;
parsing_mode = Mode::Seeking;
} break;
}
}
}
std::unique_ptr<FormatSerialiser> CAS::format_serialiser() const {
return std::make_unique<Serialiser>(chunks_);
}
CAS::Serialiser::Serialiser(const std::vector<Chunk> &chunks) : chunks_(chunks) {}
bool CAS::Serialiser::is_at_end() const {
return phase_ == Phase::EndOfFile;
}

View File

@ -34,26 +34,30 @@ public:
};
private:
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
// Storage for the array of data blobs to transcribe into audio;
// each chunk is preceded by a header which may be long, and is optionally
// also preceded by a gap.
struct Chunk {
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) :
has_gap(has_gap), long_header(long_header), data(std::move(data)) {}
};
std::vector<Chunk> chunks_;
struct Serialiser: public FormatSerialiser {
Serialiser(const std::vector<Chunk> &chunks);
private:
bool is_at_end() const override;
void reset() override;
Pulse next_pulse() override;
// Storage for the array of data blobs to transcribe into audio;
// each chunk is preceded by a header which may be long, and is optionally
// also preceded by a gap.
struct Chunk {
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) :
has_gap(has_gap), long_header(long_header), data(std::move(data)) {}
};
std::vector<Chunk> chunks_;
const std::vector<Chunk> &chunks_;
// Tracker for active state within the file list.
std::size_t chunk_pointer_ = 0;
@ -65,7 +69,7 @@ private:
} phase_ = Phase::Header;
std::size_t distance_into_phase_ = 0;
std::size_t distance_into_bit_ = 0;
} serialiser_;
};
};
}

View File

@ -14,14 +14,7 @@
using namespace Storage::Tape;
CSW::CSW(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CSW::CSW(const std::vector<uint8_t> &&data, CompressionType type, bool initial_level, uint32_t sampling_rate) :
Tape(serialiser_),
serialiser_(std::move(data), type, initial_level, sampling_rate) {}
CSW::Serialiser::Serialiser(const std::string &file_name) : source_data_pointer_(0) {
CSW::CSW(const std::string &file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
if(file.stats().st_size < 0x20) throw ErrorNotCSW;
@ -42,21 +35,22 @@ CSW::Serialiser::Serialiser(const std::string &file_name) : source_data_pointer_
// The header now diverges based on version.
uint32_t number_of_waves = 0;
CompressionType compression_type;
if(major_version == 1) {
pulse_.length.clock_rate = file.get16le();
if(file.get8() != 1) throw ErrorNotCSW;
compression_type_ = CompressionType::RLE;
compression_type = CompressionType::RLE;
pulse_.type = (file.get8() & 1) ? Pulse::High : Pulse::Low;
file.seek(0x20, SEEK_SET);
} else {
pulse_.length.clock_rate = file.get32le();
number_of_waves = file.get32le();
number_of_waves = file.get32le(); // TODO: is this still useful?
switch(file.get8()) {
case 1: compression_type_ = CompressionType::RLE; break;
case 2: compression_type_ = CompressionType::ZRLE; break;
case 1: compression_type = CompressionType::RLE; break;
case 2: compression_type = CompressionType::ZRLE; break;
default: throw ErrorNotCSW;
}
@ -73,36 +67,42 @@ CSW::Serialiser::Serialiser(const std::string &file_name) : source_data_pointer_
file_data.resize(remaining_data);
file.read(file_data.data(), remaining_data);
if(compression_type_ == CompressionType::ZRLE) {
set_data(std::move(file_data), compression_type);
}
CSW::CSW(std::vector<uint8_t> &&data, CompressionType type, bool initial_level, uint32_t sampling_rate) {
set_data(std::move(data), type);
pulse_.length.clock_rate = sampling_rate;
pulse_.type = initial_level ? Pulse::Type::High : Pulse::Type::Low;
}
void CSW::set_data(std::vector<uint8_t> &&data, CompressionType type) {
// TODO: compression types.
if(type == CompressionType::ZRLE) {
// The only clue given by CSW as to the output size in bytes is that there will be
// number_of_waves waves. Waves are usually one byte, but may be five. So this code
// is pessimistic.
source_data_.resize(size_t(number_of_waves) * 5);
// source_data_.resize(size_t(number_of_waves) * 5);
// uncompress will tell how many compressed bytes there actually were, so use its
// modification of output_length to throw away all the memory that isn't actually
// needed.
uLongf output_length = uLongf(number_of_waves * 5);
uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size());
source_data_.resize(std::size_t(output_length));
// uLongf output_length = uLongf(number_of_waves * 5);
// uncompress(source_data_.data(), &output_length, file_data.data(), file_data.size());
// source_data_.resize(std::size_t(output_length));
} else {
source_data_ = std::move(file_data);
source_data_ = std::move(data);
}
invert_pulse();
}
CSW::Serialiser::Serialiser(
const std::vector<uint8_t> &&data,
const CompressionType compression_type,
const bool initial_level,
const uint32_t sampling_rate
) : compression_type_(compression_type) {
pulse_.length.clock_rate = sampling_rate;
pulse_.type = initial_level ? Pulse::High : Pulse::Low;
source_data_ = std::move(data);
std::unique_ptr<FormatSerialiser> CSW::format_serialiser() const {
return std::make_unique<Serialiser>(source_data_, pulse_);
}
CSW::Serialiser::Serialiser(const std::vector<uint8_t> &data, Pulse pulse) : pulse_(pulse), source_data_(data) {}
uint8_t CSW::Serialiser::get_next_byte() {
if(source_data_pointer_ == source_data_.size()) return 0xff;

View File

@ -36,33 +36,35 @@ public:
/*!
Constructs a @c CSW containing content as specified. Does not throw.
*/
CSW(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
CSW(std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
enum {
ErrorNotCSW
};
private:
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
Serialiser(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
void set_data(std::vector<uint8_t> &&data, CompressionType type);
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public FormatSerialiser {
Serialiser(const std::vector<uint8_t> &data, Pulse);
private:
// implemented to satisfy @c Tape
// implemented to satisfy @c FormatSerialiser
bool is_at_end() const override;
void reset() override;
Pulse next_pulse() override;
Pulse pulse_;
CompressionType compression_type_;
uint8_t get_next_byte();
uint32_t get_next_int32le();
void invert_pulse();
std::vector<uint8_t> source_data_;
std::size_t source_data_pointer_;
} serialiser_;
Pulse pulse_;
const std::vector<uint8_t> &source_data_;
std::size_t source_data_pointer_ = 0;
};
std::vector<uint8_t> source_data_;
Pulse pulse_;
};
}

View File

@ -12,44 +12,59 @@
using namespace Storage::Tape;
CommodoreTAP::CommodoreTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CommodoreTAP::CommodoreTAP(const std::string &file_name) : file_name_(file_name) {
Storage::FileHolder file(file_name);
CommodoreTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read)
{
const bool is_c64 = file_.check_signature("C64-TAPE-RAW");
file_.seek(0, SEEK_SET);
const bool is_c16 = file_.check_signature("C16-TAPE-RAW");
const bool is_c64 = file.check_signature("C64-TAPE-RAW");
file.seek(0, SEEK_SET);
const bool is_c16 = file.check_signature("C16-TAPE-RAW");
if(!is_c64 && !is_c16) {
throw ErrorNotCommodoreTAP;
}
type_ = is_c16 ? FileType::C16 : FileType::C64;
// const FileType type = is_c16 ? FileType::C16 : FileType::C64;
// Get and check the file version.
version_ = file_.get8();
if(version_ > 2) {
const uint8_t version = file.get8();
if(version > 2) {
throw ErrorNotCommodoreTAP;
}
updated_layout_ = version >= 1;
half_waves_ = version >= 2;
// Read clock rate-implying bytes.
platform_ = Platform(file_.get8());
video_ = VideoStandard(file_.get8());
file_.seek(1, SEEK_CUR);
platform_ = Platform(file.get8());
const VideoStandard video = VideoStandard(file.get8());
file.seek(1, SEEK_CUR);
// Read file size.
file_size_ = file_.get32le();
const bool double_clock = platform_ != Platform::C16 || !half_waves_; // TODO: is the platform check correct?
// Pick clock rate.
current_pulse_.length.clock_rate = static_cast<unsigned int>(
initial_pulse_.length.clock_rate = static_cast<unsigned int>(
[&] {
switch(platform_) {
default:
case Platform::Vic20: // It empirically seems like Vic-20 waves are counted with C64 timings?
case Platform::C64: return video_ == VideoStandard::PAL ? 985'248 : 1'022'727;
case Platform::C16: return video_ == VideoStandard::PAL ? 886'722 : 894'886;
case Platform::C64: return video == VideoStandard::PAL ? 985'248 : 1'022'727;
case Platform::C16: return video == VideoStandard::PAL ? 886'722 : 894'886;
}
}() * (double_clock() ? 2 : 1)
}() * (double_clock ? 2 : 1)
);
}
std::unique_ptr<FormatSerialiser> CommodoreTAP::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_, initial_pulse_, half_waves_, updated_layout_);
}
CommodoreTAP::Serialiser::Serialiser(
const std::string &file_name,
Pulse initial,
bool half_waves,
bool updated_layout) :
file_(file_name, FileHolder::FileMode::Read),
current_pulse_(initial),
half_waves_(half_waves),
updated_layout_(updated_layout)
{
reset();
}
@ -71,7 +86,7 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() {
const auto read_next_length = [&]() -> bool {
uint32_t next_length;
const uint8_t next_byte = file_.get8();
if(!updated_layout() || next_byte > 0) {
if(updated_layout_ || next_byte > 0) {
next_length = uint32_t(next_byte) << 3;
} else {
next_length = file_.get24le();
@ -88,7 +103,7 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() {
}
};
if(half_waves()) {
if(half_waves_) {
if(read_next_length()) {
current_pulse_.type = current_pulse_.type == Pulse::High ? Pulse::Low : Pulse::High;
}
@ -102,3 +117,14 @@ Storage::Tape::Pulse CommodoreTAP::Serialiser::next_pulse() {
return current_pulse_;
}
// MARK: - TargetPlatform::Distinguisher
TargetPlatform::Type CommodoreTAP::target_platforms() {
switch(platform_) {
default: return TargetPlatform::Type::Commodore;
case Platform::C64: return TargetPlatform::Type::C64;
case Platform::Vic20: return TargetPlatform::Type::Vic20;
case Platform::C16: return TargetPlatform::Type::Plus4;
}
}

View File

@ -11,6 +11,8 @@
#include "../Tape.hpp"
#include "../../FileHolder.hpp"
#include "../../TargetPlatforms.hpp"
#include <cstdint>
#include <string>
@ -19,7 +21,7 @@ namespace Storage::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 TargetPlatform::Distinguisher {
public:
/*!
Constructs a @c CommodoreTAP containing content from the file with name @c file_name.
@ -33,8 +35,25 @@ public:
};
private:
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
TargetPlatform::Type target_platforms() override;
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
enum class FileType {
C16, C64,
};
enum class Platform: uint8_t {
C64 = 0,
Vic20 = 1,
C16 = 2,
};
enum class VideoStandard: uint8_t {
PAL = 0,
NTSC1 = 1,
NTSC2 = 2,
};
struct Serialiser: public FormatSerialiser {
Serialiser(const std::string &file_name, Pulse initial, bool half_waves, bool updated_layout);
private:
bool is_at_end() const override;
@ -42,35 +61,17 @@ private:
Pulse next_pulse() override;
Storage::FileHolder file_;
uint32_t file_size_;
enum class FileType {
C16, C64,
} type_;
uint8_t version_;
enum class Platform: uint8_t {
C64 = 0,
Vic20 = 1,
C16 = 2,
} platform_;
enum class VideoStandard: uint8_t {
PAL = 0,
NTSC1 = 1,
NTSC2 = 2,
} video_;
bool updated_layout() const {
return version_ >= 1;
}
bool half_waves() const {
return version_ >= 2;
}
bool double_clock() const {
return platform_ != Platform::C16 || !half_waves();
}
Pulse current_pulse_;
bool half_waves_;
bool updated_layout_;
bool is_at_end_ = false;
} serialiser_;
};
std::string file_name_;
Pulse initial_pulse_;
bool half_waves_;
bool updated_layout_;
Platform platform_;
TargetPlatform::Type target_platforms_;
};
}

View File

@ -12,27 +12,29 @@
using namespace Storage::Tape;
OricTAP::OricTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
OricTAP::OricTAP(const std::string &file_name) : file_name_(file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
OricTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read)
{
// Check for a sequence of at least three 0x16s followed by a 0x24.
while(true) {
const uint8_t next = file_.get8();
const uint8_t next = file.get8();
if(next != 0x16 && next != 0x24) {
throw ErrorNotOricTAP;
}
if(next == 0x24) {
if(file_.tell() < 4) {
if(file.tell() < 4) {
throw ErrorNotOricTAP;
}
break;
}
}
}
// Rewind and start again.
std::unique_ptr<FormatSerialiser> OricTAP::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_);
}
OricTAP::Serialiser::Serialiser(const std::string &file_name) : file_(file_name, FileHolder::FileMode::Read) {
reset();
}

View File

@ -33,7 +33,9 @@ public:
};
private:
struct Serialiser: public TapeSerialiser {
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public FormatSerialiser {
Serialiser(const std::string &file_name);
private:
@ -53,7 +55,8 @@ private:
} phase_, next_phase_;
int phase_counter_;
uint16_t data_end_address_, data_start_address_;
} serialiser_;
};
std::string file_name_;
};
}

View File

@ -21,23 +21,26 @@ Log::Logger<Log::Source::TZX> logger;
}
TZX::TZX(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
TZX::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read),
current_level_(false) {
TZX::TZX(const std::string &file_name) : file_name_(file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
// Check for signature followed by a 0x1a
if(!file_.check_signature("ZXTape!")) throw ErrorNotTZX;
if(file_.get8() != 0x1a) throw ErrorNotTZX;
if(!file.check_signature("ZXTape!")) throw ErrorNotTZX;
if(file.get8() != 0x1a) throw ErrorNotTZX;
// Get version number
const uint8_t major_version = file_.get8();
const uint8_t minor_version = file_.get8();
const uint8_t major_version = file.get8();
const uint8_t minor_version = file.get8();
// Reject if an incompatible version
if(major_version != 1 || minor_version > 21) throw ErrorNotTZX;
}
std::unique_ptr<FormatSerialiser> TZX::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_);
}
TZX::Serialiser::Serialiser(const std::string &file_name) : file_(file_name, FileHolder::FileMode::Read) {
reset();
}
@ -113,14 +116,15 @@ void TZX::Serialiser::get_csw_recording_block() {
std::vector<uint8_t> raw_block = file_.read(block_length - 10);
CSW csw(
const CSW csw(
std::move(raw_block),
(compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE,
current_level_,
sampling_rate
);
while(!csw.is_at_end()) {
Pulse next_pulse = csw.next_pulse();
auto serialiser = csw.serialiser();
while(!serialiser->is_at_end()) {
Pulse next_pulse = serialiser->next_pulse();
current_level_ = (next_pulse.type == Pulse::High);
push_back(next_pulse);
}

View File

@ -32,6 +32,8 @@ public:
};
private:
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
@ -104,7 +106,8 @@ private:
void post_gap(unsigned int milliseconds);
void post_pulse(const Storage::Time &time);
} serialiser_;
};
std::string file_name_;
};
}

View File

@ -48,43 +48,52 @@
using namespace Storage::Tape;
PRG::PRG(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
PRG::PRG(const std::string &file_name) : file_name_(file_name) {
FileHolder file(file_name, FileHolder::FileMode::Read);
PRG::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read)
{
// There's really no way to validate other than that if this file is larger than 64kb,
// of if load address + length > 65536 then it's broken.
if(file_.stats().st_size >= 65538 || file_.stats().st_size < 3)
if(file.stats().st_size >= 65538 || file.stats().st_size < 3)
throw ErrorBadFormat;
load_address_ = file_.get16le();
length_ = uint16_t(file_.stats().st_size - 2);
load_address_ = file.get16le();
length_ = uint16_t(file.stats().st_size - 2);
if(load_address_ + length_ >= 65536)
throw ErrorBadFormat;
}
Storage::Tape::Pulse PRG::Serialiser::next_pulse() {
// The below are in microseconds per pole.
constexpr unsigned int leader_zero_length = 179;
constexpr unsigned int zero_length = 169;
constexpr unsigned int one_length = 247;
constexpr unsigned int marker_length = 328;
std::unique_ptr<FormatSerialiser> PRG::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_, load_address_, length_);
}
bit_phase_ = (bit_phase_+1)&3;
PRG::Serialiser::Serialiser(const std::string &file_name, uint16_t load_address, uint16_t length) :
file_(file_name, FileHolder::FileMode::Read),
load_address_(load_address),
length_(length),
timings_(false)
{
reset();
}
void PRG::Serialiser::set_target_platforms(TargetPlatform::Type type) {
timings_ = Timings(type & TargetPlatform::Type::Plus4);
}
Storage::Tape::Pulse PRG::Serialiser::next_pulse() {
bit_phase_ = (bit_phase_ + 1)&3;
if(!bit_phase_) get_next_output_token();
Pulse pulse;
pulse.length.clock_rate = 1'000'000;
pulse.type = (bit_phase_&1) ? Pulse::High : Pulse::Low;
switch(output_token_) {
case Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break;
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 = Pulse::Zero; pulse.length.length = 5000; break;
case Leader: pulse.length.length = timings_.leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? timings_.one_length : timings_.zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? timings_.zero_length : timings_.one_length; break;
case WordMarker: pulse.length.length = (bit_phase_&2) ? timings_.one_length : timings_.marker_length; break;
case EndOfBlock: pulse.length.length = (bit_phase_&2) ? timings_.zero_length : timings_.marker_length; break;
case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
}
return pulse;
}

View File

@ -10,6 +10,7 @@
#include "../Tape.hpp"
#include "../../FileHolder.hpp"
#include "../../TargetPlatforms.hpp"
#include <cstdint>
#include <string>
@ -34,15 +35,18 @@ public:
};
private:
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public FormatSerialiser, public TargetPlatform::Recipient {
Serialiser(const std::string &file_name, uint16_t load_address, uint16_t length);
void set_target_platforms(TargetPlatform::Type) override;
private:
bool is_at_end() const override;
Pulse next_pulse() override;
void reset() override;
FileHolder file_;
uint16_t load_address_;
uint16_t length_;
@ -68,7 +72,24 @@ private:
uint8_t output_byte_;
uint8_t check_digit_;
uint8_t copy_mask_ = 0x80;
} serialiser_;
struct Timings {
Timings(bool is_plus4) :
leader_zero_length( is_plus4 ? 240 : 179),
zero_length( is_plus4 ? 240 : 169),
one_length( is_plus4 ? 480 : 247),
marker_length( is_plus4 ? 960 : 328) {}
// The below are in microseconds per pole.
unsigned int leader_zero_length;
unsigned int zero_length;
unsigned int one_length;
unsigned int marker_length;
} timings_;
};
std::string file_name_;
uint16_t load_address_;
uint16_t length_;
};
}

View File

@ -15,14 +15,89 @@
#include "../../../Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::TapeUEF> logger;
}
// MARK: - ZLib extensions
using namespace Storage::Tape;
float gzgetfloat(gzFile file) {
UEF::Parser::Parser(const std::string &file_name) {
file_ = gzopen(file_name.c_str(), "rb");
char identifier[10];
const int bytes_read = gzread(file_, identifier, 10);
if(bytes_read < 10 || std::strcmp(identifier, "UEF File!")) {
throw Storage::Tape::UEF::ErrorNotUEF;
}
uint8_t version[2];
gzread(file_, version, 2);
if(version[1] > 0 || version[0] > 10) {
throw Storage::Tape::UEF::ErrorNotUEF;
}
start_of_next_chunk_ = gztell(file_);
}
void UEF::Parser::reset() {
start_of_next_chunk_ = 12;
}
UEF::Parser::~Parser() {
gzclose(file_);
}
template<>
uint8_t UEF::Parser::read<uint8_t>() {
// This is a workaround for gzgetc, which seems to be broken in ZLib 1.2.8.
uint8_t result;
gzread(file_, &result, 1);
return result;
}
template<>
uint16_t UEF::Parser::read<uint16_t>() {
uint8_t bytes[2];
gzread(file_, bytes, 2);
return uint16_t(bytes[0] | (bytes[1] << 8));
}
template<>
uint32_t UEF::Parser::read<uint32_t, 3>() {
uint8_t bytes[3];
gzread(file_, bytes, 3);
return uint32_t(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16));
}
template<>
uint32_t UEF::Parser::read<uint32_t>() {
uint8_t bytes[4];
gzread(file, bytes, 4);
gzread(file_, bytes, 4);
return uint32_t(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24));
}
std::optional<UEF::Parser::Chunk> UEF::Parser::next() {
gzseek(file_, start_of_next_chunk_, SEEK_SET);
const uint16_t chunk_id = read<uint16_t>();
const uint32_t chunk_length = read<uint32_t>();
const auto start = gztell(file_);
start_of_next_chunk_ = start + chunk_length;
if(gzeof(file_)) {
return std::nullopt;
}
return Chunk{
.id = chunk_id,
.length = chunk_length,
};
}
template<>
float UEF::Parser::read<float>() {
uint8_t bytes[4];
gzread(file_, bytes, 4);
/* assume a four byte array named Float exists, where Float[0]
was the first byte read from the UEF, Float[1] the second, etc */
@ -46,98 +121,58 @@ float gzgetfloat(gzFile file) {
return result;
}
uint8_t gzget8(gzFile file) {
// This is a workaround for gzgetc, which seems to be broken in ZLib 1.2.8.
uint8_t result;
gzread(file, &result, 1);
return result;
}
UEF::UEF(const std::string &file_name) : file_name_(file_name) {
Parser parser(file_name);
int gzget16(gzFile file) {
uint8_t bytes[2];
gzread(file, bytes, 2);
return bytes[0] | (bytes[1] << 8);
}
int gzget24(gzFile file) {
uint8_t bytes[3];
gzread(file, bytes, 3);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16);
}
int gzget32(gzFile file) {
uint8_t bytes[4];
gzread(file, bytes, 4);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
}
using namespace Storage::Tape;
UEF::UEF(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
UEF::Serialiser::Serialiser(const std::string &file_name) {
file_ = gzopen(file_name.c_str(), "rb");
char identifier[10];
const int bytes_read = gzread(file_, identifier, 10);
if(bytes_read < 10 || std::strcmp(identifier, "UEF File!")) {
throw ErrorNotUEF;
// If a chunk of type 0005 exists anywhere in the UEF then the UEF specifies its target machine.
// So check and, if so, update the list of machines for which this file thinks it is suitable.
while(const auto chunk = parser.next()) {
if(chunk->id == 0x0005) {
const uint8_t target = parser.read<uint8_t>();
switch(target >> 4) {
case 0: target_platforms_ = TargetPlatform::BBCModelA; break;
case 1: target_platforms_ = TargetPlatform::AcornElectron; break;
case 2: target_platforms_ = TargetPlatform::BBCModelB; break;
case 3: target_platforms_ = TargetPlatform::BBCMaster; break;
case 4: target_platforms_ = TargetPlatform::AcornAtom; break;
default: break;
}
}
}
}
uint8_t version[2];
gzread(file_, version, 2);
std::unique_ptr<FormatSerialiser> UEF::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_);
}
if(version[1] > 0 || version[0] > 10) {
throw ErrorNotUEF;
}
set_platform_type();
UEF::Serialiser::Serialiser(const std::string &file_name): parser_(file_name) {
}
UEF::Serialiser::~Serialiser() {
gzclose(file_);
}
// MARK: - Public methods
void UEF::Serialiser::reset() {
gzseek(file_, 12, SEEK_SET);
parser_.reset();
set_is_at_end(false);
clear();
}
// MARK: - Chunk navigator
bool UEF::Serialiser::get_next_chunk(Chunk &result) {
const uint16_t chunk_id = uint16_t(gzget16(file_));
const uint32_t chunk_length = uint32_t(gzget32(file_));
const z_off_t start_of_next_chunk = gztell(file_) + chunk_length;
if(gzeof(file_)) {
return false;
}
result.id = chunk_id;
result.length = chunk_length;
result.start_of_next_chunk = start_of_next_chunk;
return true;
}
void UEF::Serialiser::push_next_pulses() {
while(empty()) {
// read chunk details
Chunk next_chunk;
if(!get_next_chunk(next_chunk)) {
const auto next_chunk = parser_.next();
if(!next_chunk) {
set_is_at_end(true);
return;
}
switch(next_chunk.id) {
case 0x0100: queue_implicit_bit_pattern(next_chunk.length); break;
case 0x0102: queue_explicit_bit_pattern(next_chunk.length); break;
switch(next_chunk->id) {
case 0x0100: queue_implicit_bit_pattern(next_chunk->length); break;
case 0x0102: queue_explicit_bit_pattern(next_chunk->length); break;
case 0x0112: queue_integer_gap(); break;
case 0x0116: queue_floating_point_gap(); break;
@ -145,28 +180,26 @@ void UEF::Serialiser::push_next_pulses() {
case 0x0111: queue_carrier_tone_with_dummy(); break;
case 0x0114: queue_security_cycles(); break;
case 0x0104: queue_defined_data(next_chunk.length); break;
case 0x0104: queue_defined_data(next_chunk->length); break;
// change of base rate
case 0x0113: {
// TODO: something smarter than just converting this to an int
const float new_time_base = gzgetfloat(file_);
const float new_time_base = parser_.read<float>();
time_base_ = unsigned(roundf(new_time_base));
}
break;
case 0x0117: {
const int baud_rate = gzget16(file_);
const auto baud_rate = parser_.read<uint16_t>();
is_300_baud_ = (baud_rate == 300);
}
break;
default:
logger.info().append("Skipping chunk of type %04x", next_chunk.id);
logger.info().append("Skipping chunk of type %04x", next_chunk->id);
break;
}
gzseek(file_, next_chunk.start_of_next_chunk, SEEK_SET);
}
}
@ -174,15 +207,15 @@ void UEF::Serialiser::push_next_pulses() {
void UEF::Serialiser::queue_implicit_bit_pattern(uint32_t length) {
while(length--) {
queue_implicit_byte(gzget8(file_));
queue_implicit_byte(parser_.read<uint8_t>());
}
}
void UEF::Serialiser::queue_explicit_bit_pattern(const uint32_t length) {
const std::size_t length_in_bits = (length << 3) - size_t(gzget8(file_));
const std::size_t length_in_bits = (length << 3) - size_t(parser_.read<uint8_t>());
uint8_t current_byte = 0;
for(std::size_t bit = 0; bit < length_in_bits; bit++) {
if(!(bit&7)) current_byte = gzget8(file_);
if(!(bit&7)) current_byte = parser_.read<uint8_t>();
queue_bit(current_byte&1);
current_byte >>= 1;
}
@ -190,13 +223,13 @@ void UEF::Serialiser::queue_explicit_bit_pattern(const uint32_t length) {
void UEF::Serialiser::queue_integer_gap() {
Time duration;
duration.length = unsigned(gzget16(file_));
duration.length = parser_.read<uint16_t>();
duration.clock_rate = time_base_;
emplace_back(Pulse::Zero, duration);
}
void UEF::Serialiser::queue_floating_point_gap() {
const float length = gzgetfloat(file_);
const float length = parser_.read<float>();
Time duration;
duration.length = unsigned(length * 4000000);
duration.clock_rate = 4000000;
@ -204,26 +237,26 @@ void UEF::Serialiser::queue_floating_point_gap() {
}
void UEF::Serialiser::queue_carrier_tone() {
unsigned int number_of_cycles = unsigned(gzget16(file_));
auto number_of_cycles = parser_.read<uint16_t>();
while(number_of_cycles--) queue_bit(1);
}
void UEF::Serialiser::queue_carrier_tone_with_dummy() {
unsigned int pre_cycles = unsigned(gzget16(file_));
unsigned int post_cycles = unsigned(gzget16(file_));
auto pre_cycles = parser_.read<uint16_t>();
auto post_cycles = parser_.read<uint16_t>();
while(pre_cycles--) queue_bit(1);
queue_implicit_byte(0xaa);
while(post_cycles--) queue_bit(1);
}
void UEF::Serialiser::queue_security_cycles() {
int number_of_cycles = gzget24(file_);
bool first_is_pulse = gzget8(file_) == 'P';
bool last_is_pulse = gzget8(file_) == 'P';
auto number_of_cycles = parser_.read<uint32_t, 3>();
bool first_is_pulse = parser_.read<uint8_t>() == 'P';
bool last_is_pulse = parser_.read<uint8_t>() == 'P';
uint8_t current_byte = 0;
for(int cycle = 0; cycle < number_of_cycles; cycle++) {
if(!(cycle&7)) current_byte = gzget8(file_);
for(uint32_t cycle = 0; cycle < number_of_cycles; cycle++) {
if(!(cycle&7)) current_byte = parser_.read<uint8_t>();
int bit = (current_byte >> 7);
current_byte <<= 1;
@ -245,16 +278,16 @@ void UEF::Serialiser::queue_security_cycles() {
void UEF::Serialiser::queue_defined_data(uint32_t length) {
if(length < 3) return;
const int bits_per_packet = gzget8(file_);
const char parity_type = char(gzget8(file_));
int number_of_stop_bits = gzget8(file_);
const int bits_per_packet = parser_.read<uint8_t>();
const char parity_type = char(parser_.read<uint8_t>());
int number_of_stop_bits = parser_.read<uint8_t>();
const bool has_extra_stop_wave = (number_of_stop_bits < 0);
number_of_stop_bits = abs(number_of_stop_bits);
length -= 3;
while(length--) {
uint8_t byte = gzget8(file_);
uint8_t byte = parser_.read<uint8_t>();
uint8_t parity_value = byte;
parity_value ^= (parity_value >> 4);
@ -321,33 +354,8 @@ void UEF::Serialiser::queue_bit(const int bit) {
}
}
// MARK: - TypeDistinguisher
// MARK: - TargetPlatform::Distinguisher
TargetPlatform::Type UEF::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type UEF::Serialiser::target_platform_type() {
return platform_type_;
}
void UEF::Serialiser::set_platform_type() {
// If a chunk of type 0005 exists anywhere in the UEF then the UEF specifies its target machine.
// So check and, if so, update the list of machines for which this file thinks it is suitable.
Chunk next_chunk;
while(get_next_chunk(next_chunk)) {
if(next_chunk.id == 0x0005) {
uint8_t target = gzget8(file_);
switch(target >> 4) {
case 0: platform_type_ = TargetPlatform::BBCModelA; break;
case 1: platform_type_ = TargetPlatform::AcornElectron; break;
case 2: platform_type_ = TargetPlatform::BBCModelB; break;
case 3: platform_type_ = TargetPlatform::BBCMaster; break;
case 4: platform_type_ = TargetPlatform::AcornAtom; break;
default: break;
}
}
gzseek(file_, next_chunk.start_of_next_chunk, SEEK_SET);
}
reset();
TargetPlatform::Type UEF::target_platforms() {
return target_platforms_;
}

View File

@ -13,6 +13,7 @@
#include "../../TargetPlatforms.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <zlib.h>
@ -21,7 +22,7 @@ namespace Storage::Tape {
/*!
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
*/
class UEF : public Tape, public TargetPlatform::TypeDistinguisher {
class UEF : public Tape, public TargetPlatform::Distinguisher {
public:
/*!
Constructs a @c UEF containing content from the file with name @c file_name.
@ -35,31 +36,40 @@ public:
};
private:
TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type target_platforms() override;
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Parser {
Parser(const std::string &file_name);
~Parser();
struct Chunk {
uint16_t id;
uint32_t length;
};
std::optional<Chunk> next();
void reset();
template <typename TargetT, int num_bytes = 0> TargetT read();
private:
gzFile file_;
z_off_t start_of_next_chunk_;
};
struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
~Serialiser();
TargetPlatform::Type target_platform_type();
TargetPlatform::Type target_platforms();
private:
void reset() override;
void set_platform_type();
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
gzFile file_;
Parser parser_;
unsigned int time_base_ = 1200;
bool is_300_baud_ = false;
struct Chunk {
uint16_t id;
uint32_t length;
z_off_t start_of_next_chunk;
};
bool get_next_chunk(Chunk &);
void push_next_pulses() override;
void queue_implicit_bit_pattern(uint32_t length);
@ -76,7 +86,9 @@ private:
void queue_bit(int bit);
void queue_implicit_byte(uint8_t byte);
} serialiser_;
};
std::string file_name_;
TargetPlatform::Type target_platforms_ = TargetPlatform::Acorn;
};
}

View File

@ -11,9 +11,7 @@
using namespace Storage::Tape;
ZX80O81P::ZX80O81P(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
ZX80O81P::Serialiser::Serialiser(const std::string &file_name) {
ZX80O81P::ZX80O81P(const std::string &file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
// Grab the actual file contents
@ -22,17 +20,26 @@ ZX80O81P::Serialiser::Serialiser(const std::string &file_name) {
// If it's a ZX81 file, prepend a file name.
std::string type = file.extension();
platform_type_ = TargetPlatform::ZX80;
target_platforms_ = TargetPlatform::ZX80;
if(type == "p" || type == "81") {
// TODO, maybe: prefix a proper file name; this is leaving the file nameless.
data_.insert(data_.begin(), 0x80);
platform_type_ = TargetPlatform::ZX81;
target_platforms_ = TargetPlatform::ZX81;
}
std::shared_ptr<::Storage::Data::ZX8081::File> zx_file = Storage::Data::ZX8081::FileFromData(data_);
const auto zx_file = Storage::Data::ZX8081::FileFromData(data_);
if(!zx_file) throw ErrorNotZX80O81P;
}
// then rewind and start again
TargetPlatform::Type ZX80O81P::target_platforms() {
return target_platforms_;
}
std::unique_ptr<FormatSerialiser> ZX80O81P::format_serialiser() const {
return std::make_unique<Serialiser>(data_);
}
ZX80O81P::Serialiser::Serialiser(const std::vector<uint8_t> &data) : data_(data) {
reset();
}
@ -104,11 +111,3 @@ Pulse ZX80O81P::Serialiser::next_pulse() {
return pulse;
}
TargetPlatform::Type ZX80O81P::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type ZX80O81P::Serialiser::target_platform_type() {
return platform_type_;
}

View File

@ -22,7 +22,7 @@ namespace Storage::Tape {
/*!
Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture.
*/
class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher {
class ZX80O81P: public Tape, public TargetPlatform::Distinguisher {
public:
/*!
Constructs a @c ZX80O containing content from the file with name @c file_name.
@ -37,11 +37,11 @@ public:
private:
// TargetPlatform::TypeDistinguisher.
TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type target_platforms() override;
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
TargetPlatform::Type target_platform_type();
struct Serialiser: public FormatSerialiser {
Serialiser(const std::vector<uint8_t> &data);
private:
bool is_at_end() const override;
@ -49,17 +49,17 @@ private:
Pulse next_pulse() override;
bool has_finished_data() const;
TargetPlatform::Type platform_type_;
uint8_t byte_;
int bit_pointer_;
int wave_pointer_;
bool is_past_silence_, has_ended_final_byte_;
bool is_high_;
std::vector<uint8_t> data_;
const std::vector<uint8_t> &data_;
std::size_t data_pointer_;
} serialiser_;
};
TargetPlatform::Type target_platforms_;
std::vector<uint8_t> data_;
};
}

View File

@ -18,26 +18,30 @@ using namespace Storage::Tape;
https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format
*/
ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : file_name_(file_name) {
Storage::FileHolder file(file_name);
ZXSpectrumTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read)
{
// Check for a continuous series of blocks through to
// exactly file end.
//
// To consider: could also check those blocks of type 0
// and type ff for valid checksums?
while(file_.tell() != file_.stats().st_size) {
const uint16_t block_length = file_.get16le();
if(file_.eof() || file_.tell() + block_length > file_.stats().st_size) {
while(file.tell() != file.stats().st_size) {
const uint16_t block_length = file.get16le();
if(file.eof() || file.tell() + block_length > file.stats().st_size) {
throw ErrorNotZXSpectrumTAP;
}
file_.seek(block_length, SEEK_CUR);
file.seek(block_length, SEEK_CUR);
}
}
reset();
std::unique_ptr<FormatSerialiser> ZXSpectrumTAP::format_serialiser() const {
return std::make_unique<Serialiser>(file_name_);
}
ZXSpectrumTAP::Serialiser::Serialiser(const std::string &file_name) : file_(file_name, FileHolder::FileMode::Read) {
read_next_block();
}
bool ZXSpectrumTAP::Serialiser::is_at_end() const {

View File

@ -34,7 +34,9 @@ public:
};
private:
struct Serialiser: public TapeSerialiser {
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
struct Serialiser: public FormatSerialiser {
Serialiser(const std::string &file_name);
private:
Storage::FileHolder file_;
@ -54,7 +56,8 @@ private:
bool is_at_end() const override;
void reset() override;
Pulse next_pulse() override;
} serialiser_;
};
std::string file_name_;
};
}

View File

@ -18,13 +18,13 @@ Parser::Parser(): crc_(0x1021) {
shifter_.set_delegate(this);
}
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape) {
const SymbolType symbol = get_next_symbol(tape);
int Parser::get_next_bit(Storage::Tape::TapeSerialiser &serialiser) {
const SymbolType symbol = get_next_symbol(serialiser);
return (symbol == SymbolType::One) ? 1 : 0;
}
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
if(get_next_bit(tape)) {
int Parser::get_next_byte(Storage::Tape::TapeSerialiser &serialiser) {
if(get_next_bit(serialiser)) {
set_error_flag();
return -1;
}
@ -32,9 +32,9 @@ int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
int value = 0;
int c = 8;
while(c--) {
value = (value >> 1) | (get_next_bit(tape) << 7);
value = (value >> 1) | (get_next_bit(serialiser) << 7);
}
if(!get_next_bit(tape)) {
if(!get_next_bit(serialiser)) {
set_error_flag();
return -1;
}
@ -42,15 +42,15 @@ int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
return value;
}
unsigned int Parser::get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape) {
unsigned int result = unsigned(get_next_byte(tape));
result |= unsigned(get_next_byte(tape)) << 8;
unsigned int Parser::get_next_short(Storage::Tape::TapeSerialiser &serialiser) {
unsigned int result = unsigned(get_next_byte(serialiser));
result |= unsigned(get_next_byte(serialiser)) << 8;
return result;
}
unsigned int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &tape) {
unsigned int result = get_next_short(tape);
result |= get_next_short(tape) << 8;
unsigned int Parser::get_next_word(Storage::Tape::TapeSerialiser &serialiser) {
unsigned int result = get_next_short(serialiser);
result |= get_next_short(serialiser) << 8;
return result;
}

View File

@ -46,10 +46,10 @@ class Parser: public Storage::Tape::Parser<SymbolType>, public Shifter::Delegate
public:
Parser();
int get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &);
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &);
unsigned int get_next_short(const std::shared_ptr<Storage::Tape::Tape> &);
unsigned int get_next_word(const std::shared_ptr<Storage::Tape::Tape> &);
int get_next_bit(Storage::Tape::TapeSerialiser &);
int get_next_byte(Storage::Tape::TapeSerialiser &);
unsigned int get_next_short(Storage::Tape::TapeSerialiser &);
unsigned int get_next_word(Storage::Tape::TapeSerialiser &);
void reset_crc();
uint16_t get_crc() const;

View File

@ -20,10 +20,10 @@ Parser::Parser() :
Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Header> Parser::get_next_header(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::unique_ptr<Header> Parser::get_next_header(Storage::Tape::TapeSerialiser &serialiser) {
return duplicate_match<Header>(
get_next_header_body(tape, true),
get_next_header_body(tape, false)
get_next_header_body(serialiser, true),
get_next_header_body(serialiser, false)
);
}
@ -31,10 +31,10 @@ std::unique_ptr<Header> Parser::get_next_header(const std::shared_ptr<Storage::T
Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Data> Parser::get_next_data(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::unique_ptr<Data> Parser::get_next_data(Storage::Tape::TapeSerialiser &serialiser) {
return duplicate_match<Data>(
get_next_data_body(tape, true),
get_next_data_body(tape, false)
get_next_data_body(serialiser, true),
get_next_data_body(serialiser, false)
);
}
@ -43,7 +43,10 @@ std::unique_ptr<Data> Parser::get_next_data(const std::shared_ptr<Storage::Tape:
including setting the duplicate_matched flag.
*/
template<class ObjectType>
std::unique_ptr<ObjectType> Parser::duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> second_copy) {
std::unique_ptr<ObjectType> Parser::duplicate_match(
std::unique_ptr<ObjectType> first_copy,
std::unique_ptr<ObjectType> second_copy
) {
// if only one copy was parsed successfully, return it
if(!first_copy) return second_copy;
if(!second_copy) return first_copy;
@ -64,19 +67,19 @@ template<class ObjectType>
return std::move(*copy_to_return);
}
std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original) {
std::unique_ptr<Header> Parser::get_next_header_body(Storage::Tape::TapeSerialiser &serialiser, bool is_original) {
auto header = std::make_unique<Header>();
reset_error_flag();
// find and proceed beyond lead-in tone
proceed_to_symbol(tape, SymbolType::LeadIn);
proceed_to_symbol(serialiser, SymbolType::LeadIn);
// look for landing zone
proceed_to_landing_zone(tape, is_original);
proceed_to_landing_zone(serialiser, is_original);
reset_parity_byte();
// get header type
const uint8_t header_type = get_next_byte(tape);
const uint8_t header_type = get_next_byte(serialiser);
switch(header_type) {
default: header->type = Header::Unknown; break;
case 0x01: header->type = Header::RelocatableProgram; break;
@ -89,11 +92,11 @@ std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Stora
// grab rest of data
header->data.reserve(191);
for(std::size_t c = 0; c < 191; c++) {
header->data.push_back(get_next_byte(tape));
header->data.push_back(get_next_byte(serialiser));
}
const uint8_t parity_byte = get_parity_byte();
header->parity_was_valid = get_next_byte(tape) == parity_byte;
header->parity_was_valid = get_next_byte(serialiser) == parity_byte;
// parse if this is not pure data
if(header->type != Header::DataBlock) {
@ -125,20 +128,20 @@ void Header::serialise(uint8_t *target, [[maybe_unused]] uint16_t length) {
std::memcpy(&target[1], data.data(), 191);
}
std::unique_ptr<Data> Parser::get_next_data_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original) {
std::unique_ptr<Data> Parser::get_next_data_body(Storage::Tape::TapeSerialiser &serialiser, bool is_original) {
auto data = std::make_unique<Data>();
reset_error_flag();
// find and proceed beyond lead-in tone to the next landing zone
proceed_to_symbol(tape, SymbolType::LeadIn);
proceed_to_landing_zone(tape, is_original);
proceed_to_symbol(serialiser, SymbolType::LeadIn);
proceed_to_landing_zone(serialiser, is_original);
reset_parity_byte();
// accumulate until the next non-word marker is hit
while(!tape->is_at_end()) {
const SymbolType start_symbol = get_next_symbol(tape);
while(!serialiser.is_at_end()) {
const SymbolType start_symbol = get_next_symbol(serialiser);
if(start_symbol != SymbolType::Word) break;
data->data.push_back(get_next_byte_contents(tape));
data->data.push_back(get_next_byte_contents(serialiser));
}
// the above has reead the parity byte to the end of the data; if it matched the calculated parity it'll now be zero
@ -154,11 +157,11 @@ std::unique_ptr<Data> Parser::get_next_data_body(const std::shared_ptr<Storage::
/*!
Finds and completes the next landing zone.
*/
void Parser::proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original) {
void Parser::proceed_to_landing_zone(Storage::Tape::TapeSerialiser &serialiser, bool is_original) {
uint8_t landing_zone[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
while(!tape->is_at_end()) {
while(!serialiser.is_at_end()) {
memmove(landing_zone, &landing_zone[1], sizeof(uint8_t) * 8);
landing_zone[8] = get_next_byte(tape);
landing_zone[8] = get_next_byte(serialiser);
bool is_landing_zone = true;
for(int c = 0; c < 9; c++) {
@ -174,8 +177,8 @@ void Parser::proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape>
/*!
Swallows the next byte; sets the error flag if it is not equal to @c value.
*/
void Parser::expect_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, uint8_t value) {
const uint8_t next_byte = get_next_byte(tape);
void Parser::expect_byte(Storage::Tape::TapeSerialiser &serialiser, uint8_t value) {
const uint8_t next_byte = get_next_byte(serialiser);
if(next_byte != value) set_error_flag();
}
@ -186,9 +189,9 @@ void Parser::add_parity_byte(uint8_t byte) { parity_byte_ ^= byte; }
/*!
Proceeds to the next word marker then returns the result of @c get_next_byte_contents.
*/
uint8_t Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
proceed_to_symbol(tape, SymbolType::Word);
return get_next_byte_contents(tape);
uint8_t Parser::get_next_byte(Storage::Tape::TapeSerialiser &serialiser) {
proceed_to_symbol(serialiser, SymbolType::Word);
return get_next_byte_contents(serialiser);
}
/*!
@ -196,11 +199,11 @@ uint8_t Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape)
Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not
::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight.
*/
uint8_t Parser::get_next_byte_contents(const std::shared_ptr<Storage::Tape::Tape> &tape) {
uint8_t Parser::get_next_byte_contents(Storage::Tape::TapeSerialiser &serialiser) {
int byte_plus_parity = 0;
int c = 9;
while(c--) {
const SymbolType next_symbol = get_next_symbol(tape);
const SymbolType next_symbol = get_next_symbol(serialiser);
if((next_symbol != SymbolType::One) && (next_symbol != SymbolType::Zero)) set_error_flag();
byte_plus_parity = (byte_plus_parity >> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8);
}
@ -219,9 +222,9 @@ uint8_t Parser::get_next_byte_contents(const std::shared_ptr<Storage::Tape::Tape
/*!
Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format.
*/
uint16_t Parser::get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape) {
uint16_t value = get_next_byte(tape);
value |= get_next_byte(tape) << 8;
uint16_t Parser::get_next_short(Storage::Tape::TapeSerialiser &serialiser) {
uint16_t value = get_next_byte(serialiser);
value |= get_next_byte(serialiser) << 8;
return value;
}

View File

@ -61,13 +61,13 @@ public:
Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Header> get_next_header(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::unique_ptr<Header> get_next_header(Storage::Tape::TapeSerialiser &);
/*!
Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Data> get_next_data(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::unique_ptr<Data> get_next_data(Storage::Tape::TapeSerialiser &);
private:
/*!
@ -75,20 +75,21 @@ private:
including setting the duplicate_matched flag.
*/
template<class ObjectType>
std::unique_ptr<ObjectType> duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> second_copy);
std::unique_ptr<ObjectType> duplicate_match(
std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> second_copy);
std::unique_ptr<Header> get_next_header_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
std::unique_ptr<Data> get_next_data_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
std::unique_ptr<Header> get_next_header_body(Storage::Tape::TapeSerialiser &, bool is_original);
std::unique_ptr<Data> get_next_data_body(Storage::Tape::TapeSerialiser &, bool is_original);
/*!
Finds and completes the next landing zone.
*/
void proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
void proceed_to_landing_zone(Storage::Tape::TapeSerialiser &, bool is_original);
/*!
Swallows the next byte; sets the error flag if it is not equal to @c value.
*/
void expect_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, uint8_t value);
void expect_byte(Storage::Tape::TapeSerialiser &, uint8_t value);
uint8_t parity_byte_ = 0;
void reset_parity_byte();
@ -98,19 +99,19 @@ private:
/*!
Proceeds to the next word marker then returns the result of @c get_next_byte_contents.
*/
uint8_t get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);
uint8_t get_next_byte(Storage::Tape::TapeSerialiser &);
/*!
Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One.
Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not
::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight.
*/
uint8_t get_next_byte_contents(const std::shared_ptr<Storage::Tape::Tape> &tape);
uint8_t get_next_byte_contents(Storage::Tape::TapeSerialiser &);
/*!
Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format.
*/
uint16_t get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape);
uint16_t get_next_short(Storage::Tape::TapeSerialiser &);
/*!
Per the contract with Analyser::Static::TapeParser; sums time across pulses. If this pulse

View File

@ -25,7 +25,7 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
float low = std::numeric_limits<float>::max();
float high = std::numeric_limits<float>::min();
int samples = 0;
while(!tape_player.tape()->is_at_end()) {
while(!tape_player.is_at_end()) {
float next_length = 0.0f;
do {
next_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
@ -43,14 +43,14 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
if(samples == 1111*2) break; // Cycles are read, not half-cycles.
}
if(tape_player.tape()->is_at_end()) return nullptr;
if(tape_player.is_at_end()) return nullptr;
/*
"The next 256 cycles are then read (1B34H) and averaged to determine the cassette HI cycle length."
*/
float total_length = 0.0f;
samples = 512;
while(!tape_player.tape()->is_at_end()) {
while(!tape_player.is_at_end()) {
total_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
if(tape_player.input() != last_level) {
samples--;
@ -60,7 +60,7 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
tape_player.run_for_input_pulse();
}
if(tape_player.tape()->is_at_end()) return nullptr;
if(tape_player.is_at_end()) return nullptr;
/*
This figure is multiplied by 1.5 and placed in LOWLIM where it defines the minimum acceptable length
@ -103,7 +103,7 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
*/
const float minimum_start_bit_duration = float(speed.minimum_start_bit_duration) * 0.00001145f * 0.5f;
int input = 0;
while(!tape_player.tape()->is_at_end()) {
while(!tape_player.is_at_end()) {
// Find next transition.
bool level = tape_player.input();
float duration = 0.0;
@ -132,11 +132,11 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
);
int bits_left = 8;
bool level = tape_player.input();
while(!tape_player.tape()->is_at_end() && bits_left--) {
while(!tape_player.is_at_end() && bits_left--) {
// Count number of transitions within cycles_per_window.
int transitions = 0;
int cycles_remaining = cycles_per_window;
while(!tape_player.tape()->is_at_end() && cycles_remaining) {
while(!tape_player.is_at_end() && cycles_remaining) {
const int cycles_until_next_event = int(tape_player.get_cycles_until_next_event());
const int cycles_to_run_for = std::min(cycles_until_next_event, cycles_remaining);
@ -149,7 +149,7 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
}
}
if(tape_player.tape()->is_at_end()) return -1;
if(tape_player.is_at_end()) return -1;
int next_bit = 0;
switch(transitions) {
@ -170,7 +170,7 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
transition count two more."
*/
int required_transitions = 2 - (transitions&1);
while(!tape_player.tape()->is_at_end()) {
while(!tape_player.is_at_end()) {
tape_player.run_for_input_pulse();
if(level != tape_player.input()) {
level = tape_player.input();
@ -179,7 +179,7 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
}
}
if(tape_player.tape()->is_at_end()) return -1;
if(tape_player.is_at_end()) return -1;
}
return result;
}

View File

@ -10,27 +10,27 @@
using namespace Storage::Tape::Oric;
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding) {
int Parser::get_next_byte(Storage::Tape::TapeSerialiser &serialiser, bool use_fast_encoding) {
detection_mode_ = use_fast_encoding ? FastZero : SlowZero;
cycle_length_ = 0.0f;
int result = 0;
int bit_count = 0;
while(bit_count < 11 && !tape->is_at_end()) {
SymbolType symbol = get_next_symbol(tape);
while(bit_count < 11 && !serialiser.is_at_end()) {
SymbolType symbol = get_next_symbol(serialiser);
if(!bit_count && symbol != SymbolType::Zero) continue;
detection_mode_ = use_fast_encoding ? FastData : SlowData;
result |= ((symbol == SymbolType::One) ? 1 : 0) << bit_count;
bit_count++;
}
// TODO: check parity?
return tape->is_at_end() ? -1 : ((result >> 1)&0xff);
return serialiser.is_at_end() ? -1 : ((result >> 1)&0xff);
}
bool Parser::sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape) {
bool Parser::sync_and_get_encoding_speed(Storage::Tape::TapeSerialiser &serialiser) {
detection_mode_ = Sync;
while(!tape->is_at_end()) {
const SymbolType symbol = get_next_symbol(tape);
while(!serialiser.is_at_end()) {
const SymbolType symbol = get_next_symbol(serialiser);
switch(symbol) {
case SymbolType::FoundSlow: return false;
case SymbolType::FoundFast: return true;

View File

@ -25,8 +25,8 @@ enum class SymbolType {
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
public:
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding);
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape);
int get_next_byte(Storage::Tape::TapeSerialiser &, bool use_fast_encoding);
bool sync_and_get_encoding_speed(Storage::Tape::TapeSerialiser &);
private:
void process_pulse(const Storage::Tape::Pulse &pulse) override;

View File

@ -162,22 +162,22 @@ void Parser::inspect_waves(const std::vector<Storage::Tape::ZXSpectrum::WaveType
}
}
std::optional<Block> Parser::find_block(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::optional<Block> Parser::find_block(Storage::Tape::TapeSerialiser &serialiser) {
// Decide whether to kick off a speed detection phase.
if(should_detect_speed()) {
speed_phase_ = SpeedDetectionPhase::WaitingForGap;
}
// Find pilot tone.
proceed_to_symbol(tape, SymbolType::Pilot);
if(is_at_end(tape)) return std::nullopt;
proceed_to_symbol(serialiser, SymbolType::Pilot);
if(is_at_end(serialiser)) return std::nullopt;
// Find sync bit.
proceed_to_symbol(tape, SymbolType::Zero);
if(is_at_end(tape)) return std::nullopt;
proceed_to_symbol(serialiser, SymbolType::Zero);
if(is_at_end(serialiser)) return std::nullopt;
// Read marker byte.
const auto type = get_byte(tape);
const auto type = get_byte(serialiser);
if(!type) return std::nullopt;
// That succeeded.
@ -187,11 +187,11 @@ std::optional<Block> Parser::find_block(const std::shared_ptr<Storage::Tape::Tap
return block;
}
std::vector<uint8_t> Parser::get_block_body(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<uint8_t> Parser::get_block_body(Storage::Tape::TapeSerialiser &serialiser) {
std::vector<uint8_t> result;
while(true) {
const auto next_byte = get_byte(tape);
const auto next_byte = get_byte(serialiser);
if(!next_byte) break;
result.push_back(*next_byte);
}
@ -203,10 +203,10 @@ void Parser::seed_checksum(uint8_t value) {
checksum_ = value;
}
std::optional<uint8_t> Parser::get_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::optional<uint8_t> Parser::get_byte(Storage::Tape::TapeSerialiser &serialiser) {
uint8_t result = 0;
for(int c = 0; c < 8; c++) {
const SymbolType symbol = get_next_symbol(tape);
const SymbolType symbol = get_next_symbol(serialiser);
if(symbol != SymbolType::One && symbol != SymbolType::Zero) return std::nullopt;
result = uint8_t((result << 1) | (symbol == SymbolType::One));
}

View File

@ -72,19 +72,19 @@ public:
in Spectrum-world this seems to be called the flag byte. This call can therefore be followed up with one
of the get_ methods.
*/
std::optional<Block> find_block(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::optional<Block> find_block(Storage::Tape::TapeSerialiser &);
/*!
Reads the contents of the rest of this block, until the next gap.
*/
std::vector<uint8_t> get_block_body(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<uint8_t> get_block_body(Storage::Tape::TapeSerialiser &);
/*!
Reads a single byte from the tape, if there is one left, updating the internal checksum.
The checksum is computed as an exclusive OR of all bytes read.
*/
std::optional<uint8_t> get_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::optional<uint8_t> get_byte(Storage::Tape::TapeSerialiser &);
/*!
Seeds the internal checksum.

View File

@ -27,11 +27,11 @@ public:
Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol
or the tape runs out, returning the most-recently declared symbol.
*/
SymbolType get_next_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape) {
while(!has_next_symbol_ && !tape->is_at_end()) {
process_pulse(tape->next_pulse());
SymbolType get_next_symbol(Storage::Tape::TapeSerialiser &serialiser) {
while(!has_next_symbol_ && !serialiser.is_at_end()) {
process_pulse(serialiser.next_pulse());
}
if(!has_next_symbol_ && tape->is_at_end()) mark_end();
if(!has_next_symbol_ && serialiser.is_at_end()) mark_end();
has_next_symbol_ = false;
return next_symbol_;
}
@ -50,17 +50,17 @@ public:
/*!
@returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise.
*/
bool is_at_end(const std::shared_ptr<Storage::Tape::Tape> &tape) {
return tape->is_at_end() && !has_next_symbol_;
bool is_at_end(Storage::Tape::TapeSerialiser &serialiser) {
return serialiser.is_at_end() && !has_next_symbol_;
}
/*!
Swallows symbols until it reaches the first instance of the required symbol, swallows that
and returns.
*/
void proceed_to_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape, SymbolType required_symbol) {
while(!is_at_end(tape)) {
const SymbolType symbol = get_next_symbol(tape);
void proceed_to_symbol(Storage::Tape::TapeSerialiser &serialiser, SymbolType required_symbol) {
while(!is_at_end(serialiser)) {
const SymbolType symbol = get_next_symbol(serialiser);
if(symbol == required_symbol) return;
}
}

Some files were not shown because too many files have changed in this diff Show More