1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

Merge pull request #347 from TomHarte/DynamicAnalysis

Introduces dynamic selection of MSX MegaROM type
This commit is contained in:
Thomas Harte 2018-02-19 16:10:46 -05:00 committed by GitHub
commit 9c0a440c38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 2762 additions and 1406 deletions

View File

@ -0,0 +1,30 @@
//
// ConfidenceCounter.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ConfidenceCounter.hpp"
using namespace Analyser::Dynamic;
float ConfidenceCounter::get_confidence() {
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
}
void ConfidenceCounter::add_hit() {
hits_++;
}
void ConfidenceCounter::add_miss() {
misses_++;
}
void ConfidenceCounter::add_equivocal() {
if(hits_ > misses_) {
hits_++;
misses_++;
}
}

View File

@ -0,0 +1,47 @@
//
// ConfidenceCounter.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceCounter_hpp
#define ConfidenceCounter_hpp
#include "ConfidenceSource.hpp"
namespace Analyser {
namespace Dynamic {
/*!
Provides a confidence source that calculates its probability by virtual of a history of events.
The initial value of the confidence counter is 0.5.
*/
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() override;
/*! Records an event that implies this is the appropriate class — pushes probability up towards 1.0. */
void add_hit();
/*! Records an event that implies this is not the appropriate class — pushes probability down towards 0.0. */
void add_miss();
/*!
Records an event that could be correct but isn't necessarily so; which can push probability
down towards 0.5, but will never push it upwards.
*/
void add_equivocal();
private:
int hits_ = 1;
int misses_ = 1;
};
}
}
#endif /* ConfidenceCounter_hpp */

View File

@ -0,0 +1,28 @@
//
// ConfidenceSource.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceSource_hpp
#define ConfidenceSource_hpp
namespace Analyser {
namespace Dynamic {
/*!
Provides an abstract interface through which objects can declare the probability
that they are the proper target for their input; e.g. if an Acorn Electron is asked
to run an Atari 2600 program then its confidence should shrink towards 0.0; if the
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
*/
struct ConfidenceSource {
virtual float get_confidence() = 0;
};
}
}
#endif /* ConfidenceSource_hpp */

View File

@ -0,0 +1,28 @@
//
// ConfidenceSummary.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ConfidenceSummary.hpp"
#include <cassert>
#include <numeric>
using namespace Analyser::Dynamic;
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
sources_(sources), weights_(weights) {
assert(weights.size() == sources.size());
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
}
float ConfidenceSummary::get_confidence() {
float result = 0.0f;
for(std::size_t index = 0; index < sources_.size(); ++index) {
result += sources_[index]->get_confidence() * weights_[index];
}
return result / weight_sum_;
}

View File

@ -0,0 +1,46 @@
//
// ConfidenceSummary.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceSummary_hpp
#define ConfidenceSummary_hpp
#include "ConfidenceSource.hpp"
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Summaries a collection of confidence sources by calculating their weighted sum.
*/
class ConfidenceSummary: public ConfidenceSource {
public:
/*!
Instantiates a summary that will produce the weighted sum of
@c sources, each using the corresponding entry of @c weights.
Requires that @c sources and @c weights are of the same length.
*/
ConfidenceSummary(
const std::vector<ConfidenceSource *> &sources,
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() override;
private:
std::vector<ConfidenceSource *> sources_;
std::vector<float> weights_;
float weight_sum_;
};
}
}
#endif /* ConfidenceSummary_hpp */

View File

@ -0,0 +1,115 @@
//
// MultiCRTMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiCRTMachine.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
if(crt_machine) function(crt_machine);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *crt_machine = machine->crt_machine();
if(crt_machine) function(crt_machine);
}
}
void MultiCRTMachine::setup_output(float aspect_ratio) {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->setup_output(aspect_ratio);
});
}
void MultiCRTMachine::close_output() {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->close_output();
});
}
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_crt() : nullptr;
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(const Cycles cycles) {
perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(cycles);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
double MultiCRTMachine::get_clock_rate() {
// TODO: something smarter than this? Not all clock rates will necessarily be the same.
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_rate() : 0.0;
}
bool MultiCRTMachine::get_clock_is_unlimited() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_clock_is_unlimited() : false;
}
void MultiCRTMachine::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}
void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) {
// TODO:
}
void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) {
// TODO: consider passing along.
}
void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) {
// TODO: consider passing along.
}

View File

@ -0,0 +1,95 @@
//
// MultiCRTMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiCRTMachine_hpp
#define MultiCRTMachine_hpp
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/CRTMachine.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "MultiSpeaker.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the CRT machine interface to multiple machines.
Keeps a reference to the original vector of machines; will access it only after
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void multi_crt_did_run_machines() = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void setup_output(float aspect_ratio) override;
void close_output() override;
Outputs::CRT::CRT *get_crt() override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(const Cycles cycles) override;
double get_clock_rate() override;
bool get_clock_is_unlimited() override;
void set_delegate(::CRTMachine::Machine::Delegate *delegate) override;
private:
// CRTMachine::Machine::Delegate
void machine_did_change_clock_rate(Machine *machine) override;
void machine_did_change_clock_is_unlimited(Machine *machine) override;
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
};
}
}
#endif /* MultiCRTMachine_hpp */

View File

@ -0,0 +1,64 @@
//
// MultiConfigurable.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiConfigurable.hpp"
#include <algorithm>
using namespace Analyser::Dynamic;
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
Configurable::Device *device = machine->configurable_device();
if(device) devices_.push_back(device);
}
}
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
// Produce the list of unique options.
for(const auto &device : devices_) {
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
for(auto &option : device_options) {
if(std::find(options.begin(), options.end(), option) == options.end()) {
options.push_back(std::move(option));
}
}
}
return options;
}
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
for(const auto &device : devices_) {
device->set_selections(selection_by_option);
}
}
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_accurate_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}

View File

@ -0,0 +1,43 @@
//
// MultiConfigurable.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiConfigurable_hpp
#define MultiConfigurable_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configurable interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiConfigurable: public Configurable::Device {
public:
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
Configurable::SelectionSet get_accurate_selections() override;
Configurable::SelectionSet get_user_friendly_selections() override;
private:
std::vector<Configurable::Device *> devices_;
};
}
}
#endif /* MultiConfigurable_hpp */

View File

@ -0,0 +1,29 @@
//
// MultiConfigurationTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiConfigurationTarget.hpp"
using namespace Analyser::Dynamic;
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) targets_.push_back(configuration_target);
}
}
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) {
}
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}

View File

@ -0,0 +1,42 @@
//
// MultiConfigurationTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiConfigurationTarget_hpp
#define MultiConfigurationTarget_hpp
#include "../../../../Machines/ConfigurationTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configuration target interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
public:
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
void configure_as_target(const Analyser::Static::Target &target) override;
bool insert_media(const Analyser::Static::Media &media) override;
private:
std::vector<ConfigurationTarget::Machine *> targets_;
};
}
}
#endif /* MultiConfigurationTarget_hpp */

View File

@ -0,0 +1,22 @@
//
// MultiJoystickMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiJoystickMachine.hpp"
using namespace Analyser::Dynamic;
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
if(joystick_machine) machines_.push_back(joystick_machine);
}
}
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
return joysticks_;
}

View File

@ -0,0 +1,41 @@
//
// MultiJoystickMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiJoystickMachine_hpp
#define MultiJoystickMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the joystick machine interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiJoystickMachine: public JoystickMachine::Machine {
public:
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
private:
std::vector<JoystickMachine::Machine *> machines_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}
}
#endif /* MultiJoystickMachine_hpp */

View File

@ -0,0 +1,43 @@
//
// MultiKeyboardMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiKeyboardMachine.hpp"
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
}
void MultiKeyboardMachine::clear_all_keys() {
for(const auto &machine: machines_) {
machine->clear_all_keys();
}
}
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
for(const auto &machine: machines_) {
machine->set_key_state(key, is_pressed);
}
}
void MultiKeyboardMachine::type_string(const std::string &string) {
for(const auto &machine: machines_) {
machine->type_string(string);
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
for(const auto &machine: machines_) {
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
}
}

View File

@ -0,0 +1,44 @@
//
// MultiKeyboardMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiKeyboardMachine_hpp
#define MultiKeyboardMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Machines/KeyboardMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the keyboard machine interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
private:
std::vector<::KeyboardMachine::Machine *> machines_;
};
}
}
#endif /* MultiKeyboardMachine_hpp */

View File

@ -0,0 +1,60 @@
//
// MultiSpeaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiSpeaker.hpp"
using namespace Analyser::Dynamic;
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::vector<Outputs::Speaker::Speaker *> speakers;
for(const auto &machine: machines) {
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
if(speaker) speakers.push_back(speaker);
}
if(speakers.empty()) return nullptr;
return new MultiSpeaker(speakers);
}
MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) :
speakers_(speakers), front_speaker_(speakers.front()) {
for(const auto &speaker: speakers_) {
speaker->set_delegate(this);
}
}
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
float ideal = 0.0f;
for(const auto &speaker: speakers_) {
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
}
return ideal / static_cast<float>(speakers_.size());
}
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
}
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(delegate_ && speaker == front_speaker_) {
delegate_->speaker_did_complete_samples(this, buffer);
}
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
}

View File

