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

Compare commits

..

6 Commits

Author SHA1 Message Date
Thomas Harte
4078baa424 Capture up to eight scans per submission group. 2022-07-03 21:54:28 -04:00
Thomas Harte
3179d0d963 Vend data pointers. 2022-07-02 21:58:23 -04:00
Thomas Harte
6a8c792c63 Add new scan target to SDL build. 2022-07-02 21:57:48 -04:00
Thomas Harte
678e1a38fa Ensure some basic traffic reaches the putative software scan target. 2022-07-02 21:29:59 -04:00
Thomas Harte
f4004baff8 Set out a stall. 2022-07-02 16:31:38 -04:00
Thomas Harte
f04e4faae2 Avoid potential ScanTarget include-guard collisions. 2022-07-02 13:54:16 -04:00
828 changed files with 2847852 additions and 177605 deletions

View File

@@ -1,87 +0,0 @@
name: Build
on: [pull_request]
jobs:
build-mac-xcodebuild:
name: Mac UI / xcodebuild / ${{ matrix.os }}
strategy:
matrix:
os: [macos-12, macos-13, macos-14]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Make
working-directory: OSBindings/Mac
run: xcodebuild CODE_SIGN_IDENTITY=-
build-sdl-cmake:
name: SDL UI / cmake / ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
shell: bash
run: |
case $RUNNER_OS in
Linux)
sudo apt-get --allow-releaseinfo-change update
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
;;
macOS)
brew install cmake sdl2
;;
esac
- name: Make
shell: bash
run: |
case $RUNNER_OS in
Linux)
jobs=$(nproc --all)
;;
macOS)
jobs=$(sysctl -n hw.activecpu)
;;
*)
jobs=1
esac
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
cmake --build build -v -j"$jobs"
build-sdl-scons:
name: SDL UI / scons / ${{ matrix.os }}
strategy:
matrix:
os: [macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
shell: bash
run: |
case $RUNNER_OS in
Linux)
sudo apt-get --allow-releaseinfo-change update
sudo apt-get --fix-missing install gcc-10 libsdl2-dev scons
;;
macOS)
brew install scons sdl2
;;
esac
- name: Make
working-directory: OSBindings/SDL
shell: bash
run: |
case $RUNNER_OS in
Linux)
jobs=$(nproc --all)
;;
macOS)
jobs=$(sysctl -n hw.activecpu)
;;
*)
jobs=1
esac
scons -j"$jobs"

16
.github/workflows/ccpp.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: SDL/Ubuntu
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
- name: Make
working-directory: OSBindings/SDL
run: scons -j$(nproc --all)

View File

@@ -6,9 +6,9 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ActivityObserver_h
#define ActivityObserver_h
#include <cstdint>
#include <string>
namespace Activity {
@@ -23,8 +23,6 @@ namespace Activity {
*/
class Observer {
public:
virtual ~Observer() = default;
/// Provides hints as to the sort of information presented on an LED.
enum LEDPresentation: uint8_t {
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
@@ -57,3 +55,5 @@ class Observer {
};
}
#endif /* ActivityObserver_h */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ActivitySource_h
#define ActivitySource_h
#include "Observer.hpp"
@@ -18,3 +19,6 @@ class Source {
};
}
#endif /* ActivitySource_h */

View File

@@ -6,11 +6,13 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ConfidenceCounter_hpp
#define ConfidenceCounter_hpp
#include "ConfidenceSource.hpp"
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a confidence source that calculates its probability by virtual of a history of events.
@@ -40,3 +42,6 @@ class ConfidenceCounter: public ConfidenceSource {
};
}
}
#endif /* ConfidenceCounter_hpp */

View File

@@ -6,9 +6,11 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ConfidenceSource_hpp
#define ConfidenceSource_hpp
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides an abstract interface through which objects can declare the probability
@@ -21,3 +23,6 @@ struct ConfidenceSource {
};
}
}
#endif /* ConfidenceSource_hpp */

View File

@@ -6,13 +6,15 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ConfidenceSummary_hpp
#define ConfidenceSummary_hpp
#include "ConfidenceSource.hpp"
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Summaries a collection of confidence sources by calculating their weighted sum.
@@ -39,3 +41,6 @@ class ConfidenceSummary: public ConfidenceSource {
};
}
}
#endif /* ConfidenceSummary_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiConfigurable_hpp
#define MultiConfigurable_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Configurable/Configurable.hpp"
@@ -14,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configurable interface to multiple machines.
@@ -35,3 +37,6 @@ class MultiConfigurable: public Configurable::Device {
};
}
}
#endif /* MultiConfigurable_hpp */

View File

@@ -6,14 +6,16 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiJoystickMachine_hpp
#define MultiJoystickMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the joystick machine interface to multiple machines.
@@ -33,3 +35,6 @@ class MultiJoystickMachine: public MachineTypes::JoystickMachine {
};
}
}
#endif /* MultiJoystickMachine_hpp */

View File

@@ -56,10 +56,10 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTy
}
}
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
bool was_consumed = false;
for(const auto &machine: machines_) {
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
return was_consumed;
}

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiKeyboardMachine_hpp
#define MultiKeyboardMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Machines/KeyboardMachine.hpp"
@@ -14,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the keyboard machine interface to multiple machines.
@@ -30,7 +32,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
public:
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
bool set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
@@ -54,3 +56,6 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
};
}
}
#endif /* MultiKeyboardMachine_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiMediaTarget_hpp
#define MultiMediaTarget_hpp
#include "../../../../Machines/MediaTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
@@ -14,7 +15,8 @@
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the media target interface to multiple machines.
@@ -34,3 +36,6 @@ struct MultiMediaTarget: public MachineTypes::MediaTarget {
};
}
}
#endif /* MultiMediaTarget_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiProducer_hpp
#define MultiProducer_hpp
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/MachineTypes.hpp"
@@ -18,7 +19,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
template <typename MachineType> class MultiInterface {
public:
@@ -45,7 +47,7 @@ template <typename MachineType> class MultiInterface {
std::recursive_mutex &machines_mutex_;
private:
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
};
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
@@ -114,3 +116,7 @@ class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, pu
*/
}
}
#endif /* MultiProducer_hpp */

View File

@@ -61,7 +61,7 @@ void MultiSpeaker::set_output_volume(float volume) {
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
auto delegate = delegate_.load(std::memory_order_relaxed);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
@@ -71,7 +71,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
auto delegate = delegate_.load(std::memory_order_relaxed);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
@@ -85,7 +85,7 @@ void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
auto delegate = delegate_.load(std::memory_order_relaxed);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
}

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiSpeaker_hpp
#define MultiSpeaker_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Outputs/Speaker/Speaker.hpp"
@@ -15,7 +16,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
@@ -54,3 +56,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
};
}
}
#endif /* MultiSpeaker_hpp */

View File

