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:
commit
083c1b7ca7
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 &);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 &);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 &);
|
||||
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"/>
|
||||
|
@ -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 \
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user