@ -0,0 +1,58 @@
//
// MultiSpeaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiSpeaker_hpp
#define MultiSpeaker_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Outputs/Speaker/Speaker.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
transparently to connect a single caller to multiple destinations.
Makes a static internal copy of the list of machines; expects the owner to keep it
abreast of the current frontmost machine.
*/
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
public:
/*!
Provides a construction mechanism that may return nullptr, in the case that all included
machines return nullptr as their speaker.
*/
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
/// This class requires the caller to nominate changes in the frontmost machine.
void set_new_front_machine(::Machine::DynamicMachine *machine);
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum);
void set_output_rate(float cycles_per_second, int buffer_size);
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate);
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer);
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
};
}
}
#endif /* MultiSpeaker_hpp */

View File

@ -0,0 +1,104 @@
//
// MultiMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiMachine.hpp"
#include <algorithm>
using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
machines_(std::move(machines)),
configurable_(machines_),
configuration_target_(machines_),
crt_machine_(machines_, machines_mutex_),
joystick_machine_(machines),
keyboard_machine_(machines_) {
crt_machine_.set_delegate(this);
}
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
if(has_picked_) {
return machines_.front()->configuration_target();
} else {
return &configuration_target_;
}
}
CRTMachine::Machine *MultiMachine::crt_machine() {
if(has_picked_) {
return machines_.front()->crt_machine();
} else {
return &crt_machine_;
}
}
JoystickMachine::Machine *MultiMachine::joystick_machine() {
if(has_picked_) {
return machines_.front()->joystick_machine();
} else {
return &joystick_machine_;
}
}
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
if(has_picked_) {
return machines_.front()->keyboard_machine();
} else {
return &keyboard_machine_;
}
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
} else {
return &configurable_;
}
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence());
crt->print_type();
printf("; ");
}
printf("\n");
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
});
if(machines_.front().get() != front) {
crt_machine_.did_change_machine_order();
}
if(
(machines_.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence())
) {
pick_first();
}
}
void MultiMachine::pick_first() {
has_picked_ = true;
// machines_.erase(machines_.begin() + 1, machines_.end());
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
// request a close_output while the context is active.
}
void *MultiMachine::raw_pointer() {
return nullptr;
}

View File

@ -0,0 +1,71 @@
//
// MultiMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiMachine_hpp
#define MultiMachine_hpp
#include "../../../Machines/DynamicMachine.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiConfigurationTarget.hpp"
#include "Implementation/MultiCRTMachine.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides the same interface as to a single machine, while multiplexing all
underlying calls to an array of real dynamic machines.
Calls to crt_machine->get_crt will return that for the frontmost machine;
anything installed as the speaker's delegate will similarly receive
feedback only from that machine.
Following each crt_machine->run_for, reorders the supplied machines by
confidence.
If confidence for any machine becomes disproportionately low compared to
the others in the set, that machine stops running.
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
public:
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
ConfigurationTarget::Machine *configuration_target() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
Configurable::Device *configurable_device() override;
void *raw_pointer() override;
private:
void multi_crt_did_run_machines() override;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::mutex machines_mutex_;
MultiConfigurable configurable_;
MultiConfigurationTarget configuration_target_;
MultiCRTMachine crt_machine_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
void pick_first();
bool has_picked_ = false;
};
}
}
#endif /* MultiMachine_hpp */

26
Analyser/Machines.hpp Normal file
View File

@ -0,0 +1,26 @@
//
// Machines.h
// Clock Signal
//
// Created by Thomas Harte on 24/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Machines_h
#define Machines_h
namespace Analyser {
enum class Machine {
AmstradCPC,
Atari2600,
Electron,
MSX,
Oric,
Vic20,
ZX8081
};
}
#endif /* Machines_h */

View File

@ -7,14 +7,16 @@
//
#include "Disk.hpp"
#include "../../Storage/Disk/Controller/DiskController.hpp"
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../NumberTheory/CRC.hpp"
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include <algorithm>
using namespace StaticAnalyser::Acorn;
using namespace Analyser::Static::Acorn;
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
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
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(false, disk);
@ -41,9 +43,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// DFS files are stored contiguously, and listed in descending order of distance from track 0.
// So iterating backwards implies the least amount of seeking.
for(std::size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) {
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
@ -69,12 +69,12 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_front(new_file);
if(!data_length) catalogue->files.push_back(new_file);
}
return catalogue;
}
std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::unique_ptr<Catalogue> catalogue(new Catalogue);
Storage::Encodings::MFM::Parser parser(true, disk);

View File

@ -10,15 +10,16 @@
#define StaticAnalyser_Acorn_Disk_hpp
#include "File.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace StaticAnalyser {
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 {
std::string name;
std::list<File> files;
std::vector<File> files;
enum class BootOption {
None,
LoadBOOT,
@ -30,6 +31,7 @@ struct Catalogue {
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}

View File

@ -9,12 +9,12 @@
#ifndef StaticAnalyser_Acorn_File_hpp
#define StaticAnalyser_Acorn_File_hpp
#include <list>
#include <memory>
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
struct File {
@ -38,9 +38,10 @@ struct File {
std::vector<uint8_t> data;
};
std::list<Chunk> chunks;
std::vector<Chunk> chunks;
};
}
}
}

View File

@ -11,11 +11,11 @@
#include "Disk.hpp"
#include "Tape.hpp"
using namespace StaticAnalyser::Acorn;
using namespace Analyser::Static::Acorn;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
@ -56,21 +56,21 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) {
Target target;
target.machine = Target::Electron;
target.probability = 1.0; // TODO: a proper estimation
target.acorn.has_dfs = false;
target.acorn.has_adfs = false;
target.acorn.should_shift_restart = false;
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Electron;
target->confidence = 1.0; // TODO: a proper estimation
target->acorn.has_dfs = false;
target->acorn.has_adfs = false;
target->acorn.should_shift_restart = false;
// strip out inappropriate cartridges
target.media.cartridges = AcornCartridgesFrom(media.cartridges);
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// if there are any tapes, attempt to get data from the first
if(media.tapes.size() > 0) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
std::list<File> files = GetFiles(tape);
std::vector<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
@ -96,9 +96,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target.loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target.media.tapes = media.tapes;
target->media.tapes = media.tapes;
}
}
@ -108,18 +108,18 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
target.media.disks = media.disks;
target.acorn.has_dfs = !!dfs_catalogue;
target.acorn.has_adfs = !!adfs_catalogue;
target->media.disks = media.disks;
target->acorn.has_dfs = !!dfs_catalogue;
target->acorn.has_adfs = !!adfs_catalogue;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
target.acorn.should_shift_restart = true;
target->acorn.should_shift_restart = true;
else
target.loading_command = "*CAT\n";
target->loading_command = "*CAT\n";
}
}
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
destination.push_back(target);
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
destination.push_back(std::move(target));
}

View File

@ -11,11 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@ -9,10 +9,11 @@
#include "Tape.hpp"
#include <deque>
#include "../../NumberTheory/CRC.hpp"
#include "../../Storage/Tape/Parsers/Acorn.hpp"
using namespace StaticAnalyser::Acorn;
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
using namespace Analyser::Static::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
return file;
}
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Acorn::Parser parser;
// populate chunk list
@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T
}
// decompose into file list
std::list<File> file_list;
std::vector<File> file_list;
while(chunk_list.size()) {
std::unique_ptr<File> next_file = GetNextFile(chunk_list);

View File

@ -12,13 +12,15 @@
#include <memory>
#include "File.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Tape/Tape.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@ -11,8 +11,8 @@
#include <algorithm>
#include <cstring>
#include "../../Storage/Disk/Parsers/CPM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
static bool strcmp_insensitive(const char *a, const char *b) {
if(std::strlen(a) != std::strlen(b)) return false;
@ -58,7 +58,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
static void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue,
StaticAnalyser::Target &target) {
const std::unique_ptr<Analyser::Static::Target> &target) {
std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size());
@ -95,7 +95,7 @@ static void InspectCatalogue(
// If there's just one file, run that.
if(candidate_files.size() == 1) {
target.loading_command = RunCommandFor(*candidate_files[0]);
target->loading_command = RunCommandFor(*candidate_files[0]);
return;
}
@ -126,7 +126,7 @@ static void InspectCatalogue(
}
if(basic_files == 1 || implicit_suffixed_files == 1) {
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
target.loading_command = RunCommandFor(*candidate_files[selected_file]);
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
return;
}
@ -143,17 +143,17 @@ static void InspectCatalogue(
if(name_counts.size() == 2) {
for(auto &pair : name_counts) {
if(pair.second == 1) {
target.loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
return;
}
}
}
// Desperation.
target.loading_command = "cat\n";
target->loading_command = "cat\n";
}
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) {
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) {
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()) {
@ -169,7 +169,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
// This is a system disk, then launch it as though it were CP/M.
if(!matched) {
target.loading_command = "|cpm\n";
target->loading_command = "|cpm\n";
return true;
}
}
@ -177,24 +177,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St
return false;
}
void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target> &destination) {
Target target;
target.machine = Target::AmstradCPC;
target.probability = 1.0;
target.media.disks = media.disks;
target.media.tapes = media.tapes;
target.media.cartridges = media.cartridges;
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::AmstradCPC;
target->confidence = 1.0;
target->media.disks = media.disks;
target->media.tapes = media.tapes;
target->media.cartridges = media.cartridges;
target.amstradcpc.model = AmstradCPCModel::CPC6128;
target->amstradcpc.model = AmstradCPCModel::CPC6128;
if(!target.media.tapes.empty()) {
if(!target->media.tapes.empty()) {
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target.loading_command = "|tape\nrun\"\n1234567890";
target->loading_command = "|tape\nrun\"\n1234567890";
}
if(!target.media.disks.empty()) {
if(!target->media.disks.empty()) {
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
@ -203,11 +203,11 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), data_format);
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format);
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
} else {
if(!CheckBootSector(target.media.disks.front(), target)) {
if(!CheckBootSector(target->media.disks.front(), target)) {
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
@ -216,7 +216,7 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), system_format);
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
}
@ -224,5 +224,5 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target
}
}
destination.push_back(target);
destination.push_back(std::move(target));
}