@@ -11,12 +11,6 @@
#include <algorithm>
namespace {
Log::Logger<Log::Source::MultiMachine> logger;
}
using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
@@ -67,14 +61,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
void MultiMachine::did_run_machines(MultiTimedMachine *) {
std::lock_guard machines_lock(machines_mutex_);
if constexpr (logger.enabled) {
auto line = logger.info();
for(const auto &machine: machines_) {
auto timed_machine = machine->timed_machine();
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
}
#ifndef NDEBUG
for(const auto &machine: machines_) {
auto timed_machine = machine->timed_machine();
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
}
LOGNBR(std::endl);
#endif
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiMachine_hpp
#define MultiMachine_hpp
#include "../../../Machines/DynamicMachine.hpp"
@@ -21,7 +22,8 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides the same interface as to a single machine, while multiplexing all
@@ -79,3 +81,6 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::
};
}
}
#endif /* MultiMachine_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Machines_h
#define Machines_h
namespace Analyser {
@@ -17,7 +18,6 @@ enum class Machine {
Atari2600,
AtariST,
Amiga,
Archimedes,
ColecoVision,
Electron,
Enterprise,
@@ -25,10 +25,11 @@ enum class Machine {
MasterSystem,
MSX,
Oric,
PCCompatible,
Vic20,
ZX8081,
ZXSpectrum,
};
}
#endif /* Machines_h */

View File

@@ -13,17 +13,16 @@
#include "../../../Numeric/CRC.hpp"
#include <algorithm>
#include <cstring>
using namespace Analyser::Static::Acorn;
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Single, disk);
Storage::Encodings::MFM::Parser parser(false, disk);
const Storage::Encodings::MFM::Sector *const names = parser.sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
@@ -66,7 +65,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
uint8_t track = uint8_t(start_sector / 10);
start_sector++;
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
@@ -85,58 +84,36 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
*/
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
Storage::Encodings::MFM::Parser parser(true, disk);
// Grab the second half of the free-space map because it has the boot option in it.
const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
if(!free_space_map_second_half) return nullptr;
catalogue->has_large_sectors = free_space_map_second_half->samples[0].size() == 1024;
// Possibility: this is a large-sector disk with an old-style free space map. In which
// case the above just read the start of the root directory.
uint8_t first_directory_sector = 2;
if(catalogue->has_large_sectors && !memcmp(&free_space_map_second_half->samples[0][1], "Hugo", 4)) {
free_space_map_second_half = parser.sector(0, 0, 0);
if(!free_space_map_second_half) return nullptr;
first_directory_sector = 1;
}
std::vector<uint8_t> root_directory;
root_directory.reserve(catalogue->has_large_sectors ? 2*1024 : 5*256);
for(uint8_t c = first_directory_sector; c < first_directory_sector + (catalogue->has_large_sectors ? 2 : 5); c++) {
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}
// Check for end of directory marker.
if(root_directory[catalogue->has_large_sectors ? 0x7d7 : 0x4cb]) return nullptr;
// Quick sanity checks.
if(root_directory[0x4cb]) return nullptr;
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
// Check for both directory identifiers.
const uint8_t *const start_id = &root_directory[1];
const uint8_t *const end_id = &root_directory[root_directory.size() - 5];
catalogue->is_hugo = !memcmp(start_id, "Hugo", 4) && !memcmp(end_id, "Hugo", 4);
const bool is_nick = !memcmp(start_id, "Nick", 4) && !memcmp(end_id, "Nick", 4);
if(!catalogue->is_hugo && !is_nick) {
return nullptr;
}
if(!catalogue->has_large_sectors) {
// TODO: I don't know where the boot option rests with large sectors.
switch(free_space_map_second_half->samples[0][0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
switch(free_space_map_second_half->samples[0][0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// Parse the root directory, at least.
for(std::size_t file_offset = 0x005; file_offset < (catalogue->has_large_sectors ? 0x7d7 : 0x4cb); file_offset += 0x1a) {
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
// Obtain the name, which will be at most ten characters long, and will
// be terminated by either a NULL character or a \r.
char name[11]{};
char name[11];
std::size_t c = 0;
for(; c < 10; c++) {
const char next = root_directory[file_offset + c] & 0x7f;
@@ -145,9 +122,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
}
name[c] = '\0';
// An empty name implies the directory has ended; files are always listed in case-insensitive
// sorted order, with that list being terminated by a '\0'.
if(name[0] == '\0') break;
// Skip if the name is empty.
if(name[0] == '\0') continue;
// Populate a file then.
File new_file;
@@ -190,7 +166,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
new_file.data.reserve(size);
while(new_file.data.size() < size) {
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
if(!sector) break;
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
@@ -201,20 +177,5 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
catalogue->files.push_back(std::move(new_file));
}
// Include the directory title.
const char *title, *name;
if(catalogue->has_large_sectors) {
title = reinterpret_cast<const char *>(&root_directory[0x7dd]);
name = reinterpret_cast<const char *>(&root_directory[0x7f0]);
} else {
title = reinterpret_cast<const char *>(&root_directory[0x4d9]);
name = reinterpret_cast<const char *>(&root_directory[0x4cc]);
}
catalogue->name = std::string(title, strnlen(title, 19));
if(catalogue->name.empty() || catalogue->name == "$") {
catalogue->name = std::string(name, strnlen(name, 10));
}
return catalogue;
}

View File

@@ -6,17 +6,18 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Acorn_Disk_hpp
#define StaticAnalyser_Acorn_Disk_hpp
#include "File.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
struct Catalogue {
bool is_hugo = false;
bool has_large_sectors = false;
std::string name;
std::vector<File> files;
enum class BootOption {
@@ -31,3 +32,7 @@ std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}
#endif /* Disk_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Acorn_File_hpp
#define StaticAnalyser_Acorn_File_hpp
#include <memory>
#include <string>
#include <vector>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
struct File {
std::string name;
@@ -58,3 +61,7 @@ struct File {
};
}
}
}
#endif /* File_hpp */

View File

@@ -12,10 +12,7 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "../../../Numeric/StringSimilarity.hpp"
#include <algorithm>
#include <map>
using namespace Analyser::Static::Acorn;
@@ -62,14 +59,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
auto target8bit = std::make_unique<ElectronTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
// Copy appropriate cartridges to the 8-bit target.
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// If there are any tapes, attempt to get data from the first.
// 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);
@@ -98,34 +94,30 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target8bit->media.tapes = media.tapes;
target->media.tapes = media.tapes;
}
}
if(!media.disks.empty()) {
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
// Get any sort of catalogue that can be found.
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
// 8-bit options: DFS and Hugo-style ADFS.
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target8bit->media.disks = media.disks;
target8bit->has_dfs = bool(dfs_catalogue);
target8bit->has_pres_adfs = bool(adfs_catalogue);
target->media.disks = media.disks;
target->has_dfs = bool(dfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None) {
target8bit->should_shift_restart = true;
target->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
target->loading_command = "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
@@ -141,79 +133,39 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
target->has_ap6_rom = true;
target->has_sideways_ram = true;
}
}
}
} else if(adfs_catalogue) {
// Archimedes options, implicitly: ADFS, non-Hugo.
targetArchimedes->media.disks = media.disks;
// Also look for the best possible startup program name, if it can be discerned.
std::multimap<double, std::string, std::greater<double>> options;
for(const auto &file: adfs_catalogue->files) {
// Skip non-Pling files.
if(file.name[0] != '!') continue;
// Take whatever else comes with a preference for things that don't
// have 'boot' or 'read' in them (the latter of which will tend to be
// read_me or read_this or similar).
constexpr char read[] = "read";
constexpr char boot[] = "boot";
const auto has = [&](const char *begin, const char *end) {
return std::search(
file.name.begin(), file.name.end(),
begin, end - 1, // i.e. don't compare the trailing NULL.
[](char lhs, char rhs) {
return std::tolower(lhs) == rhs;
}
) != file.name.end();
};
const auto has_read = has(std::begin(read), std::end(read));
const auto has_boot = has(std::begin(boot), std::end(boot));
const auto probability =
Numeric::similarity(file.name, adfs_catalogue->name) +
Numeric::similarity(file.name, file_name) -
((has_read || has_boot) ? 0.2 : 0.0);
options.emplace(probability, file.name);
}
if(!options.empty()) {
targetArchimedes->main_program = options.begin()->second;
}
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
target8bit->has_acorn_adfs = true;
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target8bit->media.mass_storage_devices = media.mass_storage_devices;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target8bit->should_shift_restart = true;
target->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
target->loading_command = "*CAT\n";
}
}
TargetList targets;
if(!target8bit->media.empty()) {
targets.push_back(std::move(target8bit));
}
if(!targetArchimedes->media.empty()) {
targets.push_back(std::move(targetArchimedes));
if(!target->media.empty()) {
targets.push_back(std::move(target));
}
return targets;
}

View File

@@ -6,14 +6,21 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* AcornAnalyser_hpp */

View File

@@ -19,10 +19,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
auto new_chunk = std::make_unique<File::Chunk>();
int shift_register = 0;
// TODO: move this into the parser
const auto shift = [&] {
shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
};
// TODO: move this into the parser
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
// find next area of high tone
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
@@ -34,6 +32,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
shift();
}
#undef shift
parser.reset_crc();
parser.reset_error_flag();

View File

@@ -6,15 +6,22 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Acorn_Tape_hpp
#define StaticAnalyser_Acorn_Tape_hpp
#include <memory>
#include "File.hpp"
#include "../../../Storage/Tape/Tape.hpp"
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -6,15 +6,18 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Acorn {
namespace Analyser {
namespace Static {
namespace Acorn {
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
@@ -23,7 +26,7 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
bool should_shift_restart = false;
std::string loading_command;
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
@@ -34,10 +37,8 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
}
};
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
std::string main_program;
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
};
}
}
}
#endif /* Analyser_Static_Acorn_Target_h */

View File

