1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-12 15:31:09 +00:00

Merge pull request #422 from TomHarte/DiskIIAnalyser

Introduces an analyser for Disk II-esque files.
This commit is contained in:
Thomas Harte 2018-05-05 19:35:24 -04:00 committed by GitHub
commit 9ff34d90f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 605 additions and 152 deletions

View File

@ -16,7 +16,7 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine
for(const auto &machine: machines) {
Configurable::Device *device = machine->configurable_device();
if(device) devices_.push_back(device);
}
}
}
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {

View File

@ -14,7 +14,7 @@ MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique
for(const auto &machine: machines) {
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) targets_.push_back(configuration_target);
}
}
}
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {

View File

@ -66,7 +66,7 @@ MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::M
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
}
}
}
for(std::size_t index = 0; index < total_joysticks; ++index) {
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));

View File

@ -14,30 +14,30 @@ MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::M
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
}
}
void MultiKeyboardMachine::clear_all_keys() {
for(const auto &machine: machines_) {
machine->clear_all_keys();
}
}
}
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
for(const auto &machine: machines_) {
machine->set_key_state(key, is_pressed);
}
for(const auto &machine: machines_) {
machine->set_key_state(key, is_pressed);
}
}
void MultiKeyboardMachine::type_string(const std::string &string) {
for(const auto &machine: machines_) {
machine->type_string(string);
}
for(const auto &machine: machines_) {
machine->type_string(string);
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
for(const auto &machine: machines_) {
for(const auto &machine: machines_) {
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
}
}
}

View File

@ -82,11 +82,11 @@ void MultiMachine::multi_crt_did_run_machines() {
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
});
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
});
if(machines_.front().get() != front) {
crt_machine_.did_change_machine_order();

View File

@ -35,8 +35,8 @@ static bool is_implied_extension(const std::string &extension) {
static void right_trim(std::string &string) {
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), string.end());
return !std::isspace(ch);
}).base(), string.end());
}
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {

View File

@ -0,0 +1,123 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "../AppleII/Target.hpp"
#include "../Oric/Target.hpp"
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
namespace {
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
using Target = Analyser::Static::AppleII::Target;
auto *target = new Target;
target->machine = Analyser::Machine::AppleII;
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
target->disk_controller = Target::DiskController::ThirteenSector;
} else {
target->disk_controller = Target::DiskController::SixteenSector;
}
return target;
}
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
using Target = Analyser::Static::Oric::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Oric;
// TODO: configure the Oric as a Pravetz 8D with 8DOS.
return target;
}
}
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Grab track 0, sector 0: the boot sector.
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, 0));
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
for(const auto &pair: sector_map) {
if(!pair.second.address.sector) {
sector_zero = &pair.second;
break;
}
}
// If there's no boot sector then if there are also no sectors at all,
// decline to nominate a machine. Otherwise go with an Apple as the default.
TargetList targets;
if(!sector_zero) {
if(sector_map.empty()) {
return targets;
} else {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
return targets;
}
}
// If the boot sector looks like it's intended for the Oric, create an Oric.
// Otherwise go with the Apple II.
auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
bool did_read_shift_register = false;
bool is_oric = false;
// Look for a tight BPL loop reading the Oric's shift register address of 0x31c. The Apple II just has RAM there,
// so the probability of such a loop is infinitesimal.
for(const auto &instruction: disassembly.instructions_by_address) {
// Is this a read of the shift register?
if(
(
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDA) ||
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDX) ||
(instruction.second.operation == Analyser::Static::MOS6502::Instruction::LDY)
) &&
instruction.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Absolute &&
instruction.second.address == 0x031c) {
did_read_shift_register = true;
continue;
}
if(did_read_shift_register) {
if(
instruction.second.operation == Analyser::Static::MOS6502::Instruction::BPL &&
instruction.second.address == 0xfb) {
is_oric = true;
break;
}
did_read_shift_register = false;
}
}
// Check also for calls into the 0x3xx page above 0x320, as that's where the Oric's boot ROM is.
for(const auto address: disassembly.outward_calls) {
is_oric |= address >= 0x320 && address < 0x400;
}
if(is_oric) {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
} else {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
}
return targets;
}