View File

@ -11,11 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace AmstradCPC {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@ -10,9 +10,9 @@
#include "../Disassembler/6502.hpp"
using namespace StaticAnalyser::Atari;
using namespace Analyser::Static::Atari;
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
@ -26,17 +26,17 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
address &= 0x1fff;
return static_cast<std::size_t>(address - 0x1800);
};
StaticAnalyser::MOS6502::Disassembly high_location_disassembly =
StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory
bool has_wide_area_store = false;
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) {
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
if(has_wide_area_store) break;
}
@ -46,10 +46,10 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@ -58,12 +58,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack;
return;
}
// make an assumption that this is the Atari paging model
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@ -83,13 +83,13 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const
tigervision_access_count += masked_address == 0x3f;
}
if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@ -104,17 +104,17 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
}
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork;
if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
target.atari.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy;
Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
@ -131,23 +131,23 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
};
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
switch(segment.data.size()) {
case 8192:
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Pitfall2;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2;
break;
case 12288:
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k;
target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
@ -159,8 +159,8 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus &&
target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) {
if( target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus &&
target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
@ -172,20 +172,20 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St
}
// check for a Tigervision or Tigervision-esque scheme
if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) {
if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision;
}
}
void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &destination) {
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600.
Target target;
target.machine = Target::Atari2600;
target.probability = 1.0;
target.media.cartridges = media.cartridges;
target.atari.paging_model = Atari2600PagingModel::None;
target.atari.uses_superchip = false;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Atari2600;
target->confidence = 1.0;
target->media.cartridges = media.cartridges;
target->atari.paging_model = Atari2600PagingModel::None;
target->atari.uses_superchip = false;
// try to figure out the paging scheme
if(!media.cartridges.empty()) {
@ -193,9 +193,9 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &de
if(segments.size() == 1) {
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
DeterminePagingForCartridge(target, segment);
DeterminePagingForCartridge(*target, segment);
}
}
destination.push_back(target);
destination.push_back(std::move(target));
}

View File

@ -11,11 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Atari {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@ -7,15 +7,15 @@
//
#include "Disk.hpp"
#include "../../Storage/Disk/Controller/DiskController.hpp"
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "../../Storage/Data/Commodore.hpp"
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "../../../Storage/Data/Commodore.hpp"
#include <limits>
#include <vector>
#include <array>
using namespace StaticAnalyser::Commodore;
using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
@ -165,8 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
};
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::list<File> files;
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::vector<File> files;
CommodoreGCRParser parser;
parser.drive->set_disk(disk);

View File

@ -9,17 +9,19 @@
#ifndef StaticAnalyser_Commodore_Disk_hpp
#define StaticAnalyser_Commodore_Disk_hpp
#include "../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
#include <vector>
namespace Analyser {
namespace Static {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}
#endif /* Disk_hpp */

View File

@ -8,7 +8,7 @@
#include "File.hpp"
bool StaticAnalyser::Commodore::File::is_basic() {
bool Analyser::Static::Commodore::File::is_basic() {
// BASIC files are always relocatable (?)
if(type != File::RelocatableProgram) return false;

View File

@ -12,7 +12,8 @@
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
struct File {
@ -34,6 +35,7 @@ struct File {
bool is_basic();
};
}
}
}

View File

@ -8,18 +8,18 @@
#include "StaticAnalyser.hpp"
#include "Disk.hpp"
#include "File.hpp"
#include "Tape.hpp"
#include "Disk.hpp"
#include "../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include <sstream>
using namespace StaticAnalyser::Commodore;
using namespace Analyser::Static::Commodore;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
@ -38,42 +38,42 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
return vic20_cartridges;
}
void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> &destination) {
Target target;
target.machine = Target::Vic20; // TODO: machine estimation
target.probability = 1.0; // TODO: a proper estimation
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 1.0; // TODO: a proper estimation
int device = 0;
std::list<File> files;
std::vector<File> files;
bool is_disk = false;
// strip out inappropriate cartridges
target.media.cartridges = Vic20CartridgesFrom(media.cartridges);
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
// check disks
for(auto &disk : media.disks) {
std::list<File> disk_files = GetFiles(disk);
std::vector<File> disk_files = GetFiles(disk);
if(!disk_files.empty()) {
is_disk = true;
files.splice(files.end(), disk_files);
target.media.disks.push_back(disk);
files.insert(files.end(), disk_files.begin(), disk_files.end());
target->media.disks.push_back(disk);
if(!device) device = 8;
}
}
// check tapes
for(auto &tape : media.tapes) {
std::list<File> tape_files = GetFiles(tape);
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(!tape_files.empty()) {
files.splice(files.end(), tape_files);
target.media.tapes.push_back(tape);
files.insert(files.end(), tape_files.begin(), tape_files.end());
target->media.tapes.push_back(tape);
if(!device) device = 1;
}
}
if(!files.empty()) {
target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
target->vic20.memory_model = Vic20MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
@ -82,17 +82,17 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
string_stream << "1";
}
string_stream << "\nRUN\n";
target.loading_command = string_stream.str();
target->loading_command = string_stream.str();
// make a first guess based on loading address
switch(files.front().starting_address) {
case 0x1001:
default: break;
case 0x1201:
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target.vic20.memory_model = Vic20MemoryModel::EightKB;
target->vic20.memory_model = Vic20MemoryModel::EightKB;
break;
}
@ -108,9 +108,9 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
// An unexpanded machine has 3583 bytes free for BASIC;
// a 3kb expanded machine has 6655 bytes free.
if(file_size > 6655)
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
target.vic20.memory_model = Vic20MemoryModel::EightKB;
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
target->vic20.memory_model = Vic20MemoryModel::EightKB;
}
else
{*/
@ -129,13 +129,13 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target>
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
if(starting_address + file_size > 0x2000)
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
// }
}
}
if(!target.media.tapes.empty() || !target.media.cartridges.empty() || !target.media.disks.empty())
destination.push_back(target);
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@ -11,11 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@ -8,13 +8,13 @@
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/Commodore.hpp"
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
using namespace StaticAnalyser::Commodore;
using namespace Analyser::Static::Commodore;
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Commodore::Parser parser;
std::list<File> file_list;
std::vector<File> file_list;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);

View File

@ -9,15 +9,16 @@
#ifndef StaticAnalyser_Commodore_Tape_hpp
#define StaticAnalyser_Commodore_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@ -10,10 +10,10 @@
#include "Kernel.hpp"
using namespace StaticAnalyser::MOS6502;
using namespace Analyser::Static::MOS6502;
namespace {
using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
struct MOS6502Disassembler {
@ -312,9 +312,9 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
} // end of anonymous namespace
Disassembly StaticAnalyser::MOS6502::Disassemble(
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 StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
}

View File

@ -16,7 +16,8 @@
#include <set>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace MOS6502 {
/*!
@ -95,5 +96,6 @@ Disassembly Disassemble(
}
}
}
#endif /* Disassembler6502_hpp */

View File

@ -11,7 +11,8 @@
#include <functional>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Disassembler {
/*!
@ -24,6 +25,7 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
};
}
}
}
}

View File

@ -9,7 +9,8 @@
#ifndef Kernel_hpp
#define Kernel_hpp
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Disassembly {
template <typename D, typename S> struct PartialDisassembly {
@ -44,6 +45,7 @@ template <typename D, typename S, typename Disassembler> D Disassemble(
return partial_disassembly.disassembly;
}
}
}
}

View File

@ -10,10 +10,10 @@
#include "Kernel.hpp"
using namespace StaticAnalyser::Z80;
using namespace Analyser::Static::Z80;
namespace {
using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
class Accessor {
public:
@ -611,9 +611,9 @@ struct Z80Disassembler {
} // end of anonymous namespace
Disassembly StaticAnalyser::Z80::Disassemble(
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) {
return StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
}

View File

@ -15,7 +15,8 @@
#include <set>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Z80 {
struct Instruction {
@ -84,5 +85,6 @@ Disassembly Disassemble(
}
}
}
#endif /* StaticAnalyser_Disassembler_Z80_hpp */

View File

@ -0,0 +1,40 @@
//
// Cartridge.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Cartridge_hpp
#define Cartridge_hpp
#include "../../../Storage/Cartridge/Cartridge.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
/*!
Extends the base cartridge class by adding a (guess at) the banking scheme.
*/
struct Cartridge: public ::Storage::Cartridge::Cartridge {
enum Type {
None,
Konami,
KonamiWithSCC,
ASCII8kb,
ASCII16kb,
FMPac
};
const Type type;
Cartridge(const std::vector<Segment> &segments, Type type) :
Storage::Cartridge::Cartridge(segments), type(type) {}
};
}
}
}
#endif /* Cartridge_hpp */

View File