@@ -6,14 +6,22 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
#define Analyser_Static_Amiga_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Amiga {
namespace Analyser {
namespace Static {
namespace Amiga {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */

View File

@@ -6,12 +6,15 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Amiga_Target_h
#define Analyser_Static_Amiga_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Amiga {
namespace Analyser {
namespace Static {
namespace Amiga {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ChipRAM,
@@ -39,3 +42,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

View File

@@ -159,8 +159,8 @@ void InspectCatalogue(
}
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.
@@ -228,13 +228,26 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
}
if(!media.disks.empty()) {
const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();
const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
data_format.block_size = 1024;
data_format.first_sector = 0xc1;
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
for(auto &disk: media.disks) {
// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
// Check for an ordinary catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
target->media.disks.push_back(disk);
continue;
@@ -248,7 +261,7 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
// Failing that check for a system catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
target->media.disks.push_back(disk);
continue;

View File

@@ -6,14 +6,21 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AmstradCPC {
namespace Analyser {
namespace Static {
namespace AmstradCPC {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */

View File

@@ -6,34 +6,34 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::AmstradCPC {
namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
Model model = Model::CPC464;
std::string loading_command;
ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3);
CRTCType crtc_type = CRTCType::Type2;
// This is used internally for testing; it therefore isn't exposed reflectively.
bool catch_ssm_codes = false;
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
if(needs_declare()) {
DeclareField(model);
DeclareField(crtc_type);
AnnounceEnum(Model);
AnnounceEnum(CRTCType);
}
}
};
}
}
}
#endif /* Analyser_Static_AmstradCPC_Target_h */

View File

@@ -13,17 +13,8 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
auto target = std::make_unique<Target>();
target->media = media;
// If any disks are present, attach a Disk II.
if(!target->media.disks.empty()) {
if(!target->media.disks.empty())
target->disk_controller = Target::DiskController::SixteenSector;
}
// The emulated SCSI card requires a IIe, so upgrade to that if
// any mass storage is present.
if(!target->media.mass_storage_devices.empty()) {
target->model = Target::Model::EnhancedIIe;
target->scsi_controller = Target::SCSIController::AppleSCSI;
}
TargetList targets;
targets.push_back(std::move(target));

View File

@@ -6,14 +6,21 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
#define Analyser_Static_AppleII_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AppleII {
namespace Analyser {
namespace Static {
namespace AppleII {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AppleII_Target_h
#define Analyser_Static_AppleII_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AppleII {
namespace Analyser {
namespace Static {
namespace AppleII {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -26,32 +29,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
SixteenSector,
ThirteenSector
);
ReflectableEnum(SCSIController,
None,
AppleSCSI
);
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
SCSIController scsi_controller = SCSIController::None;
bool has_mockingboard = true;
Target() : Analyser::Static::Target(Machine::AppleII) {
if(needs_declare()) {
DeclareField(model);
DeclareField(disk_controller);
DeclareField(scsi_controller);
DeclareField(has_mockingboard);
AnnounceEnum(Model);
AnnounceEnum(DiskController);
AnnounceEnum(SCSIController);
}
}
};
constexpr bool is_iie(Target::Model model) {
return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
}
}
}
}
#endif /* Analyser_Static_AppleII_Target_h */

View File

@@ -6,14 +6,21 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AppleIIgs {
namespace Analyser {
namespace Static {
namespace AppleIIgs {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AppleIIgs_Target_h
#define Analyser_Static_AppleIIgs_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AppleIIgs {
namespace Analyser {
namespace Static {
namespace AppleIIgs {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -40,3 +43,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_AppleIIgs_Target_h */

View File

@@ -6,14 +6,21 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
#define StaticAnalyser_Atari_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Atari2600 {
namespace Analyser {
namespace Static {
namespace Atari2600 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,11 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Atari2600_Target_h
#define Analyser_Static_Atari2600_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Atari2600 {
namespace Analyser {
namespace Static {
namespace Atari2600 {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
@@ -36,3 +39,7 @@ struct Target: public ::Analyser::Static::Target {
};
}
}
}
#endif /* Analyser_Static_Atari_Target_h */

View File

@@ -6,14 +6,22 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
#define Analyser_Static_AtariST_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::AtariST {
namespace Analyser {
namespace Static {
namespace AtariST {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */

View File

@@ -6,26 +6,22 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AtariST_Target_h
#define Analyser_Static_AtariST_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AtariST {
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemorySize,
FiveHundredAndTwelveKilobytes,
OneMegabyte,
FourMegabytes);
MemorySize memory_size = MemorySize::OneMegabyte;
Target() : Analyser::Static::Target(Machine::AtariST) {
if(needs_declare()) {
DeclareField(memory_size);
AnnounceEnum(MemorySize);
}
}
Target() : Analyser::Static::Target(Machine::AtariST) {}
};
}
}
}
#endif /* Analyser_Static_AtariST_Target_h */

View File

@@ -6,14 +6,21 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Coleco {
namespace Analyser {
namespace Static {
namespace Coleco {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -37,7 +37,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = int(track) - int(track_);
track_ = track;
@@ -182,7 +182,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
uint8_t next_track = 18;
uint8_t next_sector = 1;
while(1) {
sector = parser.sector(next_track, next_sector);
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
next_track = sector->data[0];
@@ -221,7 +221,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
bool is_first_sector = true;
while(next_track) {
sector = parser.sector(next_track, next_sector);
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];

View File

@@ -6,15 +6,22 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Commodore_Disk_hpp
#define StaticAnalyser_Commodore_Disk_hpp
#include "../../../Storage/Disk/Disk.hpp"
#include "File.hpp"
#include <vector>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}
#endif /* Disk_hpp */

View File

@@ -6,13 +6,15 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef File_hpp
#define File_hpp
#include <cstdint>
#include <string>
#include <vector>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct File {
std::wstring name;
@@ -34,3 +36,7 @@ struct File {
};
}
}
}
#endif /* File_hpp */

View File

@@ -93,7 +93,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
[[fallthrough]];
case 0x1001:
memory_model = Target::MemoryModel::Unexpanded;
@@ -188,8 +188,8 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
// Unhandled:
//
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
// RO: this disk image should be treated as read-only.
}

View File

@@ -6,14 +6,21 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* CommodoreAnalyser_hpp */

View File

@@ -6,13 +6,20 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Commodore_Tape_hpp
#define StaticAnalyser_Commodore_Tape_hpp
#include "../../../Storage/Tape/Tape.hpp"
#include "File.hpp"
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -6,14 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class MemoryModel {
@@ -69,3 +72,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -11,7 +11,7 @@
#include "Kernel.hpp"
using namespace Analyser::Static::MOS6502;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
@@ -236,7 +236,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Relative: {
std::size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
++address;
address++;
instruction.operand = memory[operand_address];
}
@@ -291,34 +291,20 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
// Decide on overall flow control.
// All relative instructions are flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
switch(instruction.operation) {
default: break;
case Instruction::KIL:
case Instruction::RTS:
case Instruction::RTI:
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
disassembly.implicit_entry_points.push_back(address);
return;
case Instruction::JMP:
// Adding a new entry point for relative jumps was handled above.
if(instruction.addressing_mode == Instruction::Absolute) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
return;
case Instruction::JSR:
disassembly.remaining_entry_points.push_back(instruction.operand);
break;
}
}
}
@@ -330,5 +316,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -6,7 +6,8 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Disassembler_6502_hpp
#define StaticAnalyser_Disassembler_6502_hpp
#include <cstdint>
#include <functional>
@@ -15,7 +16,9 @@
#include <set>
#include <vector>
namespace Analyser::Static::MOS6502 {
namespace Analyser {
namespace Static {
namespace MOS6502 {
/*!
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
@@ -92,3 +95,7 @@ Disassembly Disassemble(
std::vector<uint16_t> entry_points);
}
}
}
#endif /* Disassembler6502_hpp */

View File

@@ -6,11 +6,14 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef AddressMapper_hpp
#define AddressMapper_hpp
#include <functional>
namespace Analyser::Static::Disassembler {
namespace Analyser {
namespace Static {
namespace Disassembler {
/*!
Provides an address mapper that relocates a chunk of memory so that it starts at
@@ -23,3 +26,7 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
}
}
}
}
#endif /* AddressMapper_hpp */

View File

@@ -6,61 +6,47 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Kernel_hpp
#define Kernel_hpp
namespace Analyser::Static::Disassembly {
namespace Analyser {
namespace Static {
namespace Disassembly {
template <typename D, typename S> struct PartialDisassembly {
D disassembly;
std::vector<S> remaining_entry_points;
std::vector<S> implicit_entry_points;
};
template <typename D, typename S, typename Disassembler> D Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(S)> &address_mapper,
std::vector<S> entry_points,
bool exhaustive)
{
std::vector<S> entry_points) {
PartialDisassembly<D, S> partial_disassembly;
partial_disassembly.remaining_entry_points = entry_points;
while(!partial_disassembly.remaining_entry_points.empty()) {
// Do a recursive-style disassembly for all current entry points.
while(!partial_disassembly.remaining_entry_points.empty()) {
// Pull the next entry point from the back of the vector.
const S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// pull the next entry point from the back of the vector
S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// If that address has already been visited, forget about it.
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// if that address has already been visited, forget about it
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// If it's outgoing, log it as such and forget about it; otherwise disassemble.
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
// If this is not an exhaustive disassembly, that's your lot.
if(!exhaustive) {
break;
}
// Otherwise, copy in the new 'implicit entry points' (i.e. all locations that are one after
// a disassembled region). There's a test above that'll ignore any which have already been
// disassembled from.
std::move(
partial_disassembly.implicit_entry_points.begin(),
partial_disassembly.implicit_entry_points.end(),
std::back_inserter(partial_disassembly.remaining_entry_points)
);
partial_disassembly.implicit_entry_points.clear();
// if it's outgoing, log it as such and forget about it; otherwise disassemble
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
return partial_disassembly.disassembly;
}
}
}
}
#endif /* Kernel_hpp */

View File

@@ -11,7 +11,7 @@
#include "Kernel.hpp"
using namespace Analyser::Static::Z80;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
@@ -56,11 +56,11 @@ class Accessor {
bool overrun_ = false;
};
constexpr uint8_t x(uint8_t v) { return v >> 6; }
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
constexpr uint8_t z(uint8_t v) { return v & 7; }
#define x(v) (v >> 6)
#define y(v) ((v >> 3) & 7)
#define q(v) ((v >> 3) & 1)
#define p(v) ((v >> 4) & 3)
#define z(v) (v & 7)
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
@@ -589,7 +589,7 @@ struct Z80Disassembler {
break;
}
// Add any (potentially) newly-discovered entry point.
// Add any (potentially) newly discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
@@ -598,37 +598,22 @@ struct Z80Disassembler {
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
switch(instruction.operation) {
default: break;
if(instruction.condition != Instruction::Condition::None) continue;
case Instruction::Operation::RET:
case Instruction::Operation::RETI:
case Instruction::Operation::RETN:
case Instruction::Operation::JP:
case Instruction::Operation::JR:
if(instruction.condition == Instruction::Condition::None) {
disassembly.implicit_entry_points.push_back(accessor.address());
return;
}
}
if(instruction.operation == Instruction::Operation::RET) return;
if(instruction.operation == Instruction::Operation::RETI) return;
if(instruction.operation == Instruction::Operation::RETN) return;
if(instruction.operation == Instruction::Operation::JP) return;
if(instruction.operation == Instruction::Operation::JR) return;
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach)
{
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
memory,
address_mapper,
entry_points,
approach == Approach::Exhaustive
);
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -6,7 +6,8 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Disassembler_Z80_hpp
#define StaticAnalyser_Disassembler_Z80_hpp
#include <cstdint>
#include <functional>
@@ -14,7 +15,9 @@
#include <set>
#include <vector>
namespace Analyser::Static::Z80 {
namespace Analyser {
namespace Static {
namespace Z80 {
struct Instruction {
/*! The address this instruction starts at. This is a mapped address. */
@@ -75,18 +78,13 @@ struct Disassembly {
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
enum class Approach {
/// Disassemble from the supplied entry points until an indeterminate branch or return only, adding other fully-static
/// entry points as they are observed.
Recursive,
/// Disassemble all supplied bytes, regardless of what nonsense may be encountered by accidental parsing of data areas.
Exhaustive,
};
Disassembly Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach);
std::vector<uint16_t> entry_points);
}
}
}
#endif /* StaticAnalyser_Disassembler_Z80_hpp */

View File

@@ -6,14 +6,22 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
#define Analyser_Static_DiskII_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::DiskII {
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

@@ -40,8 +40,6 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
target->basic_version = Target::BASICVersion::Any;
// Inspect any supplied disks.
//
// TODO: how best can these be discerned from MS-DOS and MSX disks?
if(!media.disks.empty()) {
// DOS will be needed.
target->dos = Target::DOS::EXDOS;

View File

@@ -6,14 +6,22 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Enterprise {
namespace Analyser {
namespace Static {
namespace Enterprise {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Enterprise_Target_h
#define Analyser_Static_Enterprise_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
@@ -14,7 +15,9 @@
#include <string>
namespace Analyser::Static::Enterprise {
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
@@ -48,3 +51,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

View File

@@ -1,100 +0,0 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/12/2023.
// Copyright 2023 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "../Enterprise/StaticAnalyser.hpp"
#include "../PCCompatible/StaticAnalyser.hpp"
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Constants.hpp"
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
auto &disk = media.disks.front();
TargetList targets;
// Total list of potential platforms is:
//
// * the Enterprise (and, by extension, CP/M-targetted software);
// * the Atari ST;
// * the MSX (ditto on CP/M); and
// * the PC.
//
// (though the MSX and Atari ST don't currently call in here for now)
// 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);
}
// Attempt to grab MFM track 0, sector 1: the boot sector.
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
Storage::Disk::track_serialisation(
*track_zero,
Storage::Encodings::MFM::MFMBitLength
), Storage::Encodings::MFM::Density::Double);
// 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);
}
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
for(const auto &pair: sector_map) {
if(pair.second.address.sector == 1) {
boot_sector = &pair.second;
break;
}
}
// This shouldn't technically be possible since the disk has been identified as FAT12, but be safe.
if(!boot_sector) {
return {};
}
// Check for key phrases that imply a PC disk.
const auto &sample = boot_sector->samples[0];
const std::vector<std::string> pc_strings = {
// MS-DOS strings.
"MSDOS",
"Non-System disk or disk error",
// DOS Plus strings.
"Insert a SYSTEM disk",
};
for(const auto &string: pc_strings) {
if(
std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
) {
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
}
}
// TODO: look for a COM, EXE or BAT, inspect. AUTOEXEC.BAT and/or CONFIG.SYS could be either PC or MSX.
// Disassembling the boot sector doesn't necessarily work, as several Enterprise titles out there in the wild seem
// to have been created by WINIMAGE which adds an x86 PC-style boot sector.
// Enterprise notes: EXOS files all start with a 16-byte header which should begin with a 0 and then have a type
// byte that will be 0xa or lower; cf http://epbas.lgb.hu/readme.html
//
// Some disks commonly passed around as Enterprise software are actually CP/M software, expecting IS-DOS (the CP/M
// clone) to be present. It's certainly possible the same could be true of MSX disks and MSX-DOS. So analysing COM
// files probably means searching for CALL 5s and/or INT 21hs, if not a more rigorous disassembly.
//
// I have not been able to locate a copy of IS-DOS so there's probably not much that can be done here; perhaps I
// 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);
}

View File

@@ -1,19 +0,0 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/12/2023.
// Copyright 2023 Thomas Harte. All rights reserved.
//
#pragma once
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::FAT12 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}

View File

@@ -6,11 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Cartridge_hpp
#define Cartridge_hpp
#include "../../../Storage/Cartridge/Cartridge.hpp"
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
/*!
Extends the base cartridge class by adding a (guess at) the banking scheme.
@@ -31,3 +34,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
};
}
}
}
#endif /* Cartridge_hpp */

View File

@@ -37,11 +37,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
auto target = std::make_unique<Analyser::Static::MSX::Target>();
target->confidence = confidence;
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
if(segment.data.size() < 48*1024) {
target->model = Analyser::Static::MSX::Target::Model::MSX1;
}
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
} else {
@@ -105,7 +100,6 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// TODO: check for a rational init address?
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
// Bonus observation: all such ROMs are from the MSX 1 era.
if(data_size <= 0xc000) {
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
continue;
@@ -115,16 +109,97 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// be at play; disassemble to try to figure it out.
std::vector<uint8_t> first_8k;
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
const Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ init_address },
Analyser::Static::Z80::Approach::Exhaustive
{ init_address }
);
// // Look for a indirect store followed by an unconditional JP or CALL into another
// // segment, that's a fairly explicit sign where found.
using Instruction = Analyser::Static::Z80::Instruction;
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
bool is_ascii = false;
// auto iterator = instructions.begin();
// while(iterator != instructions.end()) {
// auto next_iterator = iterator;
// next_iterator++;
// if(next_iterator == instructions.end()) break;
//
// if( iterator->second.operation == Instruction::Operation::LD &&
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
// (
// iterator->second.operand == 0x5000 ||
// iterator->second.operand == 0x6000 ||
// iterator->second.operand == 0x6800 ||
// iterator->second.operand == 0x7000 ||
// iterator->second.operand == 0x77ff ||
// iterator->second.operand == 0x7800 ||
// iterator->second.operand == 0x8000 ||
// iterator->second.operand == 0x9000 ||
// iterator->second.operand == 0xa000
// ) &&
// (
// next_iterator->second.operation == Instruction::Operation::CALL ||
// next_iterator->second.operation == Instruction::Operation::JP
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x6800:
// if(address >= 0x6000 && address < 0x6800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x7000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// if(address >= 0x7000 && address < 0x7800) {
// is_ascii = true;
// }
// break;
// case 0x77ff:
// if(address >= 0x7000 && address < 0x7800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
// }
// break;
// case 0x7800:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x8000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x9000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0xa000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
// }
// break;
// case 0xb000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// }
// }
//
// iterator = next_iterator;
// Look for LD (nnnn), A instructions, and collate those addresses.
std::map<uint16_t, int> address_counts;
@@ -136,46 +211,49 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
}
}
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
// Weight confidences by number of observed hits.
float total_hits =
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
address_counts[0xa000] + address_counts[0x5000] +
address_counts[0x9000] + address_counts[0xb000]
);
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
const bool is_konami = (konami_total * 5) / (total_hits * 3);
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float(ascii_8kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float(ascii_16kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
float(konami_total) / float(total_hits)));
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
float(konami_with_scc_total) / float(total_hits)));
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}

View File

@@ -6,14 +6,21 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
#define StaticAnalyser_MSX_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */

View File

@@ -6,14 +6,17 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_MSX_Tape_hpp
#define StaticAnalyser_MSX_Tape_hpp
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
struct File {
std::string name;
@@ -35,3 +38,7 @@ struct File {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* StaticAnalyser_MSX_Tape_hpp */

View File

@@ -6,26 +6,22 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_disk_drive = false;
bool has_msx_music = true;
std::string loading_command;
ReflectableEnum(Model,
MSX1,
MSX2
);
Model model = Model::MSX2;
ReflectableEnum(Region,
Japan,
USA,
@@ -36,13 +32,14 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
Target(): Analyser::Static::Target(Machine::MSX) {
if(needs_declare()) {
DeclareField(has_disk_drive);
DeclareField(has_msx_music);
DeclareField(region);
AnnounceEnum(Region);
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}
}
}
#endif /* Analyser_Static_MSX_Target_h */

View File

@@ -6,14 +6,22 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Macintosh {
namespace Analyser {
namespace Static {
namespace Macintosh {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Macintosh {
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
@@ -28,3 +31,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_Macintosh_Target_h */

View File

@@ -85,7 +85,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 2);
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
@@ -108,7 +108,7 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
use disassembly to test for likely matches.
*/
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 1);
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
@@ -175,7 +175,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
Storage::Encodings::MFM::Parser parser(true, disk);
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;

View File

@@ -6,14 +6,21 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp
#define StaticAnalyser_Oric_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,14 +6,17 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Oric_Tape_hpp
#define StaticAnalyser_Oric_Tape_hpp
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
struct File {
std::string name;
@@ -31,3 +34,7 @@ struct File {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* Tape_hpp */

View File

@@ -6,14 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ROM,
@@ -54,3 +57,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

@@ -1,25 +0,0 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// No analysis is applied yet.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::PCCompatible::Target;
auto *const target = new Target();
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@@ -1,19 +0,0 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2019.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#pragma once
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::PCCompatible {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}

View File

@@ -1,37 +0,0 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#pragma once
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::PCCompatible {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(VideoAdaptor,
MDA,
CGA);
VideoAdaptor adaptor = VideoAdaptor::CGA;
ReflectableEnum(Speed,
ApproximatelyOriginal,
Fast);
Speed speed = Speed::Fast;
Target() : Analyser::Static::Target(Machine::PCCompatible) {
if(needs_declare()) {
AnnounceEnum(VideoAdaptor);
AnnounceEnum(Speed);
DeclareField(adaptor);
DeclareField(speed);
}
}
};
}

View File

@@ -6,14 +6,21 @@
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp
#define StaticAnalyser_Sega_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::Sega {
namespace Analyser {
namespace Static {
namespace Sega {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Sega {
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class Model {
@@ -45,8 +48,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
};
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
}
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
}
}
}
#endif /* Analyser_Static_Sega_Target_h */

View File

@@ -9,7 +9,6 @@
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <iterator>
@@ -26,11 +25,9 @@
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Enterprise/StaticAnalyser.hpp"
#include "FAT12/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "PCCompatible/StaticAnalyser.hpp"
#include "Sega/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
#include "ZXSpectrum/StaticAnalyser.hpp"
@@ -51,20 +48,18 @@
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/IMD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/PCBooter.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/DSK.hpp"
#include "../../Storage/MassStorage/Formats/HDV.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
@@ -86,10 +81,7 @@
// Target Platform Types
#include "../../Storage/TargetPlatforms.hpp"
template<class> inline constexpr bool always_false_v = false;
using namespace Analyser::Static;
using namespace Storage;
namespace {
@@ -103,199 +95,126 @@ std::string get_extension(const std::string &name) {
return extension;
}
class MediaAccumulator {
public:
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
template <typename InstanceT>
void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
media.disks.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
media.tapes.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::Cartridge::Cartridge, InstanceT>) {
media.cartridges.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
media.mass_storage_devices.push_back(instance);
} else {
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
}
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();
}
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
template <typename InstanceT, typename... Args>
void insert(TargetPlatform::IntType platforms, Args &&... args) {
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
}
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
template <typename InstanceT, typename... Args>
void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
try {
insert<InstanceT>(platforms, std::forward<Args>(args)...);
} catch(...) {}
}
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
/// providing the file name as the only construction argument.
template <typename InstanceT>
void try_standard(TargetPlatform::IntType platforms, const char *extension) {
if(name_matches(extension)) {
try_insert<InstanceT>(platforms, file_name_);
}
}
bool name_matches(const char *extension) {
return extension_ == extension;
}
Media media;
private:
const std::string &file_name_;
TargetPlatform::IntType &potential_platforms_;
const std::string extension_;
};
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
MediaAccumulator accumulator(file_name, potential_platforms);
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
potential_platforms |= platforms;\
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
#define Insert(list, class, platforms, ...) \
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
#define TryInsert(list, class, platforms, ...) \
try {\
Insert(list, class, platforms, __VA_ARGS__) \
} catch(...) {}
#define Format(ext, list, class, platforms) \
if(extension == ext) { \
TryInsert(list, class, platforms, file_name) \
}
// 2MG
if(accumulator.name_matches("2mg")) {
if(extension == "2mg") {
// 2MG uses a factory method; defer to it.
try {
const auto media = Disk::Disk2MG::open(file_name);
std::visit([&](auto &&arg) {
using Type = typename std::decay<decltype(arg)>::type;
if constexpr (std::is_same<Type, std::nullptr_t>::value) {
// It's valid for no media to be returned.
} else if constexpr (std::is_same<Type, Disk::DiskImageHolderBase *>::value) {
accumulator.insert(TargetPlatform::DiskII, std::shared_ptr<Disk::DiskImageHolderBase>(arg));
} else if constexpr (std::is_same<Type, MassStorage::MassStorageDevice *>::value) {
// TODO: or is it Apple IIgs?
accumulator.insert(TargetPlatform::AppleII, std::shared_ptr<MassStorage::MassStorageDevice>(arg));
} else {
static_assert(always_false_v<Type>, "Unexpected type encountered.");
}
}, media);
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
} catch(...) {}
}
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "80");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "81");
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
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 | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, 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
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format( "ipf",
result.disks,
Disk::DiskImageHolder<Storage::Disk::IPF>,
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
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::DiskII) // PO (original Apple II kind)
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Atari2600, "a26");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
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<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");
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "dsd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::CPCDSK>>(
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "dsk");
accumulator.try_standard<MassStorage::HFV>(TargetPlatform::Macintosh, "dsk");
accumulator.try_standard<MassStorage::DSK>(TargetPlatform::Macintosh, "dsk");
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<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "image");
accumulator.try_standard<Disk::DiskImageHolder<Disk::IMD>>(TargetPlatform::PCCompatible, "imd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "img");
// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
if(accumulator.name_matches("img")) {
try {
accumulator.insert<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::FAT12, file_name);
} catch(...) {
accumulator.try_standard<Disk::DiskImageHolder<Disk::PCBooter>>(TargetPlatform::PCCompatible, "img");
}
// PO (Apple IIgs kind)
if(extension == "po") {
TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
}
accumulator.try_standard<Disk::DiskImageHolder<Disk::IPF>>(
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
"ipf");
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "o");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "po");
if(accumulator.name_matches("po")) {
accumulator.try_insert<Disk::DiskImageHolder<Disk::MacintoshIMG>>(
TargetPlatform::AppleIIgs,
file_name, Disk::MacintoshIMG::FixedType::GCR);
}
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
if(accumulator.name_matches("prg")) {
// Try instantiating as a ROM; failing that accept as a tape.
// PRG
if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape
try {
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {
try {
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {}
}
}
accumulator.try_standard<Cartridge::BinaryDump>(
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX,
"rom");
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX) // ROM
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sg");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sms");
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "ssd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
#undef Format
#undef Insert
#undef TryInsert
#undef InsertInstance
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "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");
accumulator.try_standard<Tape::TZX>(TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum, "tzx");
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
return accumulator.media;
return result;
}
Media Analyser::Static::GetMedia(const std::string &file_name) {
@@ -304,28 +223,26 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
}
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
const std::string extension = get_extension(file_name);
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
const auto try_snapshot = [&](const char *ext, auto loader) -> bool {
if(extension != ext) {
return false;
}
try {
auto target = loader(file_name);
if(target) {
targets.push_back(std::move(target));
return true;
}
} catch(...) {}
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
return false;
};
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
if(try_snapshot("sna", Storage::State::SNA::load)) return targets;
if(try_snapshot("szx", Storage::State::SZX::load)) return targets;
if(try_snapshot("z80", Storage::State::Z80::load)) return targets;
#undef TryInsert
// Otherwise:
//
@@ -336,33 +253,28 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
// 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);
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));
};
append(TargetPlatform::Acorn, Acorn::GetTargets);
append(TargetPlatform::AmstradCPC, AmstradCPC::GetTargets);
append(TargetPlatform::AppleII, AppleII::GetTargets);
append(TargetPlatform::AppleIIgs, AppleIIgs::GetTargets);
append(TargetPlatform::Amiga, Amiga::GetTargets);
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
append(TargetPlatform::AtariST, AtariST::GetTargets);
append(TargetPlatform::Coleco, Coleco::GetTargets);
append(TargetPlatform::Commodore, Commodore::GetTargets);
append(TargetPlatform::DiskII, DiskII::GetTargets);
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
append(TargetPlatform::FAT12, FAT12::GetTargets);
append(TargetPlatform::Macintosh, Macintosh::GetTargets);
append(TargetPlatform::MSX, MSX::GetTargets);
append(TargetPlatform::Oric, Oric::GetTargets);
append(TargetPlatform::PCCompatible, PCCompatible::GetTargets);
append(TargetPlatform::Sega, Sega::GetTargets);
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
}
Append(Acorn);
Append(AmstradCPC);
Append(AppleII);
Append(AppleIIgs);
Append(Amiga);
Append(Atari2600);
Append(AtariST);
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Enterprise);
Append(Macintosh);
Append(MSX);
Append(Oric);
Append(Sega);
Append(ZX8081);
Append(ZXSpectrum);
#undef Append
// Reset any tapes to their initial position.
for(const auto &target : targets) {

View File

@@ -6,7 +6,8 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_hpp
#define StaticAnalyser_hpp
#include "../Machines.hpp"
@@ -20,7 +21,8 @@
#include <string>
#include <vector>
namespace Analyser::Static {
namespace Analyser {
namespace Static {
struct State;
@@ -38,15 +40,12 @@ struct Media {
}
Media &operator +=(const Media &rhs) {
const auto append = [&](auto &destination, auto &source) {
destination.insert(destination.end(), source.begin(), source.end());
};
append(disks, rhs.disks);
append(tapes, rhs.tapes);
append(cartridges, rhs.cartridges);
append(mass_storage_devices, rhs.mass_storage_devices);
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
append(disks);
append(tapes);
append(cartridges);
append(mass_storage_devices);
#undef append
return *this;
}
};
@@ -57,7 +56,7 @@ struct Media {
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() = default;
virtual ~Target() {}
// This field is entirely optional.
std::unique_ptr<Reflection::Struct> state;
@@ -81,3 +80,6 @@ TargetList GetTargets(const std::string &file_name);
Media GetMedia(const std::string &file_name);
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,14 +6,21 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::ZX8081 {
namespace Analyser {
namespace Static {
namespace ZX8081 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,14 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::ZX8081 {
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemoryModel,
@@ -38,3 +41,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

View File

@@ -8,14 +8,11 @@
#include "StaticAnalyser.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
#include "Target.hpp"
#include <algorithm>
namespace {
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
@@ -36,69 +33,25 @@ bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
}
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
Storage::Encodings::MFM::Parser parser(true, disk);
// Grab absolutely any sector from the first track to determine general encoding.
const Storage::Encodings::MFM::Sector *any_sector = parser.any_sector(0, 0);
if(!any_sector) return false;
// Determine the sector base and get logical sector 1.
const uint8_t sector_base = any_sector->address.sector & 0xc0;
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, sector_base + 1);
// Get logical sector 1; the Spectrum appears to support various physical
// sectors as sector 1.
Storage::Encodings::MFM::Sector *boot_sector = nullptr;
uint8_t sector_mask = 0;
while(!boot_sector) {
boot_sector = parser.get_sector(0, 0, sector_mask + 1);
sector_mask += 0x40;
if(!sector_mask) break;
}
if(!boot_sector) return false;
Storage::Disk::CPM::ParameterBlock cpm_format{};
switch(sector_base) {
case 0x40: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format(); break;
case 0xc0: cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format(); break;
default: {
// Check the first ten bytes of the first sector for the disk format; if these are all
// the same value then instead substitute a default format.
std::array<uint8_t, 10> format;
std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin());
if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) {
format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52};
}
// Parse those ten bytes as:
//
// Byte 0: disc type
// Byte 1: sidedness
// bits 0-6: arrangement
// 0 => single sided
// 1 => double sided, flip sides
// 2 => double sided, up and over
// bit 7: double-track
// Byte 2: number of tracks per side
// Byte 3: number of sectors per track
// Byte 4: Log2(sector size) - 7
// Byte 5: number of reserved tracks
// Byte 6: Log2(block size) - 7
// Byte 7: number of directory blocks
// Byte 8: gap length (read/write)
// Byte 9: gap length(format)
cpm_format.sectors_per_track = format[3];
cpm_format.tracks = format[2];
cpm_format.block_size = 128 << format[6];
cpm_format.first_sector = sector_base + 1;
cpm_format.reserved_tracks = format[5];
// i.e. bits set downward from 0x4000 for as many blocks as form the catalogue.
cpm_format.catalogue_allocation_bitmap = 0x8000 - (0x8000 >> format[7]);
} break;
// Test that the contents of the boot sector sum to 3, modulo 256.
uint8_t byte_sum = 0;
for(auto byte: boot_sector->samples[0]) {
byte_sum += byte;
}
// If the boot sector sums to 3 modulo 256 then this is a Spectrum disk.
const auto byte_sum = static_cast<uint8_t>(
std::accumulate(boot_sector->samples[0].begin(), boot_sector->samples[0].end(), 0));
if(byte_sum == 3) {
return true;
}
// ... otherwise read a CPM directory and look for a BASIC program called "DISK".
const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format);
return catalogue && catalogue->is_zx_spectrum_booter();
return byte_sum == 3;
}
}

View File

@@ -6,14 +6,21 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser::Static::ZXSpectrum {
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -6,13 +6,16 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_ZXSpectrum_Target_h
#define Analyser_Static_ZXSpectrum_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::ZXSpectrum {
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -36,3 +39,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
};
}
}
}
#endif /* Target_h */

View File

@@ -1,99 +0,0 @@
![Clock Signal Application Icon](READMEImages/Icon.png)
# Building Clock Signal
Clock Signal is available as [a macOS native application using
Metal](#macos-app) or as [a cross-platform command-line-driven SDL executable
using OpenGL](#sdl-app).
## macOS app
The macOS native application requires a Metal-capable Mac running macOS 10.13 or
later and has no prerequisites beyond the normal system libraries. It can be
built [using Xcode](#building-the-macos-app-using-xcode) or on the command line
[using `xcodebuild`](#building-the-macos-app-using-xcodebuild).
Machine ROMs are intended to be built into the application bundle; populate the
dummy folders below ROMImages before building.
The Xcode project is configured to sign the application using the developer's
certificate, but if you are not the developer then you will get a "No signing
certificate" error. To avoid this, you'll specify that you want to sign the
application to run locally.
### Building the macOS app using Xcode
Open the Clock Signal Xcode project in OSBindings/Mac.
To avoid signing errors, edit the project, select the Signing & Capabilities
tab, and change the Signing Certificate drop-down menu from "Development" to
"Sign to Run Locally".
To avoid crashes when running Clock Signal via Xcode on older Macs due to
"unrecognized selector sent to instance" errors, edit the scheme, and in the Run
section, scroll down to the Metal heading and uncheck the "API Validation"
checkbox.
To build, choose "Build" from Xcode's Product menu or press
<kbd>Command</kbd> + <kbd>B</kbd>.
To build and run, choose "Run" from the Product menu or press
<kbd>Command</kbd> + <kbd>R</kbd>.
To see the folder where the Clock Signal application was built, choose "Show
Build Folder in Finder" from the Product menu. Look in the "Products" folder for
a folder named after the configuration (e.g. "Debug" or "Release").
### Building the macOS app using `xcodebuild`
To build, change to the OSBindings/Mac directory in the Terminal, then run
`xcodebuild`, specifying `-` as the code sign identity to sign the application
to run locally to avoid signing errors:
cd OSBindings/Mac
xcodebuild CODE_SIGN_IDENTITY=-
`xcodebuild` will create a "build" folder in this directory which is where you
can find the Clock Signal application after it's compiled, in a directory named
after the configuration (e.g. "Debug" or "Release").
## SDL app
The SDL app can be built on Linux, BSD, macOS, and other Unix-like operating
systems. Prerequisites are SDL 2, ZLib and OpenGL (or Mesa). OpenGL 3.2 or
better is required at runtime. It can be built [using
SCons](#building-the-sdl-app-using-scons).
### Building the SDL app using SCons
To build, change to the OSBindings/SDL directory and run `scons`. You can add a
`-j` flag to build in parallel. For example, if you have 8 processor cores:
cd OSBindings/SDL
scons -j8
The `clksignal` executable will be created in this directory. You can run it
from here or install it by copying it where you want it, for example:
cp clksignal /usr/local/bin
To start an emulator with a particular disk image `file`, if you've installed
`clksignal` to a directory in your `PATH`, run:
clksignal file
Or if you're running it from the current directory:
./clksignal file
Other options are availble. Run `clksignal` or `./clksignal` with no arguments
to learn more.
Setting up `clksignal` as the associated program for supported file types in
your favoured filesystem browser is recommended; it has no file navigation
abilities of its own.
Some emulated systems require the provision of original machine ROMs. These are
not included and may be located in either /usr/local/share/CLK/ or
/usr/share/CLK/. You will be prompted for them if they are found to be missing.
The structure should mirror that under OSBindings in the source archive; see the
readme.txt in each folder to determine the proper files and names ahead of time.

30
BUILD.txt Normal file
View File

@@ -0,0 +1,30 @@
Linux, BSD
==========
Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime.
Build:
cd OSBindings/SDL
scons
Optionally:
cp clksignal /usr/bin
To launch:
clksignal file
Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own.
Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time.
macOS
=====
There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application.
Build: open the Xcode project in OSBindings/Mac and press command+b.
Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building.

View File

@@ -1,70 +0,0 @@
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
project(CLK
LANGUAGES CXX
VERSION 24.01.22
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CLK_UIS "SDL")
#list(PREPEND CLK_UIS "QT")
#if(APPLE)
# list(PREPEND CLK_UIS "MAC")
# set(CLK_DEFAULT_UI "MAC")
#else()
set(CLK_DEFAULT_UI "SDL")
#endif()
set(CLK_UI ${CLK_DEFAULT_UI} CACHE STRING "User interface")
set_property(CACHE CLK_UI PROPERTY STRINGS ${CLK_UIS})
if(NOT CLK_UI IN_LIST CLK_UIS)
list(JOIN CLK_UIS ", " CLK_UIS_PRETTY)
message(FATAL_ERROR "Invalid value for 'CLK_UI'; must be one of ${CLK_UIS_PRETTY}")
endif()
message(STATUS "Configuring for ${CLK_UI} UI")
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include("CLK_SOURCES")
add_executable(clksignal ${CLK_SOURCES})
if(MSVC)
target_compile_options(clksignal PRIVATE /W4)
else()
# TODO: Add -Wpedandic.
target_compile_options(clksignal PRIVATE -Wall -Wextra)
endif()
find_package(ZLIB REQUIRED)
target_link_libraries(clksignal PRIVATE ZLIB::ZLIB)
if(CLK_UI STREQUAL "MAC")
enable_language(OBJC OBJCXX SWIFT)
# TODO: Build the Mac version.
else()
find_package(OpenGL REQUIRED)
target_link_libraries(clksignal PRIVATE OpenGL::GL)
if(APPLE)
target_compile_definitions(clksignal PRIVATE "GL_SILENCE_DEPRECATION" "IGNORE_APPLE")
endif()
endif()
if(CLK_UI STREQUAL "QT")
# TODO: Build the Qt version.
elseif(APPLE)
set(BLA_VENDOR Apple)
find_package(BLAS REQUIRED)
target_link_libraries(clksignal PRIVATE BLAS::BLAS)
endif()
if(CLK_UI STREQUAL "SDL")
find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2)
target_link_libraries(clksignal PRIVATE SDL2::SDL2)
endif()
# TODO: Investigate building on Windows.

View File

@@ -6,7 +6,8 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp
#include "ForceInline.hpp"
@@ -276,3 +277,5 @@ template <class T> class HalfClockReceiver: public T {
private:
HalfCycles half_cycles_;
};
#endif /* ClockReceiver_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ClockingHintSource_hpp
#define ClockingHintSource_hpp
namespace ClockingHint {
@@ -83,3 +84,5 @@ class Source {
};
}
#endif /* ClockingHintSource_h */

View File

@@ -6,7 +6,8 @@
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef DeferredQueue_h
#define DeferredQueue_h
#include <functional>
#include <vector>
@@ -73,11 +74,6 @@ template <typename TimeUnit> class DeferredQueue {
}
}
/// @returns @c true if no actions are enqueued; @c false otherwise.
bool empty() const {
return pending_actions_.empty();
}
private:
// The list of deferred actions.
struct DeferredAction {
@@ -124,3 +120,5 @@ template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<
private:
std::function<void(TimeUnit)> target_;
};
#endif /* DeferredQueue_h */

View File

@@ -6,7 +6,8 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef DeferredValue_h
#define DeferredValue_h
/*!
Provides storage for a single deferred value: one with a current value and a certain number
@@ -43,3 +44,5 @@ template <int DeferredDepth, typename ValueT> class DeferredValue {
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
}
};
#endif /* DeferredValue_h */

View File

@@ -6,7 +6,8 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifndef NDEBUG
@@ -21,3 +22,5 @@
#endif
#endif
#endif /* ForceInline_h */

View File

@@ -6,15 +6,14 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef JustInTime_h
#define JustInTime_h
#include "ClockReceiver.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"
#include <atomic>
/*!
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
of time since run_for was last called.
@@ -25,7 +24,7 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
If the held object implements @c next_sequence_point() then it'll be used to flush implicitly
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
as and when sequence points are hit. Callers can use will_flush() to predict these.
If the held object is a subclass of ClockingHint::Source, this template will register as an
@@ -39,7 +38,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
private:
/*!
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
**Does not delete the object.**
@@ -122,13 +121,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// If this object provides sequence points, checks for changes to the next
/// sequence point upon deletion of the pointer.
[[nodiscard]] forceinline auto operator->() {
#ifndef NDEBUG
assert(!flush_concurrency_check_.test_and_set());
#endif
flush();
#ifndef NDEBUG
flush_concurrency_check_.clear();
#endif
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
}
@@ -137,13 +130,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
[[nodiscard]] forceinline auto operator -> () const {
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
#ifndef NDEBUG
assert(!non_const_this->flush_concurrency_check_.test_and_set());
#endif
non_const_this->flush();
#ifndef NDEBUG
non_const_this->flush_concurrency_check_.clear();
#endif
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
}
@@ -246,9 +233,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.next_sequence_point();
time_until_event_ = object_.get_next_sequence_point();
} else {
const auto time = object_.next_sequence_point();
const auto time = object_.get_next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
@@ -271,16 +258,12 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
bool did_flush_ = false;
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
clocking_preference_ = clocking;
}
#ifndef NDEBUG
std::atomic_flag flush_concurrency_check_{};
#endif
};
/*!
@@ -293,7 +276,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
object_(std::forward<Args>(args)...),
threshold_(threshold) {}
threshold_(threshold) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
@@ -332,5 +315,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
LocalTimeScale time_since_update_;
TargetTimeScale threshold_;
bool is_flushed_ = true;
Concurrency::AsyncTaskQueue<true> task_queue_;
Concurrency::AsyncTaskQueue task_queue_;
};
#endif /* JustInTime_h */

View File

@@ -6,7 +6,8 @@
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ScanSynchroniser_h
#define ScanSynchroniser_h
#include "../Outputs/ScanTarget.hpp"
@@ -83,3 +84,5 @@ class ScanSynchroniser {
};
}
#endif /* ScanSynchroniser_h */

View File

@@ -6,7 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef TimeTypes_h
#define TimeTypes_h
#include <chrono>
@@ -19,8 +20,6 @@ inline Nanos nanos_now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
inline Seconds seconds(Nanos nanos) {
return double(nanos) / 1e9;
}
}
#endif /* TimeTypes_h */

View File

@@ -6,7 +6,8 @@
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef VSyncPredictor_hpp
#define VSyncPredictor_hpp
#include "TimeTypes.hpp"
#include <cassert>
@@ -150,3 +151,5 @@ class VSyncPredictor {
};
}
#endif /* VSyncPredictor_hpp */

View File

@@ -9,11 +9,9 @@
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#include "../../Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::WDFDC> logger;
}
#define LOG_PREFIX "[WD FDC] "
#include "../../Outputs/Log.hpp"
using namespace WD;
@@ -31,10 +29,10 @@ void WD1770::write(int address, uint8_t value) {
if((value&0xf0) == 0xd0) {
if(value == 0xd0) {
// Force interrupt **immediately**.
logger.info().append("Force interrupt immediately");
LOG("Force interrupt immediately");
posit_event(int(Event1770::ForceInterrupt));
} else {
logger.error().append("TODO: force interrupt");
ERROR("!!!TODO: force interrupt!!!");
update_status([] (Status &status) {
status.type = Status::One;
});
@@ -68,7 +66,7 @@ uint8_t WD1770::read(int address) {
// Per Jean Louis-Guérin's documentation:
//
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
// read live after a type 1.
// * the track 0 bit is captured during a type 1 instruction and lost upon any other type,
// it is not live sampled.
@@ -101,14 +99,14 @@ uint8_t WD1770::read(int address) {
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
// logger.info().append("Returned status %02x of type %d", status, 1+int(status_.type));
// LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type));
return status;
}
case 1:
logger.info().append("Returned track %d", track_);
LOG("Returned track " << int(track_));
return track_;
case 2:
logger.info().append("Returned sector %d", sector_);
LOG("Returned sector " << int(sector_));
return sector_;
case 3:
update_status([] (Status &status) {
@@ -214,7 +212,7 @@ void WD1770::posit_event(int new_event_type) {
// Wait for a new command, branch to the appropriate handler.
case 0:
wait_for_command:
logger.info().append("Idle...");
LOG("Idle...");
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
@@ -231,7 +229,7 @@ void WD1770::posit_event(int new_event_type) {
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
});
logger.info().append("Starting %02x", command_);
LOG("Starting " << PADHEX(2) << int(command_));
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
@@ -261,7 +259,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = false;
});
logger.info().append("Step/Seek/Restore with track %d data %d", track_, data_);
LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_));
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
if(has_motor_on_line()) goto begin_type1_spin_up;
@@ -341,7 +339,7 @@ void WD1770::posit_event(int new_event_type) {
READ_ID();
if(index_hole_count_ == 6) {
logger.info().append("Nothing found to verify");
LOG("Nothing found to verify");
update_status([] (Status &status) {
status.seek_error = true;
});
@@ -359,7 +357,7 @@ void WD1770::posit_event(int new_event_type) {
}
if(header_[0] == track_) {
logger.info().append("Reached track %d", track_);
LOG("Reached track " << std::dec << int(track_));
update_status([] (Status &status) {
status.crc_error = false;
});
@@ -432,7 +430,7 @@ void WD1770::posit_event(int new_event_type) {
READ_ID();
if(index_hole_count_ == 5) {
logger.info().append("Failed to find sector %d", sector_);
LOG("Failed to find sector " << std::dec << int(sector_));
update_status([] (Status &status) {
status.record_not_found = true;
});
@@ -442,12 +440,12 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
logger.info().append("Considering %d/%d", header_[0], header_[2]);
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
logger.info().append("Found %d/%d", header_[0], header_[2]);
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if(get_crc_generator().get_value()) {
logger.info().append("CRC error; back to searching");
LOG("CRC error; back to searching");
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -505,18 +503,18 @@ void WD1770::posit_event(int new_event_type) {
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
logger.info().append("CRC error; terminating");
LOG("CRC error; terminating");
update_status([] (Status &status) {
status.crc_error = true;
});
goto wait_for_command;
}
logger.info().append("Finished reading sector %d", sector_);
LOG("Finished reading sector " << std::dec << int(sector_));
if(command_ & 0x10) {
sector_++;
logger.info().append("Advancing to search for sector %d", sector_);
LOG("Advancing to search for sector " << std::dec << int(sector_));
goto test_type2_write_protection;
}
goto wait_for_command;
@@ -600,7 +598,7 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
logger.info().append("Wrote sector %d", sector_);
LOG("Wrote sector " << std::dec << int(sector_));
goto wait_for_command;

View File

@@ -6,7 +6,8 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef _770_hpp
#define _770_hpp
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
@@ -30,7 +31,7 @@ class WD1770: public Storage::Disk::MFMController {
@param p The type of controller to emulate.
*/
WD1770(Personality p);
virtual ~WD1770() = default;
virtual ~WD1770() {}
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
using Storage::Disk::MFMController::set_is_double_density;
@@ -140,3 +141,5 @@ class WD1770: public Storage::Disk::MFMController {
};
}
#endif /* _770_hpp */

View File

@@ -10,14 +10,6 @@
#include "../../Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::NCR5380> logger;
}
// TODO:
//
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
using namespace NCR::NCR5380;
using SCSI::Line;
@@ -36,16 +28,20 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
void NCR5380::write(int address, uint8_t value, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Set current SCSI bus state to %02x", value);
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
dma_acknowledge(value);
// printf("w %02x\n", value);
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
break;
case 1: {
logger.info().append("[1] Initiator command register set: %02x", value);
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
@@ -61,7 +57,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
} break;
case 2:
logger.info().append("[2] Set mode: %02x", value);
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
@@ -73,7 +69,6 @@ void NCR5380::write(int address, uint8_t value, bool) {
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
arbitration_in_progress_ = false;
phase_mismatch_ = false;
switch(mode_ & 0x3) {
case 0x0:
bus_output_ &= ~SCSI::Line::Busy;
@@ -93,36 +88,31 @@ void NCR5380::write(int address, uint8_t value, bool) {
bus_.update_observers();
break;
}
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
// is reset (0) in the Mode Register".
end_of_dma_ &= bool(value & 0x2);
update_control_output();
break;
case 3: {
logger.info().append("[3] Set target command: %02x", value);
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
target_command_ = value;
update_control_output();
} break;
case 4:
logger.info().append("[4] Set select enabled: %02x", value);
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
break;
case 5:
logger.info().append("[5] Start DMA send: %02x", value);
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::Send;
break;
case 6:
logger.info().append("[6] Start DMA target receive: %02x", value);
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
logger.info().append("[7] Start DMA initiator receive: %02x", value);
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
@@ -146,15 +136,18 @@ void NCR5380::write(int address, uint8_t value, bool) {
uint8_t NCR5380::read(int address, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Get current SCSI bus state: %02x", (bus_.get_state() & 0xff));
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
return dma_acknowledge();
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
return uint8_t(bus_.get_state());
case 1:
logger.info().append("[1] Initiator command register get: %c%c", arbitration_in_progress_ ? 'p' : '-', lost_arbitration_ ? 'l' : '-');
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
@@ -166,11 +159,11 @@ uint8_t NCR5380::read(int address, bool) {
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
logger.info().append("[2] Get mode");
// LOG("[SCSI 2] Get mode");
return mode_;
case 3:
logger.info().append("[3] Get target command");
// LOG("[SCSI 3] Get target command");
return target_command_;
case 4: {
@@ -184,38 +177,41 @@ uint8_t NCR5380::read(int address, bool) {
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
logger.info().append("[4] Get current bus state: %02x", result);
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const bool phase_matches =
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
const uint8_t result =
(end_of_dma_ ? 0x80 : 0x00) |
/* b7 = end of DMA */
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
/* b5 = parity error */
(irq_ ? 0x10 : 0x00) |
(phase_matches() ? 0x08 : 0x00) |
/* b4 = IRQ active */
(phase_matches ? 0x08 : 0x00) |
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
logger.info().append("[5] Get bus and status: %02x", result);
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
return result;
}
case 6:
logger.info().append("[6] Get input data");
// LOG("[SCSI 6] Get input data");
return 0xff;
case 7:
logger.info().append("[7] Reset parity/interrupt");
irq_ = false;
// LOG("[SCSI 7] Reset parity/interrupt");
return 0xff;
}
return 0;
}
SCSI::BusState NCR5380::target_output() const {
SCSI::BusState NCR5380::target_output() {
SCSI::BusState output = SCSI::DefaultBusState;
if(target_command_ & 0x08) output |= Line::Request;
if(target_command_ & 0x04) output |= Line::Message;
@@ -240,17 +236,6 @@ void NCR5380::update_control_output() {
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
/*
When connected as an Initiator with DMA Mode True,
if the phase lines I//O, C//D, and /MSG do not match the
phase bits in the Target Command Register, a phase mismatch
interrupt is generated when /REQ goes active.
*/
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
irq_ = true;
phase_mismatch_ = true;
}
switch(state_) {
default: break;
@@ -311,13 +296,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
dma_request_ = false;
break;
case SCSI::Line::Request:
// Don't issue a new DMA request if a phase mismatch has
// been detected and this is an intiator receiving.
// This is a bit of reading between the lines.
// (i.e. guesswork, partly)
dma_request_ =
!phase_mismatch_ ||
(dma_operation_ != DMAOperation::InitiatorReceive);
dma_request_ = true;
break;
case SCSI::Line::Request | SCSI::Line::Acknowledge:
dma_request_ = false;
@@ -337,38 +316,3 @@ void NCR5380::set_execution_state(ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}
size_t NCR5380::scsi_id() {
return device_id_;
}
bool NCR5380::dma_request() {
return dma_request_;
}
uint8_t NCR5380::dma_acknowledge() {
const uint8_t bus_state = uint8_t(bus_.get_state());
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
return bus_state;
}
void NCR5380::dma_acknowledge(uint8_t value) {
data_bus_ = value;
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
bool NCR5380::phase_matches() const {
const auto bus_state = bus_.get_state();
return
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
}

View File

@@ -6,14 +6,16 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ncr5380_hpp
#define ncr5380_hpp
#include <cstdint>
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
namespace NCR::NCR5380 {
namespace NCR {
namespace NCR5380 {
/*!
Models the NCR 5380, a SCSI interface chip.
@@ -22,24 +24,12 @@ class NCR5380 final: public SCSI::Bus::Observer {
public:
NCR5380(SCSI::Bus &bus, int clock_rate);
/*! Writes @c value to @c address. */
/*! Writes @c value to @c address. */
void write(int address, uint8_t value, bool dma_acknowledge = false);
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
/*! @returns The SCSI ID assigned to this device. */
size_t scsi_id();
/*! @return @c true if DMA request is active; @c false otherwise. */
bool dma_request();
/*! Signals DMA acknowledge with a simultaneous read. */
uint8_t dma_acknowledge();
/*! Signals DMA acknowledge with a simultaneous write. */
void dma_acknowledge(uint8_t);
private:
SCSI::Bus &bus_;
@@ -56,10 +46,6 @@ class NCR5380 final: public SCSI::Bus::Observer {
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
bool end_of_dma_ = false;
bool irq_ = false;
bool phase_mismatch_ = false;
enum class ExecutionState {
None,
@@ -77,11 +63,13 @@ class NCR5380 final: public SCSI::Bus::Observer {
void set_execution_state(ExecutionState state);
SCSI::BusState target_output() const;
SCSI::BusState target_output();
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
bool phase_matches() const;
};
}
}
#endif /* ncr5380_hpp */

View File

@@ -6,7 +6,8 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef _522_hpp
#define _522_hpp
#include <cstdint>
@@ -14,7 +15,8 @@
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS::MOS6522 {
namespace MOS {
namespace MOS6522 {
enum Port {
A = 0,
@@ -68,7 +70,7 @@ class IRQDelegatePortHandler: public PortHandler {
/// Sets the delegate that will receive notification of changes in the interrupt line.
void set_interrupt_delegate(Delegate *delegate);
/// Overrides @c PortHandler::set_interrupt_status, notifying the delegate if one is set.
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
void set_interrupt_status(bool new_status);
private:
@@ -136,6 +138,9 @@ template <class BusHandlerT> class MOS6522: public MOS6522Storage {
void evaluate_port_b_output();
};
}
}
#include "Implementation/6522Implementation.hpp"
#endif /* _522_hpp */

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