View File

@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
#define Analyser_Static_DiskII_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace DiskII {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */

View File

@ -20,6 +20,7 @@
#include "Atari/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
@ -94,10 +95,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // DO
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // DSK (Apple)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
@ -106,10 +107,10 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::AppleII) // NIB
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::AppleII) // PO
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
@ -134,7 +135,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::AppleII) // WOZ
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
#undef Format
#undef Insert
@ -168,6 +169,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
@ -183,9 +185,9 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
// 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(),
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
return a->confidence > b->confidence;
});
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
return a->confidence > b->confidence;
});
return targets;
}

View File

@ -587,8 +587,6 @@
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
4BB2CB2A208BDDCF00FD192E /* AppleGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */; };
4BB2CB2B208BDDCF00FD192E /* AppleGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */; };
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
@ -626,6 +624,10 @@
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */; };
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
4BD67DD0209BF27B00AB2146 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; };
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
@ -642,6 +644,8 @@
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; };
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; };
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; };
@ -1296,8 +1300,6 @@
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AppleGCR.cpp; path = Encodings/AppleGCR.cpp; sourceTree = "<group>"; };
4BB2CB29208BDDCF00FD192E /* AppleGCR.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = AppleGCR.hpp; path = Encodings/AppleGCR.hpp; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
@ -1370,6 +1372,10 @@
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSBestEffortUpdater.mm; path = Updater/CSBestEffortUpdater.mm; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BD67DCE209BF27B00AB2146 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
@ -1416,6 +1422,9 @@
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; };
4BF437ED209D0F7E008CBD6B /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
4BF437F0209D112F008CBD6B /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = "<group>"; };
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = "<group>"; };
@ -2239,6 +2248,7 @@
4B7A90EA20410A85008514A2 /* Coleco */,
4B8944FB201967B4007DE474 /* Commodore */,
4B894507201967B4007DE474 /* Disassembler */,
4BD67DC8209BE4D600AB2146 /* DiskII */,
4B89450F201967B4007DE474 /* MSX */,
4B8944F6201967B4007DE474 /* Oric */,
4B894504201967B4007DE474 /* ZX8081 */,
@ -2675,10 +2685,9 @@
4BB697CF1D4BA44900248BDF /* Encodings */ = {
isa = PBXGroup;
children = (
4BB2CB28208BDDCF00FD192E /* AppleGCR.cpp */,
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
4BB2CB29208BDDCF00FD192E /* AppleGCR.hpp */,
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
4BD67DCD209BF27B00AB2146 /* AppleGCR */,
4B7136831F78724F008B8ED9 /* MFM */,
);
name = Encodings;
@ -2993,6 +3002,28 @@
name = Updater;
sourceTree = "<group>";
};
4BD67DC8209BE4D600AB2146 /* DiskII */ = {
isa = PBXGroup;
children = (
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */,
4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */,
);
path = DiskII;
sourceTree = "<group>";
};
4BD67DCD209BF27B00AB2146 /* AppleGCR */ = {
isa = PBXGroup;
children = (
4BD67DCE209BF27B00AB2146 /* Encoder.cpp */,
4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */,
4BD67DCF209BF27B00AB2146 /* Encoder.hpp */,
4BF437F0209D112F008CBD6B /* Sector.hpp */,
4BF437ED209D0F7E008CBD6B /* SegmentParser.hpp */,
);
name = AppleGCR;
path = Encodings/AppleGCR;
sourceTree = "<group>";
};
4BD9137C1F3115AC009BCF85 /* 8255 */ = {
isa = PBXGroup;
children = (
@ -3569,6 +3600,7 @@
4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */,
4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */,
4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */,
4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */,
4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */,
4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
@ -3599,6 +3631,7 @@
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */,
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */,
@ -3639,6 +3672,7 @@
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */,
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */,
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
@ -3648,7 +3682,6 @@
4B894525201967B4007DE474 /* Tape.cpp in Sources */,
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
4BB2CB2B208BDDCF00FD192E /* AppleGCR.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
@ -3708,13 +3741,13 @@
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
4BB2CB2A208BDDCF00FD192E /* AppleGCR.cpp in Sources */,
4B894530201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */,
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */,
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */,
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */,
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
4B894538201967B4007DE474 /* Tape.cpp in Sources */,
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */,
@ -3766,6 +3799,7 @@
4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
4B302184208A550100773308 /* DiskII.cpp in Sources */,
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */,
4BD67DD0209BF27B00AB2146 /* Encoder.cpp in Sources */,
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */,
4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */,
@ -3840,6 +3874,7 @@
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */,
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,

View File

@ -68,7 +68,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

@ -49,7 +49,7 @@ Gw
<action selector="cancelCreateMachine:" target="-2" id="lf8-PM-c0m"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
<rect key="frame" x="20" y="14" width="398" height="34"/>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
<font key="font" metaFont="system"/>

View File

@ -22,6 +22,7 @@ SOURCES += glob.glob('../../Analyser/Static/Atari/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp')
@ -78,6 +79,7 @@ SOURCES += glob.glob('../../Storage/Disk/DiskImage/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/DiskImage/Formats/Utility/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/DPLL/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/Encodings/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/Encodings/AppleGCR/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/Encodings/MFM/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp')
SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp')

View File

@ -9,7 +9,7 @@
#include "AppleDSK.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/AppleGCR.hpp"
#include "../../Encodings/AppleGCR/Encoder.hpp"
using namespace Storage::Disk;

View File

@ -9,7 +9,7 @@
#include "NIB.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../Encodings/AppleGCR.hpp"
#include "../../Encodings/AppleGCR/Encoder.hpp"
#include <vector>

View File

@ -1,73 +0,0 @@
//
// AppleGCR.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef AppleGCR_hpp
#define AppleGCR_hpp
#include <cstdint>
#include "../../Disk/Track/PCMSegment.hpp"
namespace Storage {
namespace Encodings {
namespace AppleGCR {
/*!
@returns the eight-bit 13-sector GCR encoding for the low five bits of @c value.
*/
// unsigned int five_and_three_encoding_for_value(int value);
/*!
@returns the eight-bit 16-sector GCR encoding for the low six bits of @c value.
*/
// unsigned int six_and_two_encoding_for_value(int value);
/*!
A block is defined to be five source bytes, which encodes to eight GCR bytes.
*/
// void encode_five_and_three_block(uint8_t *destination, uint8_t *source);
/*!
A block is defined to be three source bytes, which encodes to four GCR bytes.
*/
// void encode_six_and_two_block(uint8_t *destination, uint8_t *source);
/*!
@returns the four bit nibble for the five-bit GCR @c quintet if a valid GCR value; INT_MAX otherwise.
*/
// unsigned int decoding_from_quintet(unsigned int quintet);
/*!
@returns the byte composed by splitting the dectet into two qintets, decoding each and composing the resulting nibbles.
*/
// unsigned int decoding_from_dectet(unsigned int dectet);
/// Describes the standard three-byte prologue that begins a header.
const uint8_t header_prologue[3] = {0xd5, 0xaa, 0x96};
/// Describes the standard three-byte prologue that begins a data section.
const uint8_t data_prologue[3] = {0xd5, 0xaa, 0xad};
/// Describes the epilogue that ends both data sections and headers.
const uint8_t epilogue[3] = {0xde, 0xaa, 0xeb};
/*!
Produces the Apple-standard '4 and 4' per-sector header. This is the same
for both the 13- and 16-sector formats, and is 112 bits long.
*/
Storage::Disk::PCMSegment header(uint8_t volume, uint8_t track, uint8_t sector);
Storage::Disk::PCMSegment six_and_two_data(const uint8_t *source);
Storage::Disk::PCMSegment six_and_two_sync(int length);
Storage::Disk::PCMSegment five_and_three_data(const uint8_t *source);
Storage::Disk::PCMSegment five_and_three_sync(int length);
}
}
}
#endif /* AppleGCR_hpp */

View File

@ -6,11 +6,11 @@
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "AppleGCR.hpp"
#include "Encoder.hpp"
namespace {
const unsigned int five_and_three_mapping[] = {
const uint8_t five_and_three_mapping[] = {
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba,
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb,
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef,
@ -53,26 +53,6 @@ Storage::Disk::PCMSegment sync(int length, int bit_size) {
using namespace Storage::Encodings;
/*void AppleGCR::encode_five_and_three_block(uint8_t *destination, uint8_t *source) {
destination[0] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[0] >> 3 ));
destination[1] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[0] << 2) | (source[1] >> 6) ));
destination[2] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[1] >> 1 ));
destination[3] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[1] << 4) | (source[2] >> 4) ));
destination[4] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[2] << 1) | (source[3] >> 7) ));
destination[5] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[3] >> 2 ));
destination[6] = static_cast<uint8_t>(five_and_three_encoding_for_value( (source[3] << 3) | (source[4] >> 5) ));
destination[7] = static_cast<uint8_t>(five_and_three_encoding_for_value( source[4] ));
}*/
/*void AppleGCR::encode_six_and_two_block(uint8_t *destination, uint8_t *source) {
destination[0] = static_cast<uint8_t>(six_and_two_encoding_for_value( source[0] >> 2 ));
destination[1] = static_cast<uint8_t>(six_and_two_encoding_for_value( (source[0] << 4) | (source[1] >> 4) ));
destination[2] = static_cast<uint8_t>(six_and_two_encoding_for_value( (source[1] << 2) | (source[2] >> 6) ));
destination[3] = static_cast<uint8_t>(six_and_two_encoding_for_value( source[2] ));
}*/
Storage::Disk::PCMSegment AppleGCR::six_and_two_sync(int length) {
return sync(length, 10);
}
@ -132,6 +112,11 @@ Storage::Disk::PCMSegment AppleGCR::five_and_three_data(const uint8_t *source) {
// destination_pointer += 8;
// }
// Map five-bit values up to full bytes.
for(std::size_t c = 0; c < 410; ++c) {
segment.data[3 + c] = five_and_three_mapping[segment.data[3 + c]];
}
return segment;
}
@ -154,24 +139,24 @@ Storage::Disk::PCMSegment AppleGCR::six_and_two_data(const uint8_t *source) {
// and combined copies of the bottom two bits of the sector
// contents; the 256 bytes afterwards are the remaining
// six bits.
const uint8_t bit_shuffle[] = {0, 2, 1, 3};
const uint8_t bit_reverse[] = {0, 2, 1, 3};
for(std::size_t c = 0; c < 84; ++c) {
segment.data[3 + c] =
static_cast<uint8_t>(
bit_shuffle[source[c]&3] |
(bit_shuffle[source[c + 86]&3] << 2) |
(bit_shuffle[source[c + 172]&3] << 4)
bit_reverse[source[c]&3] |
(bit_reverse[source[c + 86]&3] << 2) |
(bit_reverse[source[c + 172]&3] << 4)
);
}
segment.data[87] =
static_cast<uint8_t>(
(bit_shuffle[source[84]&3] << 0) |
(bit_shuffle[source[170]&3] << 2)
(bit_reverse[source[84]&3] << 0) |
(bit_reverse[source[170]&3] << 2)
);
segment.data[88] =
static_cast<uint8_t>(
(bit_shuffle[source[85]&3] << 0) |
(bit_shuffle[source[171]&3] << 2)
(bit_reverse[source[85]&3] << 0) |
(bit_reverse[source[171]&3] << 2)
);
for(std::size_t c = 0; c < 256; ++c) {

View File

@ -0,0 +1,60 @@
//
// AppleGCR.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef AppleGCR_hpp
#define AppleGCR_hpp
#include <cstdint>
#include "../../../Disk/Track/PCMSegment.hpp"
namespace Storage {
namespace Encodings {
namespace AppleGCR {
/// Describes the standard three-byte prologue that begins a header.
const uint8_t header_prologue[3] = {0xd5, 0xaa, 0x96};
/// Describes the standard three-byte prologue that begins a data section.
const uint8_t data_prologue[3] = {0xd5, 0xaa, 0xad};
/// Describes the epilogue that ends both data sections and headers.
const uint8_t epilogue[3] = {0xde, 0xaa, 0xeb};
/*!
Produces the Apple-standard four-and-four per-sector header. This is the same
for both the 13- and 16-sector formats, and is 112 bits long.
*/
Storage::Disk::PCMSegment header(uint8_t volume, uint8_t track, uint8_t sector);
/*!
Produces the data section of a six-and-two format sector; the segment returned
will be 2,792 bits long, encoding the first 256 bytes from @c source.
*/
Storage::Disk::PCMSegment six_and_two_data(const uint8_t *source);
/*!
Produces @c length sync six-and-two format sync bytes. The segment returned
is @c 10*length bits long.
*/
Storage::Disk::PCMSegment six_and_two_sync(int length);
/*!
Produces the data section of a five-and-three format sector; the segment returned
will be 3,336 bits long, encoding the first 256 bytes from @c source.
*/
Storage::Disk::PCMSegment five_and_three_data(const uint8_t *source);
/*!
Produces @c length sync five-and-three format sync bytes. The segment returned
is @c 9*length bits long.
*/
Storage::Disk::PCMSegment five_and_three_sync(int length);
}
}
}
#endif /* AppleGCR_hpp */

View File

@ -0,0 +1,63 @@
//
// Sector.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/05/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Sector_h
#define Sector_h
#include <cstdint>
#include <vector>
namespace Storage {
namespace Encodings {
namespace AppleGCR {
struct Sector {
/*!
Describes the location of a sector, implementing < to allow for use as a set key.
*/
struct Address {
uint_fast8_t volume = 0, track = 0, sector = 0;
bool operator < (const Address &rhs) const {
return ((volume << 16) | (track << 8) | sector) < ((rhs.volume << 16) | (rhs.track << 8) | rhs.sector);
}
};
Address address;
std::vector<uint8_t> data;
bool has_data_checksum_error = false;
bool has_header_checksum_error = false;
enum class Encoding {
FiveAndThree, SixAndTwo
};
Encoding encoding = Encoding::SixAndTwo;
Sector() {}
Sector(Sector &&rhs) :
address(rhs.address),
data(std::move(rhs.data)),
has_data_checksum_error(rhs.has_data_checksum_error),
has_header_checksum_error(rhs.has_header_checksum_error),
encoding(rhs.encoding) {}
Sector(const Sector &rhs) :
address(rhs.address),
data(rhs.data),
has_data_checksum_error(rhs.has_data_checksum_error),
has_header_checksum_error(rhs.has_header_checksum_error),
encoding(rhs.encoding) {}
};
}
}
}
#endif /* Sector_h */

View File

@ -0,0 +1,172 @@
//
// SegmentParser.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/05/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "SegmentParser.hpp"
#include "Encoder.hpp"
#include <array>
namespace {
const uint8_t six_and_two_unmapping[] = {
0x00, 0x01, 0xff, 0xff,
0x02, 0x03, 0xff, 0x04, 0x05, 0x06, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x07, 0x08, 0xff, 0xff,
0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0xff, 0xff,
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0xff, 0x14,
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1b, 0xff, 0x1c, 0x1d, 0x1e, 0xff, 0xff,
0xff, 0x1f, 0xff, 0xff, 0x20, 0x21, 0xff, 0x22,
0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0xff, 0xff,
0xff, 0xff, 0xff, 0x29, 0x2a, 0x2b, 0xff, 0x2c,
0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0xff, 0xff,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xff, 0x39,
0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0xff, 0xff
};
}
using namespace Storage::Encodings::AppleGCR;
std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &&segment) {
std::map<std::size_t, Sector> result;
uint_fast8_t shift_register = 0;
const std::size_t scanning_sentinel = std::numeric_limits<std::size_t>::max();
std::unique_ptr<Sector> new_sector;
std::size_t sector_location = 0;
std::size_t pointer = scanning_sentinel;
std::array<uint_fast8_t, 8> header{{0, 0, 0, 0, 0, 0, 0, 0}};
std::array<uint_fast8_t, 3> scanner;
for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) {
// Apple GCR parsing: bytes always have the top bit set.
shift_register = static_cast<uint_fast8_t>((shift_register << 1) | segment.bit(bit));
if(!(shift_register&0x80)) continue;
// Grab the byte.
const uint_fast8_t value = shift_register;
shift_register = 0;
if(pointer == scanning_sentinel) {
scanner[0] = scanner[1];
scanner[1] = scanner[2];
scanner[2] = value;
if(
scanner[0] == header_prologue[0] &&
scanner[1] == header_prologue[1] &&
(
scanner[2] == header_prologue[2] ||
scanner[2] == data_prologue[2]
)) {
pointer = 0;
// If this is the start of a data section, and at least
// one header has been witnessed, start a sector.
if(scanner[2] == data_prologue[2]) {
new_sector.reset(new Sector);
new_sector->data.reserve(412);
} else {
sector_location = static_cast<std::size_t>(bit);
}
}
} else {
if(new_sector) {
// If this is an epilogue, make sense of this whole sector;
// otherwise just keep the byte for later.
if(value == epilogue[1]) {
std::unique_ptr<Sector> sector = std::move(new_sector);
new_sector.reset();
pointer = scanning_sentinel;
// Check for an expected size; the first byte of the epilogue has been stored, so
// these numbers are one bigger than usual.
if(sector->data.size() != 412 && sector->data.size() != 344) continue;
sector->data.resize(sector->data.size() - 1);
// Check for apparent four and four encoding.
uint_fast8_t header_mask = 0xff;
for(auto c : header) header_mask &= c;
header_mask &= 0xaa;
if(header_mask != 0xaa) continue;
sector->address.volume = ((header[0] << 1) | 1) & header[1];
sector->address.track = ((header[2] << 1) | 1) & header[3];
sector->address.sector = ((header[4] << 1) | 1) & header[5];
// Check the header checksum.
uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7];
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) continue;
if(sector->data.size() == 343) {
// Unmap the sector contents as 6 and 2 data.
bool out_of_bounds = false;
for(auto &c : sector->data) {
if(c < 0x96 || six_and_two_unmapping[c - 0x96] == 0xff) {
out_of_bounds = true;
break;
}
c = six_and_two_unmapping[c - 0x96];
}
if(out_of_bounds) continue;
// Undo the XOR step on sector contents and check that checksum.
for(std::size_t c = 1; c < sector->data.size(); ++c) {
sector->data[c] ^= sector->data[c-1];
}
if(sector->data.back()) continue;
// Having checked the checksum, remove it.
sector->data.resize(sector->data.size() - 1);
// Undo the 6 and 2 mapping.
const uint8_t bit_reverse[] = {0, 2, 1, 3};
#define unmap(byte, nibble, shift) \
sector->data[86 + byte] = static_cast<uint8_t>(\
(sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]);
for(std::size_t c = 0; c < 84; ++c) {
unmap(c, c, 0);
unmap(c+86, c, 2);
unmap(c+172, c, 4);
}
unmap(84, 84, 0);
unmap(170, 84, 2);
unmap(85, 85, 0);
unmap(171, 85, 2);
#undef unmap
}
// Throw away the collection of two-bit chunks.
sector->data.erase(sector->data.begin(), sector->data.end() - 256);
// Add this sector to the map.
result.insert(std::make_pair(sector_location, std::move(*sector)));
} else {
new_sector->data.push_back(value);
}
} else {
// Just capture the header in place; it'll be decoded
// once a whole sector has been read.
header[pointer] = value;
++pointer;
if(pointer == 8) {
pointer = scanning_sentinel;
}
}
}
}
return result;
}

View File

@ -0,0 +1,30 @@
//
// SegmentParser.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/05/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef TrackParser_hpp
#define TrackParser_hpp
#include "Sector.hpp"
#include "../../Track/PCMSegment.hpp"
#include <map>
namespace Storage {
namespace Encodings {
namespace AppleGCR {
/*!
Scans @c segment for all included sectors, returning a set that maps from location within
the segment (counted in bits from the beginning) to sector.
*/
std::map<std::size_t, Sector> sectors_from_segment(const Disk::PCMSegment &&segment);
}
}
}
#endif /* TrackParser_hpp */

View File

@ -33,7 +33,8 @@ struct Sector {
};
Address address;
uint8_t size = 0;
uint8_t size = 0; // Size is stored in ordinary MFM form — the number of bytes included in this sector
// is 2^(7 + size), or 128 << size.
// Multiple samplings of the underlying data are accepted, to allow weak and fuzzy data to be communicated.
std::vector<std::vector<uint8_t>> samples;

View File

@ -19,7 +19,8 @@ namespace MFM {
/*!
Scans @c segment for all included sectors, returning a set that maps from location within
the segment (counted in bits from the beginning) to sector.
the segment (counted in bits from the beginning and pointing to the location the disk
had reached upon detection of the ID mark) to sector.
*/
std::map<std::size_t, Sector> sectors_from_segment(const Disk::PCMSegment &&segment, bool is_double_density);

View File

@ -17,6 +17,30 @@ namespace Storage {
namespace Encodings {
namespace MFM {
/*!
The MFM shifter parses a stream of bits as input in order to produce
a stream of MFM tokens as output. So e.g. it is suitable for use in parsing
the output of a PLL windowing of disk events.
It supports both FM and MFM parsing; see @c set_is_double_density.
It will ordinarily honour sync patterns; that should be turned off when within
a sector because false syncs can occur. See @c set_should_obey_syncs.
Bits should be fed in with @c add_input_bit.
The current output token can be read with @c get_token. It will usually be None but
may indicate that an index, ID, data or deleted data mark was found, that an
MFM sync mark was found, or that an ordinary byte has been decoded.
It will properly reset and/or seed a CRC generator based on the data and ID marks,
and feed it with incoming bytes. You can access that CRC generator to query its
value via @c get_crc_generator(). An easy way to check whether the disk contained
a proper CRC is to read bytes until you've just read whatever CRC was on the disk,
then check that the generator has a value of zero.
A specific instance of the CRC generator can be supplied at construction if preferred.
*/
class Shifter {
public:
Shifter();

View File

@ -46,11 +46,11 @@ class CAS: public Tape {
void get_next(Storage::FileHolder &file, uint8_t (&buffer)[10], std::size_t quantity);
// 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.
// 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;
bool long_header;
std::vector<std::uint8_t> data;
};
std::vector<Chunk> chunks_;

View File

@ -23,10 +23,11 @@ enum Type: IntType {
BBCModelB = 1 << 8,
ColecoVision = 1 << 9,
Commodore = 1 << 10,
MSX = 1 << 11,
Oric = 1 << 12,
ZX80 = 1 << 13,
ZX81 = 1 << 14,
DiskII = 1 << 11,
MSX = 1 << 12,
Oric = 1 << 13,
ZX80 = 1 << 14,
ZX81 = 1 << 15,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,