@ -0,0 +1,292 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Cartridge.hpp"
#include "Tape.hpp"
#include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include <algorithm>
static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
const Storage::Cartridge::Cartridge::Segment &segment,
uint16_t start_address,
Analyser::Static::MSX::Cartridge::Type type,
float confidence) {
// Size down to a multiple of 8kb in size and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(start_address, truncated_data);
} else {
output_segments.emplace_back(start_address, segment.data);
}
std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target);
target->machine = Analyser::Machine::MSX;
target->confidence = confidence;
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
} else {
target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type));
}
return target;
}
/*
Expected standard cartridge format:
DEFB "AB" ; expansion ROM header
DEFW initcode ; start of the init code, 0 if no initcode
DEFW callstat; pointer to CALL statement handler, 0 if no such handler
DEFW device; pointer to expansion device handler, 0 if no such handler
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram
DEFS 6,0 ; room reserved for future extensions
MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file
format that the MSX community has decided upon doesn't retain the type of hardware included, so
this analyser has to guess.
(additional audio hardware is also sometimes included, but it's implied by the banking hardware)
*/
static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom(
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
// No cartridges implies no targets.
if(cartridges.empty()) {
return {};
}
std::vector<std::unique_ptr<Analyser::Static::Target>> targets;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// Only one mapped item is allowed.
if(segments.size() != 1) continue;
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
Storage::Cartridge::Cartridge::Segment segment = segments.front();
const size_t data_size = segment.data.size();
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
// Check for a ROM header at address 0; if it's not found then try 0x4000
// and adjust the start address;
uint16_t start_address = 0;
bool found_start = false;
if(segment.data[0] == 0x41 && segment.data[1] == 0x42) {
start_address = 0x4000;
found_start = true;
} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) {
start_address = 0;
found_start = true;
}
// Reject cartridge if the ROM header wasn't found.
if(!found_start) continue;
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
// 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.
if(data_size <= 0xc000) {
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
continue;
}
// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must
// 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);
Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ 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;
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 = static_cast<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;
for(const auto &instruction_pair : instructions) {
if( instruction_pair.second.operation == Instruction::Operation::LD &&
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
instruction_pair.second.source == Instruction::Location::A) {
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
}
}
// Weight confidences by number of observed hits.
float total_hits =
static_cast<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]
);
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
static_cast<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,
static_cast<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,
static_cast<float>( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
static_cast<float>( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}
return targets;
}
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
// Append targets for any cartridges that look correct.
std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges);
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
// Consider building a target for disks and/or tapes.
std::unique_ptr<Target> target(new Target);
// Check tapes for loadable files.
for(const auto &tape : media.tapes) {
std::vector<File> files_on_tape = GetFiles(tape);
if(!files_on_tape.empty()) {
switch(files_on_tape.front().type) {
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
case File::Type::TokenisedBASIC: target->loading_command = "CLOAD\rRUN\r"; break;
case File::Type::Binary: target->loading_command = "BLOAD\"CAS:\",R\r"; break;
default: break;
}
target->media.tapes.push_back(tape);
}
}
// Blindly accept disks for now.
target->media.disks = media.disks;
if(!target->media.empty()) {
target->machine = Machine::MSX;
target->confidence = 1.0;
destination.push_back(std::move(target));
}
}

View File

@ -11,11 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace MSX {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@ -8,9 +8,9 @@
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/MSX.hpp"
#include "../../../Storage/Tape/Parsers/MSX.hpp"
using namespace StaticAnalyser::MSX;
using namespace Analyser::Static::MSX;
File::File(File &&rhs) :
name(std::move(rhs.name)),
@ -24,7 +24,7 @@ File::File() :
starting_address(0),
entry_address(0) {} // For the sake of initialising in a defined state.
std::vector<File> StaticAnalyser::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> files;
Storage::Tape::BinaryTapePlayer tape_player(1000000);

View File

@ -9,12 +9,13 @@
#ifndef StaticAnalyser_MSX_Tape_hpp
#define StaticAnalyser_MSX_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace MSX {
struct File {
@ -36,6 +37,7 @@ struct File {
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@ -12,9 +12,9 @@
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
using namespace StaticAnalyser::Oric;
using namespace Analyser::Static::Oric;
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@ -24,7 +24,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const
return score;
}
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@ -48,7 +48,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
return Score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) {
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@ -73,23 +73,23 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
return Score(disassembly, rom_functions, variable_locations);
}
void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) {
Target target;
target.machine = Target::Oric;
target.probability = 1.0;
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Oric;
target->confidence = 1.0;
int basic10_votes = 0;
int basic11_votes = 0;
for(auto &tape : media.tapes) {
std::list<File> tape_files = GetFiles(tape);
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(tape_files.size()) {
for(auto file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::Disassembler::OffsetMapper(file.starting_address), entry_points);
Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);
@ -97,23 +97,23 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des
}
}
target.media.tapes.push_back(tape);
target.loading_command = "CLOAD\"\"\n";
target->media.tapes.push_back(tape);
target->loading_command = "CLOAD\"\"\n";
}
}
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
if(!media.disks.empty()) {
target.oric.has_microdisc = true;
target.media.disks = media.disks;
target->oric.has_microdisc = true;
target->media.disks = media.disks;
} else {
target.oric.has_microdisc = false;
target->oric.has_microdisc = false;
}
// TODO: really this should add two targets if not all votes agree
target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
target->oric.use_atmos_rom = basic11_votes >= basic10_votes;
if(target->oric.has_microdisc) target->oric.use_atmos_rom = true;
if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size())
destination.push_back(target);
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
destination.push_back(std::move(target));
}

View File

@ -11,13 +11,14 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Oric {
void AddTargets(const Media &media, std::list<Target> &destination);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@ -7,12 +7,12 @@
//
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "../../../Storage/Tape/Parsers/Oric.hpp"
using namespace StaticAnalyser::Oric;
using namespace Analyser::Static::Oric;
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::list<File> files;
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> files;
Storage::Tape::Oric::Parser parser;
while(!tape->is_at_end()) {

View File

@ -9,12 +9,13 @@
#ifndef StaticAnalyser_Oric_Tape_hpp
#define StaticAnalyser_Oric_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include <list>
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Oric {
struct File {
@ -30,8 +31,9 @@ struct File {
std::vector<uint8_t> data;
};
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@ -8,6 +8,7 @@
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
@ -21,34 +22,34 @@
#include "ZX8081/StaticAnalyser.hpp"
// Cartridges
#include "../Storage/Cartridge/Formats/BinaryDump.hpp"
#include "../Storage/Cartridge/Formats/PRG.hpp"
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
#include "../../Storage/Cartridge/Formats/PRG.hpp"
// Disks
#include "../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
// Tapes
#include "../Storage/Tape/Formats/CAS.hpp"
#include "../Storage/Tape/Formats/CommodoreTAP.hpp"
#include "../Storage/Tape/Formats/CSW.hpp"
#include "../Storage/Tape/Formats/OricTAP.hpp"
#include "../Storage/Tape/Formats/TapePRG.hpp"
#include "../Storage/Tape/Formats/TapeUEF.hpp"
#include "../Storage/Tape/Formats/TZX.hpp"
#include "../Storage/Tape/Formats/ZX80O81P.hpp"
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
#include "../../Storage/Tape/Formats/CSW.hpp"
#include "../../Storage/Tape/Formats/OricTAP.hpp"
#include "../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
#include "../../Storage/Tape/Formats/TZX.hpp"
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
// Target Platform Types
#include "../Storage/TargetPlatforms.hpp"
#include "../../Storage/TargetPlatforms.hpp"
using namespace StaticAnalyser;
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
@ -88,8 +89,8 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
@ -134,13 +135,13 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
return result;
}
Media StaticAnalyser::GetMedia(const char *file_name) {
Media Analyser::Static::GetMedia(const char *file_name) {
TargetPlatform::IntType throwaway;
return GetMediaAndPlatforms(file_name, throwaway);
}
std::list<Target> StaticAnalyser::GetTargets(const char *file_name) {
std::list<Target> targets;
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) {
std::vector<std::unique_ptr<Target>> targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
@ -158,11 +159,18 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name) {
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);
// Reset any tapes to their initial position
for(auto target : targets) {
for(auto tape : media.tapes) {
for(auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();
}
}
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
// picked their insertion order carefully.
std::stable_sort(targets.begin(), targets.end(),
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
return a->confidence > b->confidence;
});
return targets;
}

View File

@ -9,15 +9,17 @@
#ifndef StaticAnalyser_hpp
#define StaticAnalyser_hpp
#include "../Storage/Tape/Tape.hpp"
#include "../Storage/Disk/Disk.hpp"
#include "../Storage/Cartridge/Cartridge.hpp"
#include "../Machines.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include <string>
#include <list>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
enum class Vic20MemoryModel {
Unexpanded,
@ -40,15 +42,6 @@ enum class Atari2600PagingModel {
Pitfall2
};
enum class MSXCartridgeType {
None,
Konami,
KonamiWithSCC,
ASCII8kb,
ASCII16kb,
FMPac
};
enum class ZX8081MemoryModel {
Unexpanded,
SixteenKB,
@ -65,9 +58,9 @@ enum class AmstradCPCModel {
A list of disks, tapes and cartridges.
*/
struct Media {
std::list<std::shared_ptr<Storage::Disk::Disk>> disks;
std::list<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
@ -79,16 +72,11 @@ struct Media {
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
enum Machine {
AmstradCPC,
Atari2600,
Electron,
MSX,
Oric,
Vic20,
ZX8081
} machine;
float probability;
Machine machine;
Media media;
float confidence;
std::string loading_command;
// TODO: this is too C-like a solution; make Target a base class and
// turn the following into information held by more specific subclasses.
@ -122,14 +110,7 @@ struct Target {
struct {
AmstradCPCModel model;
} amstradcpc;
struct {
MSXCartridgeType cartridge_type;
} msx;
};
std::string loading_command;
Media media;
};
/*!
@ -137,13 +118,14 @@ struct Target {
@returns The list of potential targets, sorted from most to least probable.
*/
std::list<Target> GetTargets(const char *file_name);
std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name);
/*!
Inspects the supplied file and determines the media included.
*/
Media GetMedia(const char *file_name);
}
}
#endif /* StaticAnalyser_hpp */

View File

@ -11,7 +11,7 @@
#include <string>
#include <vector>
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<Storage::Data::ZX8081::File> files;
@ -27,41 +27,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
return files;
}
void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) {
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) {
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
if(!files.empty()) {
StaticAnalyser::Target target;
target.machine = Target::ZX8081;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::ZX8081;
// Guess the machine type from the file only if it isn't already known.
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
default:
target.zx8081.isZX81 = files.front().isZX81;
target->zx8081.isZX81 = files.front().isZX81;
break;
case TargetPlatform::ZX80: target.zx8081.isZX81 = false; break;
case TargetPlatform::ZX81: target.zx8081.isZX81 = true; break;
case TargetPlatform::ZX80: target->zx8081.isZX81 = false; break;
case TargetPlatform::ZX81: target->zx8081.isZX81 = true; break;
}
/*if(files.front().data.size() > 16384) {
target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
} else*/ if(files.front().data.size() > 1024) {
target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
} else {
target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
}
target.media.tapes = media.tapes;
target->media.tapes = media.tapes;
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
if(target.zx8081.isZX81) {
target.loading_command = "J\"\"\n";
if(target->zx8081.isZX81) {
target->loading_command = "J\"\"\n";
} else {
target.loading_command = "W\n";
target->loading_command = "W\n";
}
destination.push_back(target);
destination.push_back(std::move(target));
}
}
}

View File

@ -10,13 +10,15 @@
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../Storage/TargetPlatforms.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace ZX8081 {
void AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms);
}
}
}

View File

@ -26,6 +26,10 @@ struct Option {
virtual ~Option() {}
Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {}
virtual bool operator==(const Option &rhs) {
return long_name == rhs.long_name && short_name == rhs.short_name;
}
};
struct BooleanOption: public Option {
@ -35,6 +39,12 @@ struct BooleanOption: public Option {
struct ListOption: public Option {
std::vector<std::string> options;
ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {}
virtual bool operator==(const Option &rhs) {
const ListOption *list_rhs = dynamic_cast<const ListOption *>(&rhs);
if(!list_rhs) return false;
return long_name == rhs.long_name && short_name == rhs.short_name && options == list_rhs->options;
}
};
struct BooleanSelection;

View File

@ -867,19 +867,19 @@ class ConcreteMachine:
}
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const StaticAnalyser::Target &target) override final {
void configure_as_target(const Analyser::Static::Target &target) override final {
switch(target.amstradcpc.model) {
case StaticAnalyser::AmstradCPCModel::CPC464:
case Analyser::Static::AmstradCPCModel::CPC464:
rom_model_ = ROMType::OS464;
has_128k_ = false;
has_fdc_ = false;
break;
case StaticAnalyser::AmstradCPCModel::CPC664:
case Analyser::Static::AmstradCPCModel::CPC664:
rom_model_ = ROMType::OS664;
has_128k_ = false;
has_fdc_ = true;
break;
case StaticAnalyser::AmstradCPCModel::CPC6128:
case Analyser::Static::AmstradCPCModel::CPC6128:
rom_model_ = ROMType::OS6128;
has_128k_ = true;
has_fdc_ = true;
@ -908,7 +908,7 @@ class ConcreteMachine:
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) override final {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
@ -976,8 +976,8 @@ class ConcreteMachine:
key_state_.clear_all_keys();
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
private:

View File

@ -72,34 +72,34 @@ class ConcreteMachine:
close_output();
}
void configure_as_target(const StaticAnalyser::Target &target) override {
void configure_as_target(const Analyser::Static::Target &target) override {
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
switch(target.atari.paging_model) {
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case Analyser::Static::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case Analyser::Static::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case Analyser::Static::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case Analyser::Static::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case Analyser::Static::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case Analyser::Static::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Atari8k:
case Analyser::Static::Atari2600PagingModel::Atari8k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari16k:
case Analyser::Static::Atari2600PagingModel::Atari16k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari32k:
case Analyser::Static::Atari2600PagingModel::Atari32k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
} else {
@ -112,7 +112,7 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
}
bool insert_media(const StaticAnalyser::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) override {
return false;
}
@ -130,6 +130,18 @@ class ConcreteMachine:
}
}
bool get_switch_is_enabled(Atari2600Switch input) override {
uint8_t port_input = bus_->mos6532_.get_port_input(1);
switch(input) {
case Atari2600SwitchReset: return !!(port_input & 0x01);
case Atari2600SwitchSelect: return !!(port_input & 0x02);
case Atari2600SwitchColour: return !!(port_input & 0x08);
case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40);
case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80);
default: return false;
}
}
void set_reset_switch(bool state) override {
bus_->set_reset_line(state);
}

View File

@ -33,6 +33,9 @@ class Machine:
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
/// Gets the state of switch @c input.
virtual bool get_switch_is_enabled(Atari2600Switch input) = 0;
// Presses or releases the reset button.
virtual void set_reset_switch(bool state) = 0;
};

View File

@ -44,11 +44,15 @@ class Machine: public ROMMachine::Machine {
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual void print_type() {}
// TODO: sever the clock-rate stuff.
double get_clock_rate() {
virtual double get_clock_rate() {
return clock_rate_;
}
bool get_clock_is_unlimited() {
virtual bool get_clock_is_unlimited() {
return clock_is_unlimited_;
}
class Delegate {
@ -56,7 +60,7 @@ class Machine: public ROMMachine::Machine {
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
void set_delegate(Delegate *delegate) { delegate_ = delegate; }
virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
void set_clock_rate(double clock_rate) {

View File

@ -346,19 +346,19 @@ class ConcreteMachine:
return true;
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.loading_command.length()) {
type_string(target.loading_command);
}
switch(target.vic20.memory_model) {
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
case Analyser::Static::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case StaticAnalyser::Vic20MemoryModel::EightKB:
case Analyser::Static::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
case Analyser::Static::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
@ -377,7 +377,7 @@ class ConcreteMachine:
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front());
}
@ -662,8 +662,8 @@ class ConcreteMachine:
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
// MARK: - Configuration options.

View File

@ -9,7 +9,7 @@
#ifndef ConfigurationTarget_hpp
#define ConfigurationTarget_hpp
#include "../StaticAnalyser/StaticAnalyser.hpp"
#include "../Analyser/Static/StaticAnalyser.hpp"
#include "../Configurable/Configurable.hpp"
#include <string>
@ -17,20 +17,20 @@
namespace ConfigurationTarget {
/*!
A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target
A ConfigurationTarget::Machine is anything that can accept a Analyser::Static::Target
and configure itself appropriately, or accept a list of media subsequently to insert.
*/
class Machine {
public:
/// Instructs the machine to configure itself as described by @c target and insert the included media.
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
virtual void configure_as_target(const Analyser::Static::Target &target) = 0;
/*!
Requests that the machine insert @c media as a modification to current state
@returns @c true if any media was inserted; @c false otherwise.
*/
virtual bool insert_media(const StaticAnalyser::Media &media) = 0;
virtual bool insert_media(const Analyser::Static::Media &media) = 0;
};
}

View File

@ -0,0 +1,48 @@
//
// DynamicMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DynamicMachine_h
#define DynamicMachine_h
#include "../Configurable/Configurable.hpp"
#include "ConfigurationTarget.hpp"
#include "CRTMachine.hpp"
#include "JoystickMachine.hpp"
#include "KeyboardMachine.hpp"
#include "Utility/Typer.hpp"
namespace Machine {
/*!
Provides the structure for owning a machine and dynamically casting it as desired without knowledge of
the machine's parent class or, therefore, the need to establish a common one.
*/
struct DynamicMachine {
virtual ~DynamicMachine() {}
virtual ConfigurationTarget::Machine *configuration_target() = 0;
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual Configurable::Device *configurable_device() = 0;
/*!
Provides a raw pointer to the underlying machine if and only if this dynamic machine really is
only a single machine.
Very unsafe. Very temporary.
TODO: eliminate in favour of introspection for machine-specific inputs. This is here temporarily
only to permit continuity of certain features in the Mac port that have not yet made their way
to the SDL/console port.
*/
virtual void *raw_pointer() = 0;
};
}
#endif /* DynamicMachine_h */

View File

@ -119,7 +119,7 @@ class ConcreteMachine:
use_fast_tape_hack_ = activate;
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.loading_command.length()) {
type_string(target.loading_command);
}
@ -143,7 +143,7 @@ class ConcreteMachine:
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_.set_tape(media.tapes.front());
}
@ -425,8 +425,8 @@ class ConcreteMachine:
Utility::TypeRecipient::add_typer(string, std::move(mapper));
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
// MARK: - Configuration options.

View File

@ -15,7 +15,7 @@ Machine::Machine() {
}
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key);
uint16_t mapped_key = get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed);
}
@ -30,3 +30,7 @@ Inputs::Keyboard &Machine::get_keyboard() {
void Machine::type_string(const std::string &) {
}
Machine::KeyboardMapper *Machine::get_keyboard_mapper() {
return nullptr;
}

View File

@ -61,12 +61,11 @@ class Machine: public Inputs::Keyboard::Delegate {
*/
static const uint16_t KeyNotMapped = 0xfffe;
protected:
/*!
Allows individual machines to provide the mapping between host keys
as per Inputs::Keyboard and their native scheme.
*/
virtual KeyboardMapper &get_keyboard_mapper() = 0;
virtual KeyboardMapper *get_keyboard_mapper();
private:
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;

View File

@ -19,18 +19,31 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler {
ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
// printf("A16 %04x ", address);
switch(address >> 11) {
default: break;
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
map_.map(slot_, value * 8192, 0x4000, 0x4000);
if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x4000, 0x4000, 0x4000);
break;
case 0xe:
map_.map(slot_, value * 8192, 0x8000, 0x4000);
if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x4000, 0x8000, 0x4000);
break;
}
}
virtual void print_type() override {
printf("A16");
}
private:
MSX::MemoryMap &map_;
int slot_;

View File

@ -19,24 +19,43 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler {
ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
// printf("A8 %04x ", address);
switch(address >> 11) {
default: break;
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0xc:
map_.map(slot_, value * 8192, 0x4000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x4000, 0x2000);
break;
case 0xd:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x6000, 0x2000);
break;
case 0xe:
map_.map(slot_, value * 8192, 0x8000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x8000, 0x2000);
break;
case 0xf:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0xa000, 0x2000);
break;
}
}
virtual void print_type() override {
printf("A8");
}
private:
MSX::MemoryMap &map_;
int slot_;

View File

@ -19,21 +19,36 @@ class KonamiROMSlotHandler: public ROMSlotHandler {
KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
// printf("K %04x[%c]\n", address, pc_is_outside_bios ? '.' : '+');
switch(address >> 13) {
default: break;
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 3:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x6000, 0x2000);
break;
case 4:
map_.map(slot_, value * 8192, 0x8000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x8000, 0x2000);
break;
case 5:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0xa000, 0x2000);
break;
}
}
virtual void print_type() override {
printf("K");
}
private:
MSX::MemoryMap &map_;
int slot_;

View File

@ -20,40 +20,66 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) :
map_(map), slot_(slot), scc_(scc) {}
void write(uint16_t address, uint8_t value) override {
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override {
// printf("KSCC %04x ", address);
switch(address >> 11) {
default: break;
default:
if(pc_is_outside_bios) confidence_counter_.add_miss();
break;
case 0x0a:
map_.map(slot_, value * 8192, 0x4000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x4000, 0x2000);
break;
case 0x0e:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0x6000, 0x2000);
break;
case 0x12:
if(pc_is_outside_bios) {
if(address == 0x9000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
map_.unmap(slot_, 0x8000, 0x2000);
} else {
scc_is_visible_ = false;
map_.map(slot_, value * 8192, 0x8000, 0x2000);
map_.map(slot_, value * 0x2000, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) scc_.write(address, value);
if(scc_is_visible_) {
if(pc_is_outside_bios) confidence_counter_.add_hit();
scc_.write(address, value);
} else {
if(pc_is_outside_bios) confidence_counter_.add_miss();
}
break;
case 0x16:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
if(pc_is_outside_bios) {
if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal();
}
map_.map(slot_, value * 0x2000, 0xa000, 0x2000);
break;
}
}
uint8_t read(uint16_t address) override {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
confidence_counter_.add_hit();
return scc_.read(address);
}
confidence_counter_.add_miss();
return 0xff;
}
virtual void print_type() override {
printf("KSCC");
}
private:
MSX::MemoryMap &map_;
int slot_;

View File

@ -16,7 +16,7 @@ DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
set_is_double_density(true);
}
void DiskROM::write(uint16_t address, uint8_t value) {
void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) {
switch(address) {
case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb:
set_register(address, value);

View File

@ -22,7 +22,7 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 {
public:
DiskROM(const std::vector<uint8_t> &rom);
void write(uint16_t address, uint8_t value) override;
void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override;
uint8_t read(uint16_t address) override;
void run_for(HalfCycles half_cycles) override;

View File

@ -12,6 +12,7 @@
#include "Keyboard.hpp"
#include "ROMSlotHandler.hpp"
#include "../../Analyser/Static/MSX/Cartridge.hpp"
#include "Cartridges/ASCII8kb.hpp"
#include "Cartridges/ASCII16kb.hpp"
#include "Cartridges/Konami.hpp"
@ -156,7 +157,21 @@ class ConcreteMachine:
z80_.run_for(cycles);
}
void configure_as_target(const StaticAnalyser::Target &target) override {
float get_confidence() override {
if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f;
if(memory_slots_[1].handler) {
return memory_slots_[1].handler->get_confidence();
}
return 0.5f;
}
void print_type() override {
if(memory_slots_[1].handler) {
memory_slots_[1].handler->print_type();
}
}
void configure_as_target(const Analyser::Static::Target &target) override {
// Add a disk cartridge if any disks were supplied.
if(!target.media.disks.empty()) {
map(2, 0, 0x4000, 0x2000);
@ -171,30 +186,32 @@ class ConcreteMachine:
if(target.loading_command.length()) {
type_string(target.loading_command);
}
// Attach the hardware necessary for a game cartridge, if any.
switch(target.msx.cartridge_type) {
default: break;
case StaticAnalyser::MSXCartridgeType::Konami:
memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1));
break;
case StaticAnalyser::MSXCartridgeType::KonamiWithSCC:
memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_));
break;
case StaticAnalyser::MSXCartridgeType::ASCII8kb:
memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1));
break;
case StaticAnalyser::MSXCartridgeType::ASCII16kb:
memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1));
break;
}
}
bool insert_media(const StaticAnalyser::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
memory_slots_[1].source = segment.data;
map(1, 0, static_cast<uint16_t>(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address));
auto msx_cartridge = dynamic_cast<Analyser::Static::MSX::Cartridge *>(media.cartridges.front().get());
if(msx_cartridge) {
switch(msx_cartridge->type) {
default: break;
case Analyser::Static::MSX::Cartridge::Konami:
memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1));
break;
case Analyser::Static::MSX::Cartridge::KonamiWithSCC:
memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_));
break;
case Analyser::Static::MSX::Cartridge::ASCII8kb:
memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1));
break;
case Analyser::Static::MSX::Cartridge::ASCII16kb:
memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1));
break;
}
}
}
if(!media.tapes.empty()) {
@ -324,6 +341,14 @@ class ConcreteMachine:
break;
}
}
if(!address) {
pc_zero_accesses_++;
}
if(read_pointers_[address >> 13] == unpopulated_) {
performed_unmapped_access_ = true;
}
pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers.
case CPU::Z80::PartialMachineCycle::Read:
if(read_pointers_[address >> 13]) {
*cycle.value = read_pointers_[address >> 13][address & 8191];
@ -341,7 +366,7 @@ class ConcreteMachine:
if(memory_slots_[slot_hit].handler) {
update_audio();
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
memory_slots_[slot_hit].handler->write(address, *cycle.value);
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
}
} break;
@ -505,8 +530,8 @@ class ConcreteMachine:
if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask;
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
@ -637,6 +662,10 @@ class ConcreteMachine:
std::string input_text_;
MSX::KeyboardMapper keyboard_mapper_;
int pc_zero_accesses_ = 0;
bool performed_unmapped_access_ = false;
uint16_t pc_address_;
};
}

View File

@ -10,6 +10,7 @@
#define ROMSlotHandler_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include <cstddef>
#include <cstdint>
@ -46,7 +47,7 @@ class ROMSlotHandler {
virtual void run_for(HalfCycles half_cycles) {}
/*! Announces an attempt to write @c value to @c address. */
virtual void write(uint16_t address, uint8_t value) = 0;
virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0;
/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */
virtual uint8_t read(uint16_t address) { return 0xff; }
@ -57,12 +58,22 @@ class ROMSlotHandler {
/// Empty causes all out-of-bounds accesses to read a vacant bus.
Empty
};
/*!
Returns the wrapping strategy to apply to mapping requests from this ROM slot.
*/
/*! @returns The wrapping strategy to apply to mapping requests from this ROM slot. */
virtual WrappingStrategy wrapping_strategy() const {
return WrappingStrategy::Repeat;
}
/*! @returns The probability that this handler is correct for the data it owns. */
float get_confidence() {
return confidence_counter_.get_confidence();
}
virtual void print_type() {
}
protected:
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
};
}

View File

@ -254,7 +254,7 @@ class ConcreteMachine:
}
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target) override final {
void configure_as_target(const Analyser::Static::Target &target) override final {
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
@ -284,7 +284,7 @@ class ConcreteMachine:
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) override final {
if(media.tapes.size()) {
tape_player_.set_tape(media.tapes.front());
}
@ -432,8 +432,8 @@ class ConcreteMachine:
set_interrupt_line();
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
// MARK: - Configuration options.

View File

@ -15,11 +15,13 @@
namespace ROMMachine {
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher;
struct Machine {
/*!
Provides the machine with a way to obtain such ROMs as it needs.
*/
virtual bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &rom_with_name) { return true; }
virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; }
};
}

View File

@ -16,45 +16,97 @@
#include "../Oric/Oric.hpp"
#include "../ZX8081/ZX8081.hpp"
#include "../../Analyser/Dynamic/MultiMachine/MultiMachine.hpp"
#include "TypedDynamicMachine.hpp"
::Machine::DynamicMachine *::Machine::MachineForTarget(const StaticAnalyser::Target &target) {
switch(target.machine) {
case StaticAnalyser::Target::AmstradCPC: return new TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());
case StaticAnalyser::Target::Atari2600: return new TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());
case StaticAnalyser::Target::Electron: return new TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());
case StaticAnalyser::Target::MSX: return new TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());
case StaticAnalyser::Target::Oric: return new TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric());
case StaticAnalyser::Target::Vic20: return new TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());
case StaticAnalyser::Target::ZX8081: return new TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));
namespace {
default: return nullptr;
::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) {
error = Machine::Error::None;
::Machine::DynamicMachine *machine = nullptr;
switch(target.machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric()); break;
case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); break;
case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); break;
default:
error = Machine::Error::UnknownMachine;
return nullptr;
}
// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine.
CRTMachine::Machine *crt_machine = machine->crt_machine();
if(crt_machine) {
if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) {
delete machine;
error = Machine::Error::MissingROM;
return nullptr;
}
}
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) {
machine->configuration_target()->configure_as_target(target);
}
return machine;
}
std::string Machine::ShortNameForTargetMachine(const StaticAnalyser::Target::Machine machine) {
}
::Machine::DynamicMachine *::Machine::MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) {
// Zero targets implies no machine.
if(targets.empty()) {
error = Error::NoTargets;
return nullptr;
}
// If there's more than one target, get all the machines and combine them into a multimachine.
if(targets.size() > 1) {
std::vector<std::unique_ptr<Machine::DynamicMachine>> machines;
for(const auto &target: targets) {
machines.emplace_back(MachineForTarget(*target, rom_fetcher, error));
// Exit early if any errors have occurred.
if(error != Error::None) {
return nullptr;
}
}
return new Analyser::Dynamic::MultiMachine(std::move(machines));
}
// There's definitely exactly one target.
return MachineForTarget(*targets.front(), rom_fetcher, error);
}
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {
switch(machine) {
case StaticAnalyser::Target::AmstradCPC: return "AmstradCPC";
case StaticAnalyser::Target::Atari2600: return "Atari2600";
case StaticAnalyser::Target::Electron: return "Electron";
case StaticAnalyser::Target::MSX: return "MSX";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic20";
case StaticAnalyser::Target::ZX8081: return "ZX8081";
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
case Analyser::Machine::Atari2600: return "Atari2600";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
case Analyser::Machine::Vic20: return "Vic20";
case Analyser::Machine::ZX8081: return "ZX8081";
default: return "";
}
}
std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine machine) {
std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
switch(machine) {
case StaticAnalyser::Target::AmstradCPC: return "Amstrad CPC";
case StaticAnalyser::Target::Atari2600: return "Atari 2600";
case StaticAnalyser::Target::Electron: return "Acorn Electron";
case StaticAnalyser::Target::MSX: return "MSX";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic 20";
case StaticAnalyser::Target::ZX8081: return "ZX80/81";
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
case Analyser::Machine::Atari2600: return "Atari 2600";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
case Analyser::Machine::Vic20: return "Vic 20";
case Analyser::Machine::ZX8081: return "ZX80/81";
default: return "";
}
@ -63,11 +115,11 @@ std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine ma
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() {
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::MSX), MSX::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Oric), Oric::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Vic20), Commodore::Vic20::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::ZX8081), ZX8081::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ZX8081), ZX8081::get_options()));
return options;
}

View File

@ -9,52 +9,42 @@
#ifndef MachineForTarget_hpp
#define MachineForTarget_hpp
#include "../../StaticAnalyser/StaticAnalyser.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "Typer.hpp"
#include "../DynamicMachine.hpp"
#include <map>
#include <string>
#include <vector>
namespace Machine {
/*!
Provides the structure for owning a machine and dynamically casting it as desired without knowledge of
the machine's parent class or, therefore, the need to establish a common one.
*/
struct DynamicMachine {
virtual ConfigurationTarget::Machine *configuration_target() = 0;
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual Configurable::Device *configurable_device() = 0;
virtual Utility::TypeRecipient *type_recipient() = 0;
enum class Error {
None,
UnknownMachine,
MissingROM,
NoTargets
};
/*!
Allocates an instance of DynamicMachine holding a machine that can
receive the supplied target. The machine has been allocated on the heap.
It is the caller's responsibility to delete the class when finished.
receive the supplied static analyser result. The machine has been allocated
on the heap. It is the caller's responsibility to delete the class when finished.
*/
DynamicMachine *MachineForTarget(const StaticAnalyser::Target &target);
DynamicMachine *MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error);
/*!
Returns a short string name for the machine identified by the target,
which is guaranteed not to have any spaces or other potentially
filesystem-bothering contents.
*/
std::string ShortNameForTargetMachine(const StaticAnalyser::Target::Machine target);
std::string ShortNameForTargetMachine(const Analyser::Machine target);
/*!
Returns a long string name for the machine identified by the target,
usable for presentation to a human.
*/
std::string LongNameForTargetMachine(const StaticAnalyser::Target::Machine target);
std::string LongNameForTargetMachine(const Analyser::Machine target);
/*!
Returns a map from machine name to the list of options that machine

View File

@ -45,8 +45,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
return get<Configurable::Device>();
}
Utility::TypeRecipient *type_recipient() override {
return get<Utility::TypeRecipient>();
void *raw_pointer() override {
return get();
}
private:

View File

@ -238,7 +238,7 @@ template<bool is_zx81> class ConcreteMachine:
z80_.run_for(cycles);
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
void configure_as_target(const Analyser::Static::Target &target) override final {
is_zx81_ = target.zx8081.isZX81;
if(is_zx81_) {
rom_ = zx81_rom_;
@ -260,17 +260,17 @@ template<bool is_zx81> class ConcreteMachine:
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(target.zx8081.memory_model) {
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
case Analyser::Static::ZX8081MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case StaticAnalyser::ZX8081MemoryModel::SixteenKB:
case Analyser::Static::ZX8081MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB:
case Analyser::Static::ZX8081MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
@ -285,7 +285,7 @@ template<bool is_zx81> class ConcreteMachine:
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) override final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
}
@ -341,16 +341,21 @@ template<bool is_zx81> class ConcreteMachine:
tape_player_.set_motor_control(false);
}
}
void set_tape_is_playing(bool is_playing) override final {
tape_player_.set_motor_control(is_playing);
}
bool get_tape_is_playing() override final {
return tape_player_.get_motor_control();
}
// MARK: - Typer timing
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
// MARK: - Configuration options.
@ -442,7 +447,7 @@ template<bool is_zx81> class ConcreteMachine:
using namespace ZX8081;
// See header; constructs and returns an instance of the ZX80 or 81.
Machine *Machine::ZX8081(const StaticAnalyser::Target &target_hint) {
Machine *Machine::ZX8081(const Analyser::Static::Target &target_hint) {
// Instantiate the correct type of machine.
if(target_hint.zx8081.isZX81)
return new ZX8081::ConcreteMachine<true>();

View File

@ -27,9 +27,10 @@ class Machine:
public:
virtual ~Machine();
static Machine *ZX8081(const StaticAnalyser::Target &target_hint);
static Machine *ZX8081(const Analyser::Static::Target &target_hint);
virtual void set_tape_is_playing(bool is_playing) = 0;
virtual bool get_tape_is_playing() = 0;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -79,6 +79,7 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryThreadSanitizerIssue = "YES"
stopOnEveryUBSanitizerIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "NO">

View File

@ -25,8 +25,7 @@ static NSLock *CSAudioQueueDeallocLock;
@implementation CSWeakAudioQueuePointer
@end
@implementation CSAudioQueue
{
@implementation CSAudioQueue {
AudioQueueRef _audioQueue;
AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer];
NSLock *_storedBuffersLock;
@ -38,13 +37,10 @@ static NSLock *CSAudioQueueDeallocLock;
/*!
@returns @c YES if the queue is running dry; @c NO otherwise.
*/
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
{
- (BOOL)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer {
[_storedBuffersLock lock];
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
{
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity)
{
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) {
if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity) {
if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]);
_storedBuffers[c] = buffer;
[_storedBuffersLock unlock];
@ -59,12 +55,10 @@ static NSLock *CSAudioQueueDeallocLock;
static void audioOutputCallback(
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
{
AudioQueueBufferRef inBuffer) {
// Pull the delegate call for audio queue running dry outside of the locked region, to allow non-deadlocking
// lifecycle -dealloc events to result from it.
if([CSAudioQueueDeallocLock tryLock])
{
if([CSAudioQueueDeallocLock tryLock]) {
CSAudioQueue *queue = ((__bridge CSWeakAudioQueuePointer *)inUserData).queue;
BOOL isRunningDry = NO;
isRunningDry = [queue audioQueue:inAQ didCallbackWithBuffer:inBuffer];
@ -76,14 +70,11 @@ static void audioOutputCallback(
#pragma mark - Standard object lifecycle
- (instancetype)initWithSamplingRate:(Float64)samplingRate
{
- (instancetype)initWithSamplingRate:(Float64)samplingRate {
self = [super init];
if(self)
{
if(!CSAudioQueueDeallocLock)
{
if(self) {
if(!CSAudioQueueDeallocLock) {
CSAudioQueueDeallocLock = [[NSLock alloc] init];
}
_storedBuffersLock = [[NSLock alloc] init];
@ -122,8 +113,7 @@ static void audioOutputCallback(
NULL,
kCFRunLoopCommonModes,
0,
&_audioQueue))
{
&_audioQueue)) {
AudioQueueStart(_audioQueue, NULL);
}
}
@ -131,16 +121,13 @@ static void audioOutputCallback(
return self;
}
- (instancetype)init
{
- (instancetype)init {
return [self initWithSamplingRate:[[self class] preferredSamplingRate]];
}
- (void)dealloc
{
- (void)dealloc {
[CSAudioQueueDeallocLock lock];
if(_audioQueue)
{
if(_audioQueue) {
AudioQueueDispose(_audioQueue, true);
_audioQueue = NULL;
}
@ -167,15 +154,12 @@ static void audioOutputCallback(
#pragma mark - Audio enqueuer
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples
{
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples {
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
[_storedBuffersLock lock];
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++)
{
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes)
{
for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) {
if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes) {
memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes);
_storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes;
@ -197,8 +181,7 @@ static void audioOutputCallback(
#pragma mark - Sampling Rate getters
+ (AudioDeviceID)defaultOutputDevice
{
+ (AudioDeviceID)defaultOutputDevice {
AudioObjectPropertyAddress address;
address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
address.mScope = kAudioObjectPropertyScopeGlobal;
@ -209,8 +192,7 @@ static void audioOutputCallback(
return AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &deviceID) ? 0 : deviceID;
}
+ (Float64)preferredSamplingRate
{
+ (Float64)preferredSamplingRate {
AudioObjectPropertyAddress address;
address.mSelector = kAudioDevicePropertyNominalSampleRate;
address.mScope = kAudioObjectPropertyScopeGlobal;

View File

@ -6,8 +6,6 @@
#import "CSFastLoading.h"
#import "CSAtari2600.h"
#import "CSElectron.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSZX8081.h"

View File

@ -9,7 +9,7 @@
class Atari2600OptionsPanel: MachinePanel {
var atari2600: CSAtari2600! {
get {
return self.machine as! CSAtari2600
return self.machine.atari2600
}
}

View File

@ -18,8 +18,10 @@ class MachineDocument:
CSBestEffortUpdaterDelegate,
CSAudioQueueDelegate
{
lazy var actionLock = NSLock()
lazy var drawLock = NSLock()
fileprivate let actionLock = NSLock()
fileprivate let drawLock = NSLock()
fileprivate let bestEffortLock = NSLock()
var machine: CSMachine!
var name: String! {
get {
@ -77,7 +79,9 @@ class MachineDocument:
}
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
bestEffortLock.lock()
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
bestEffortLock.unlock()
}
fileprivate func setupClockRate() {
@ -91,20 +95,24 @@ class MachineDocument:
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
bestEffortLock.lock()
self.bestEffortUpdater?.clockRate = self.machine.clockRate
bestEffortLock.unlock()
}
override func close() {
optionsPanel?.setIsVisible(false)
optionsPanel = nil
openGLView.delegate = nil
bestEffortLock.lock()
bestEffortUpdater!.delegate = nil
bestEffortUpdater = nil
bestEffortLock.unlock()
actionLock.lock()
drawLock.lock()
machine = nil
openGLView.delegate = nil
openGLView.invalidate()
actionLock.unlock()
drawLock.unlock()
@ -114,10 +122,9 @@ class MachineDocument:
// MARK: configuring
func configureAs(_ analysis: CSStaticAnalyser) {
if let machine = analysis.newMachine() {
if let machine = CSMachine(analyser: analysis) {
self.machine = machine
}
analysis.apply(to: self.machine)
if let optionsPanelNibName = analysis.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
@ -149,6 +156,7 @@ class MachineDocument:
}
func runForNumberOfCycles(_ numberOfCycles: Int32) {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
@ -156,15 +164,19 @@ class MachineDocument:
actionLock.unlock()
}
}
bestEffortLock.unlock()
}
// MARK: CSAudioQueueDelegate
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
bestEffortLock.lock()
bestEffortUpdater?.update()
bestEffortLock.unlock()
}
// MARK: CSOpenGLViewDelegate
final func openGLView(_ view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
bestEffortLock.lock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortUpdater.update()
if drawLock.try() {
@ -172,6 +184,7 @@ class MachineDocument:
drawLock.unlock()
}
}
bestEffortLock.unlock()
}
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {

View File

@ -9,7 +9,7 @@
class ZX8081OptionsPanel: MachinePanel {
var zx8081: CSZX8081! {
get {
return self.machine as! CSZX8081
return self.machine.zx8081
}
}
@ -21,7 +21,7 @@ class ZX8081OptionsPanel: MachinePanel {
let isEnabled = sender.state == .on
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
self.playOrPauseTapeButton.isEnabled = !isEnabled
self.zx8081.useAutomaticTapeMotorControl = isEnabled
self.machine.useAutomaticTapeMotorControl = isEnabled
}
@IBOutlet var playOrPauseTapeButton: NSButton!
@ -44,6 +44,6 @@ class ZX8081OptionsPanel: MachinePanel {
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? .on : .off
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
self.machine.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
}
}

View File

@ -1,16 +0,0 @@
//
// CSMachine+Subclassing.h
// Clock Signal
//
// Created by Thomas Harte on 04/01/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSMachine.h"
#include "CRTMachine.hpp"
@interface CSMachine (Subclassing)
- (void)setupOutputWithAspectRatio:(float)aspectRatio;
@end

View File

@ -12,7 +12,6 @@
@interface CSMachine(Target)
- (void)applyTarget:(const StaticAnalyser::Target &)target;
- (void)applyMedia:(const StaticAnalyser::Media &)media;
- (void)applyMedia:(const Analyser::Static::Media &)media;
@end

View File

@ -7,8 +7,10 @@
//
#import <Foundation/Foundation.h>
#import "CSOpenGLView.h"
#import "CSAudioQueue.h"
#import "CSOpenGLView.h"
#import "CSStaticAnalyser.h"
@class CSMachine;
@protocol CSMachineDelegate
@ -16,16 +18,19 @@
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
@end
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
#import "CSZX8081.h"
@interface CSMachine : NSObject
- (instancetype)init NS_UNAVAILABLE;
/*!
Initialises an instance of CSMachine.
@param machine The pointer to an instance of @c Machine::DynamicMachine . C++ type is omitted because
this header is visible to Swift, and the designated initialiser cannot be placed into a category.
@param result The CSStaticAnalyser result that describes the machine needed.
*/
- (instancetype)initWithMachine:(void *)machine NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (void)runForNumberOfCycles:(int)numberOfCycles;
@ -53,4 +58,8 @@
@property (nonatomic, assign) BOOL useCompositeOutput;
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
@property (nonatomic, readonly) CSAtari2600 *atari2600;
@property (nonatomic, readonly) CSZX8081 *zx8081;
@end

View File

@ -7,7 +7,6 @@
//
#import "CSMachine.h"
#import "CSMachine+Subclassing.h"
#import "CSMachine+Target.h"
#include "CSROMFetcher.hpp"
@ -20,6 +19,7 @@
#include "StandardOptions.hpp"
#include "Typer.hpp"
#import "CSStaticAnalyser+TargetVector.h"
#import "NSBundle+DataResource.h"
#import "NSData+StdVector.h"
@ -61,13 +61,20 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
SpeakerDelegate _speakerDelegate;
MachineDelegate _machineDelegate;
NSLock *_delegateMachineAccessLock;
Machine::DynamicMachine *_machine;
CSStaticAnalyser *_analyser;
std::unique_ptr<Machine::DynamicMachine> _machine;
}
- (instancetype)initWithMachine:(void *)machine {
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
self = [super init];
if(self) {
_machine = (Machine::DynamicMachine *)machine;
_analyser = result;
Machine::Error error;
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
if(!_machine) return nil;
_delegateMachineAccessLock = [[NSLock alloc] init];
_machineDelegate.machine = self;
@ -76,7 +83,6 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
_machine->crt_machine()->set_delegate(&_machineDelegate);
CSApplyROMFetcher(*_machine->crt_machine());
}
return self;
}
@ -174,19 +180,12 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
}
- (void)paste:(NSString *)paste {
KeyboardMachine::Machine *keyboardMachine = _machine->type_recipient();
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
if(keyboardMachine)
keyboardMachine->type_string([paste UTF8String]);
}
- (void)applyTarget:(const StaticAnalyser::Target &)target {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target();
if(configurationTarget) configurationTarget->configure_as_target(target);
}
}
- (void)applyMedia:(const StaticAnalyser::Media &)media {
- (void)applyMedia:(const Analyser::Static::Media &)media {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target();
if(configurationTarget) configurationTarget->insert_media(media);
@ -336,4 +335,20 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
}
}
- (NSString *)userDefaultsPrefix {
// Assumes that the first machine in the targets list is the source of user defaults.
std::string name = Machine::ShortNameForTargetMachine(_analyser.targets.front()->machine);
return [[NSString stringWithUTF8String:name.c_str()] lowercaseString];
}
#pragma mark - Special machines
- (CSAtari2600 *)atari2600 {
return [[CSAtari2600 alloc] initWithAtari2600:_machine->raw_pointer() owner:self];
}
- (CSZX8081 *)zx8081 {
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
}
@end

View File

@ -8,4 +8,4 @@
#include "ROMMachine.hpp"
void CSApplyROMFetcher(ROMMachine::Machine &rom_machine);
ROMMachine::ROMFetcher CSROMFetcher();

View File

@ -14,8 +14,8 @@
#include <string>
void CSApplyROMFetcher(ROMMachine::Machine &rom_machine) {
rom_machine.set_rom_fetcher( [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
ROMMachine::ROMFetcher CSROMFetcher() {
return [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]];
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(auto &name: names) {
@ -31,5 +31,5 @@ void CSApplyROMFetcher(ROMMachine::Machine &rom_machine) {
}
return results;
});
};
}

View File

@ -0,0 +1,15 @@
//
// CSStaticAnalyser+ResultVector.h
// Clock Signal
//
// Created by Thomas Harte on 24/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#import "CSStaticAnalyser.h"
@interface CSStaticAnalyser (ResultVector)
- (std::vector<std::unique_ptr<Analyser::Static::Target>> &)targets;
@end

View File

@ -15,12 +15,8 @@
- (instancetype)initWithFileAtURL:(NSURL *)url;
@property(nonatomic, readonly) NSString *optionsPanelNibName;
- (CSMachine *)newMachine;
@property(nonatomic, readonly) NSString *displayName;
- (void)applyToMachine:(CSMachine *)machine;
@end
@interface CSMediaSet : NSObject

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