1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-07 23:25:00 +00:00

Compare commits

..

6 Commits

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

View File

@@ -1,136 +0,0 @@
name: Build
on: [pull_request]
jobs:
build-mac-xcodebuild:
name: Mac UI / xcodebuild / ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest] #[macos-13, macos-14, macos-15]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Make
working-directory: OSBindings/Mac
run: xcodebuild CODE_SIGN_IDENTITY=-
build-sdl-cmake:
name: SDL UI / cmake / ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
shell: bash
run: |
case $RUNNER_OS in
Linux)
sudo apt-get --allow-releaseinfo-change update
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
;;
macOS)
brew install cmake sdl2
;;
esac
- name: Make
shell: bash
run: |
case $RUNNER_OS in
Linux)
jobs=$(nproc --all)
;;
macOS)
jobs=$(sysctl -n hw.activecpu)
;;
*)
jobs=1
esac
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
cmake --build build -v -j"$jobs"
build-sdl-scons:
name: SDL UI / scons / ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
shell: bash
run: |
case $RUNNER_OS in
Linux)
sudo apt-get --allow-releaseinfo-change update
sudo apt-get --fix-missing install gcc-10 libsdl2-dev scons
;;
macOS)
brew install scons sdl2
;;
esac
- name: Make
working-directory: OSBindings/SDL
shell: bash
run: |
case $RUNNER_OS in
Linux)
jobs=$(nproc --all)
;;
macOS)
jobs=$(sysctl -n hw.activecpu)
;;
*)
jobs=1
esac
scons -j"$jobs"
build-qt5:
name: Qt 5 / ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
uses: jurplel/install-qt-action@v3
with:
version: '5.15.2'
archives: 'qtbase qtmultimedia qtx11extras icu'
- name: Make
working-directory: OSBindings/Qt
shell: bash
run: |
qmake -o Makefile clksignal.pro
make
# build-qt6:
# name: Qt 6 / ${{ matrix.os }}
# strategy:
# matrix:
# os: [ubuntu-latest]
# runs-on: ${{ matrix.os }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# - name: Install dependencies
# uses: jurplel/install-qt-action@v4
# with:
# version: '6.8'
# archives: qtbase
# - name: Make
# working-directory: OSBindings/Qt
# shell: bash
# run: |
# qmake -o Makefile clksignal.pro
# make

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

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

View File

@@ -6,9 +6,9 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ActivityObserver_h
#define ActivityObserver_h
#include <cstdint>
#include <string>
namespace Activity {
@@ -22,38 +22,38 @@ namespace Activity {
and/or to show or unshow status indicators.
*/
class Observer {
public:
virtual ~Observer() = default;
public:
/// Provides hints as to the sort of information presented on an LED.
enum LEDPresentation: uint8_t {
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
Persistent = (1 << 0),
};
/// Provides hints as to the sort of information presented on an LED.
enum LEDPresentation: uint8_t {
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
Persistent = (1 << 0),
};
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
/// Announces to the receiver that there is a drive of name @c name.
///
/// If a drive has the same name as an LED, that LED goes with this drive.
virtual void register_drive([[maybe_unused]] const std::string &name) {}
/// Announces to the receiver that there is a drive of name @c name.
///
/// If a drive has the same name as an LED, that LED goes with this drive.
virtual void register_drive([[maybe_unused]] const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
/// Informs the receiver of the new state of the LED with name @c name.
virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
enum class DriveEvent {
StepNormal,
StepBelowZero,
StepBeyondMaximum
};
enum class DriveEvent {
StepNormal,
StepBelowZero,
StepBeyondMaximum
};
/// Informs the receiver that the named event just occurred for the drive with name @c name.
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
/// Informs the receiver that the named event just occurred for the drive with name @c name.
virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
/// Informs the receiver of the motor-on status of the drive with name @c name.
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
/// Informs the receiver of the motor-on status of the drive with name @c name.
virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
};
}
#endif /* ActivityObserver_h */

View File

@@ -6,15 +6,19 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ActivitySource_h
#define ActivitySource_h
#include "Observer.hpp"
namespace Activity {
class Source {
public:
virtual void set_activity_observer(Observer *observer) = 0;
public:
virtual void set_activity_observer(Observer *observer) = 0;
};
}
#endif /* ActivitySource_h */

View File

@@ -6,11 +6,13 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ConfidenceCounter_hpp
#define ConfidenceCounter_hpp
#include "ConfidenceSource.hpp"
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a confidence source that calculates its probability by virtual of a history of events.
@@ -18,25 +20,28 @@ namespace Analyser::Dynamic {
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() final;
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() final;
/*! 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 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 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();
/*!
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;
private:
int hits_ = 1;
int misses_ = 1;
};
}
}
#endif /* ConfidenceCounter_hpp */

View File

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

View File

@@ -13,10 +13,7 @@
using namespace Analyser::Dynamic;
ConfidenceSummary::ConfidenceSummary(
const std::vector<ConfidenceSource *> &sources,
const std::vector<float> &weights
) :
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);

View File

@@ -6,36 +6,41 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef ConfidenceSummary_hpp
#define ConfidenceSummary_hpp
#include "ConfidenceSource.hpp"
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Summaries a collection of confidence sources by calculating their weighted sum.
*/
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.
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);
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() final;
/*! @returns The weighted sum of all sources. */
float get_confidence() final;
private:
const std::vector<ConfidenceSource *> sources_;
const std::vector<float> weights_;
float weight_sum_;
private:
const std::vector<ConfidenceSource *> sources_;
const std::vector<float> weights_;
float weight_sum_;
};
}
}
#endif /* ConfidenceSummary_hpp */

View File

@@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
namespace {
class MultiStruct: public Reflection::Struct {
public:
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
for(auto device: devices) {
options_.emplace_back(device->get_options());
}
}
void apply() {
auto options = options_.begin();
for(auto device: devices_) {
device->set_options(*options);
++options;
}
}
std::vector<std::string> all_keys() const final {
std::set<std::string> keys;
for(auto &options: options_) {
const auto new_keys = options->all_keys();
keys.insert(new_keys.begin(), new_keys.end());
}
return std::vector<std::string>(keys.begin(), keys.end());
}
std::vector<std::string> values_for(const std::string &name) const final {
std::set<std::string> values;
for(auto &options: options_) {
const auto new_values = options->values_for(name);
values.insert(new_values.begin(), new_values.end());
}
return std::vector<std::string>(values.begin(), values.end());
}
const std::type_info *type_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return info;
}
return nullptr;
}
size_t count_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return options->count_of(name);
}
return 0;
}
const void *get(const std::string &name) const final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void *get(const std::string &name) final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void set(const std::string &name, const void *value, const size_t offset) final {
const auto safe_type = type_of(name);
if(!safe_type) return;
// Set this property only where the child's type is the same as that
// which was returned from here for type_of.
for(auto &options: options_) {
const auto type = options->type_of(name);
if(!type) continue;
if(*type == *safe_type) {
options->set(name, value, offset);
public:
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
for(auto device: devices) {
options_.emplace_back(device->get_options());
}
}
}
private:
const std::vector<Configurable::Device *> &devices_;
std::vector<std::unique_ptr<Reflection::Struct>> options_;
void apply() {
auto options = options_.begin();
for(auto device: devices_) {
device->set_options(*options);
++options;
}
}
std::vector<std::string> all_keys() const final {
std::set<std::string> keys;
for(auto &options: options_) {
const auto new_keys = options->all_keys();
keys.insert(new_keys.begin(), new_keys.end());
}
return std::vector<std::string>(keys.begin(), keys.end());
}
std::vector<std::string> values_for(const std::string &name) const final {
std::set<std::string> values;
for(auto &options: options_) {
const auto new_values = options->values_for(name);
values.insert(new_values.begin(), new_values.end());
}
return std::vector<std::string>(values.begin(), values.end());
}
const std::type_info *type_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return info;
}
return nullptr;
}
size_t count_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return options->count_of(name);
}
return 0;
}
const void *get(const std::string &name) const final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void *get(const std::string &name) final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void set(const std::string &name, const void *value, size_t offset) final {
const auto safe_type = type_of(name);
if(!safe_type) return;
// Set this property only where the child's type is the same as that
// which was returned from here for type_of.
for(auto &options: options_) {
const auto type = options->type_of(name);
if(!type) continue;
if(*type == *safe_type) {
options->set(name, value, offset);
}
}
}
private:
const std::vector<Configurable::Device *> &devices_;
std::vector<std::unique_ptr<Reflection::Struct>> options_;
};
}
@@ -115,6 +115,6 @@ void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &s
options->apply();
}
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() const {
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
return std::make_unique<MultiStruct>(devices_);
}

View File

@@ -6,15 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiConfigurable_hpp
#define MultiConfigurable_hpp
#include "Machines/DynamicMachine.hpp"
#include "Configurable/Configurable.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Configurable/Configurable.hpp"
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configurable interface to multiple machines.
@@ -23,15 +25,18 @@ namespace Analyser::Dynamic {
order of delivered messages.
*/
class MultiConfigurable: public Configurable::Device {
public:
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
public:
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
void set_options(const std::unique_ptr<Reflection::Struct> &) final;
std::unique_ptr<Reflection::Struct> get_options() const final;
// Below is the standard Configurable::Device interface; see there for documentation.
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
std::unique_ptr<Reflection::Struct> get_options() final;
private:
std::vector<Configurable::Device *> devices_;
private:
std::vector<Configurable::Device *> devices_;
};
}
}
#endif /* MultiConfigurable_hpp */

View File

@@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() > index) {
joysticks_.push_back(joysticks[index].get());
}
}
}
const std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
public:
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
joysticks_.push_back(joysticks[index].get());
}
}
}
return inputs;
}
const std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
}
}
}
void set_input(const Input &digital_input, const bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, is_active);
return inputs;
}
}
void set_input(const Input &digital_input, const float value) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
void set_input(const Input &digital_input, bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, is_active);
}
}
}
void reset_all_inputs() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
void set_input(const Input &digital_input, float value) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
}
}
}
private:
std::vector<Input> inputs;
std::vector<Inputs::Joystick *> joysticks_;
void reset_all_inputs() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
}
private:
std::vector<Input> inputs;
std::vector<Inputs::Joystick *> joysticks_;
};
}

View File

@@ -6,14 +6,16 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiJoystickMachine_hpp
#define MultiJoystickMachine_hpp
#include "Machines/DynamicMachine.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the joystick machine interface to multiple machines.
@@ -22,14 +24,17 @@ namespace Analyser::Dynamic {
order of delivered messages.
*/
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
public:
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
public:
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}
}
#endif /* MultiJoystickMachine_hpp */

View File

@@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
}
}
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
for(const auto &machine: machines_) {
machine->set_key_state(key, is_pressed);
}
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
bool MultiKeyboardMachine::can_type(const char c) const {
bool MultiKeyboardMachine::can_type(char c) const {
bool can_type = true;
for(const auto &machine: machines_) {
can_type &= machine->can_type(c);
@@ -51,23 +51,15 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
observed_keys_.insert(
machine->get_keyboard().observed_keys().begin(),
machine->get_keyboard().observed_keys().end()
);
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
is_exclusive_ |= machine->get_keyboard().is_exclusive();
}
}
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
const Key key,
const char value,
const bool is_pressed,
const bool is_repeat
) {
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
bool was_consumed = false;
for(const auto &machine: machines_) {
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
return was_consumed;
}

View File

@@ -6,15 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiKeyboardMachine_hpp
#define MultiKeyboardMachine_hpp
#include "Machines/DynamicMachine.hpp"
#include "Machines/KeyboardMachine.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Machines/KeyboardMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the keyboard machine interface to multiple machines.
@@ -23,34 +25,37 @@ namespace Analyser::Dynamic {
order of delivered messages.
*/
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
private:
std::vector<MachineTypes::KeyboardMachine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
private:
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
std::unique_ptr<MultiKeyboard> keyboard_;
std::vector<MachineTypes::KeyboardMachine *> machines_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
void clear_all_keys() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
bool can_type(char c) const final;
Inputs::Keyboard &get_keyboard() final;
bool set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
private:
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
std::unique_ptr<MultiKeyboard> keyboard_;
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() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
bool can_type(char c) const final;
Inputs::Keyboard &get_keyboard() final;
};
}
}
#endif /* MultiKeyboardMachine_hpp */

View File

@@ -7,7 +7,6 @@
//
#include "MultiMediaTarget.hpp"
#include <unordered_set>
using namespace Analyser::Dynamic;
@@ -19,38 +18,9 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
}
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
// TODO: copy media afresh for each target machine; media
// generally has mutable state.
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}
MultiMediaChangeObserver::MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
auto media_change_observer = machine->media_change_observer();
if(media_change_observer) targets_.push_back(media_change_observer);
}
}
using ChangeEffect = MachineTypes::MediaChangeObserver::ChangeEffect;
ChangeEffect MultiMediaChangeObserver::effect_for_file_did_change(const std::string &name) const {
if(targets_.empty()) {
return ChangeEffect::None;
}
std::unordered_set<ChangeEffect> effects;
for(const auto &target: targets_) {
effects.insert(target->effect_for_file_did_change(name));
}
// No agreement => restart.
if(effects.size() > 1) {
return ChangeEffect::RestartMachine;
}
return *effects.begin();
}

View File

@@ -6,15 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiMediaTarget_hpp
#define MultiMediaTarget_hpp
#include "Machines/MediaTarget.hpp"
#include "Machines/DynamicMachine.hpp"
#include "../../../../Machines/MediaTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the media target interface to multiple machines.
@@ -23,25 +25,17 @@ namespace Analyser::Dynamic {
order of delivered messages.
*/
struct MultiMediaTarget: public MachineTypes::MediaTarget {
public:
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
public:
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard MediaTarget::Machine interface; see there for documentation.
bool insert_media(const Analyser::Static::Media &) final;
// Below is the standard MediaTarget::Machine interface; see there for documentation.
bool insert_media(const Analyser::Static::Media &media) final;
private:
std::vector<MachineTypes::MediaTarget *> targets_;
};
struct MultiMediaChangeObserver: public MachineTypes::MediaChangeObserver {
public:
MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
// Below is the standard MediaTarget::Machine interface; see there for documentation.
ChangeEffect effect_for_file_did_change(const std::string &) const final;
private:
std::vector<MachineTypes::MediaChangeObserver *> targets_;
private:
std::vector<MachineTypes::MediaTarget *> targets_;
};
}
}
#endif /* MultiMediaTarget_hpp */

View File

@@ -19,7 +19,7 @@ template <typename MachineType>
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &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.
std::size_t outstanding_machines;
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
@@ -33,7 +33,7 @@ void MultiInterface<MachineType>::perform_parallel(const std::function<void(Mach
if(machine) function(machine);
std::lock_guard lock(mutex);
--outstanding_machines;
outstanding_machines--;
condition.notify_all();
});
}
@@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
}
// MARK: - MultiScanProducer
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
std::lock_guard machines_lock(machines_mutex_);
@@ -80,12 +80,7 @@ void MultiScanProducer::did_change_machine_order() {
}
// MARK: - MultiAudioProducer
MultiAudioProducer::MultiAudioProducer(
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
std::recursive_mutex &machines_mutex
) :
MultiInterface(machines, machines_mutex)
{
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
speaker_ = MultiSpeaker::create(machines);
}
@@ -101,7 +96,7 @@ void MultiAudioProducer::did_change_machine_order() {
// MARK: - MultiTimedMachine
void MultiTimedMachine::run_for(const Time::Seconds duration) {
void MultiTimedMachine::run_for(Time::Seconds duration) {
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});

View File

@@ -6,11 +6,12 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiProducer_hpp
#define MultiProducer_hpp
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Machines/MachineTypes.hpp"
#include "Machines/DynamicMachine.hpp"
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/MachineTypes.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "MultiSpeaker.hpp"
@@ -18,94 +19,92 @@
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
template <typename MachineType> class MultiInterface {
public:
MultiInterface(
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
std::recursive_mutex &machines_mutex
) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
public:
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
protected:
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
protected:
/*!
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(MachineType *)> &);
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(MachineType *)> &);
/*!
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(MachineType *)> &);
/*!
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(MachineType *)> &);
protected:
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
protected:
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
private:
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
private:
std::vector<Concurrency::AsyncTaskQueue> queues_;
};
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
public:
using MultiInterface::MultiInterface;
public:
using MultiInterface::MultiInterface;
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void did_run_machines(MultiTimedMachine *) = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *const delegate) {
delegate_ = delegate;
}
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void did_run_machines(MultiTimedMachine *) = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void run_for(Time::Seconds duration) final;
void run_for(Time::Seconds duration) final;
private:
void run_for(Cycles) final {}
Delegate *delegate_ = nullptr;
private:
void run_for(const Cycles) final {}
Delegate *delegate_ = nullptr;
};
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
public:
using MultiInterface::MultiInterface;
public:
using MultiInterface::MultiInterface;
/*!
Informs the MultiScanProducer that the order of machines has changed; it
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();
/*!
Informs the MultiScanProducer that the order of machines has changed; it
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();
void set_scan_target(Outputs::Display::ScanTarget *) final;
Outputs::Display::ScanStatus get_scan_status() const final;
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
private:
Outputs::Display::ScanTarget *scan_target_ = nullptr;
private:
Outputs::Display::ScanTarget *scan_target_ = nullptr;
};
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
public:
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
public:
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiAudio that the order of machines has changed; it
uses this as an opportunity to switch speaker delegates as appropriate.
*/
void did_change_machine_order();
/*!
Informs the MultiAudio that the order of machines has changed; it
uses this as an opportunity to switch speaker delegates as appropriate.
*/
void did_change_machine_order();
Outputs::Speaker::Speaker *get_speaker() final;
Outputs::Speaker::Speaker *get_speaker() final;
private:
MultiSpeaker *speaker_ = nullptr;
private:
MultiSpeaker *speaker_ = nullptr;
};
/*!
@@ -117,3 +116,7 @@ private:
*/
}
}
#endif /* MultiProducer_hpp */

View File

@@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
}
}
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
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);
@@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const flo
return ideal / float(speakers_.size());
}
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
stereo_output_ = stereo;
for(const auto &speaker: speakers_) {
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
@@ -54,14 +54,14 @@ bool MultiSpeaker::get_is_stereo() {
return false;
}
void MultiSpeaker::set_output_volume(const float volume) {
void MultiSpeaker::set_output_volume(float volume) {
for(const auto &speaker: speakers_) {
speaker->set_output_volume(volume);
}
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const std::vector<int16_t> &buffer) {
auto delegate = delegate_.load(std::memory_order_relaxed);
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
@@ -70,8 +70,8 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const st
did_complete_samples(this, buffer, stereo_output_);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
auto delegate = delegate_.load(std::memory_order_relaxed);
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
@@ -80,12 +80,12 @@ void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
delegate->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
{
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
auto delegate = delegate_.load(std::memory_order_relaxed);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
}

View File

@@ -6,16 +6,18 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiSpeaker_hpp
#define MultiSpeaker_hpp
#include "Machines/DynamicMachine.hpp"
#include "Outputs/Speaker/Speaker.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Outputs/Speaker/Speaker.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
@@ -25,32 +27,35 @@ namespace Analyser::Dynamic {
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>> &);
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 *);
/// 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) override;
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
bool get_is_stereo() override;
void set_output_volume(float) override;
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
bool get_is_stereo() override;
void set_output_volume(float) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
std::mutex front_speaker_mutex_;
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
std::mutex front_speaker_mutex_;
bool stereo_output_ = false;
bool stereo_output_ = false;
};
}
}
#endif /* MultiSpeaker_hpp */

View File

@@ -7,16 +7,10 @@
//
#include "MultiMachine.hpp"
#include "Outputs/Log.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
namespace {
Log::Logger<Log::Source::MultiMachine> logger;
}
using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
@@ -27,9 +21,7 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
audio_producer_(machines_, machines_mutex_),
joystick_machine_(machines_),
keyboard_machine_(machines_),
media_target_(machines_),
media_change_observer_(machines_)
{
media_target_(machines_) {
timed_machine_.set_delegate(this);
}
@@ -37,13 +29,13 @@ Activity::Source *MultiMachine::activity_source() {
return nullptr; // TODO
}
#define Provider(type, name, member) \
type *MultiMachine::name() { \
if(has_picked_) { \
#define Provider(type, name, member) \
type *MultiMachine::name() { \
if(has_picked_) { \
return machines_.front()->name(); \
} else { \
return &member; \
} \
} else { \
return &member; \
} \
}
Provider(Configurable::Device, configurable_device, configurable_)
@@ -53,7 +45,6 @@ Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
Provider(MachineTypes::MediaTarget, media_target, media_target_)
Provider(MachineTypes::MediaChangeObserver, media_change_observer, media_change_observer_)
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
// TODO.
@@ -70,14 +61,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
void MultiMachine::did_run_machines(MultiTimedMachine *) {
std::lock_guard machines_lock(machines_mutex_);
if constexpr (logger.enabled) {
auto line = logger.info();
for(const auto &machine: machines_) {
auto timed_machine = machine->timed_machine();
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
}
#ifndef NDEBUG
for(const auto &machine: machines_) {
auto timed_machine = machine->timed_machine();
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
}
LOGNBR(std::endl);
#endif
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),

View File

@@ -6,22 +6,24 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef MultiMachine_hpp
#define MultiMachine_hpp
#include "Machines/DynamicMachine.hpp"
#include "../../../Machines/DynamicMachine.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp"
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiMediaTarget.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include "Implementation/MultiMediaTarget.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser::Dynamic {
namespace Analyser {
namespace Dynamic {
/*!
Provides the same interface as to a single machine, while multiplexing all
@@ -38,46 +40,47 @@ namespace Analyser::Dynamic {
the others in the set, that machine stops running.
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
requesting this class as a proxy.
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
requesting this class as a proxy.
@returns @c true if the multimachine would discard all but the first machine in this list;
@c false otherwise.
*/
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
@returns @c true if the multimachine would discard all but the first machine in this list;
@c false otherwise.
*/
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
MachineTypes::TimedMachine *timed_machine() final;
MachineTypes::ScanProducer *scan_producer() final;
MachineTypes::AudioProducer *audio_producer() final;
MachineTypes::JoystickMachine *joystick_machine() final;
MachineTypes::KeyboardMachine *keyboard_machine() final;
MachineTypes::MouseMachine *mouse_machine() final;
MachineTypes::MediaTarget *media_target() final;
MachineTypes::MediaChangeObserver *media_change_observer() final;
void *raw_pointer() final;
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
MachineTypes::TimedMachine *timed_machine() final;
MachineTypes::ScanProducer *scan_producer() final;
MachineTypes::AudioProducer *audio_producer() final;
MachineTypes::JoystickMachine *joystick_machine() final;
MachineTypes::KeyboardMachine *keyboard_machine() final;
MachineTypes::MouseMachine *mouse_machine() final;
MachineTypes::MediaTarget *media_target() final;
void *raw_pointer() final;
private:
void did_run_machines(MultiTimedMachine *) final;
private:
void did_run_machines(MultiTimedMachine *) final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;
MultiConfigurable configurable_;
MultiTimedMachine timed_machine_;
MultiScanProducer scan_producer_;
MultiAudioProducer audio_producer_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;
MultiMediaChangeObserver media_change_observer_;
MultiConfigurable configurable_;
MultiTimedMachine timed_machine_;
MultiScanProducer scan_producer_;
MultiAudioProducer audio_producer_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;
void pick_first();
bool has_picked_ = false;
void pick_first();
bool has_picked_ = false;
};
}
}
#endif /* MultiMachine_hpp */

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,28 +12,25 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "Numeric/StringSimilarity.hpp"
#include <algorithm>
#include <map>
using namespace Analyser::Static::Acorn;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
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();
// Only one mapped item is allowed.
// only one mapped item is allowed
if(segments.size() != 1) continue;
// Cartridges must be 8 or 16 kb in size.
// which must be 8 or 16 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
// Check copyright string.
// is a copyright string present?
const uint8_t copyright_offset = segment.data[7];
if(
segment.data[copyright_offset] != 0x00 ||
@@ -42,16 +39,16 @@ AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
segment.data[copyright_offset+3] != 0x29
) continue;
// Check language entry point.
// is the language entry point valid?
if(!(
(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
)) continue;
// Check service entry point.
// is the service entry point valid?
if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
// Probability of a random binary blob that isn't an Acorn ROM proceeding to here:
// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
// 1/(2^32) *
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
// 1/4
@@ -62,78 +59,65 @@ AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
return acorn_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType,
bool
) {
auto target8bit = std::make_unique<ElectronTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
// Copy appropriate cartridges to the 8-bit target.
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// If there are tapes, attempt to get data from the first.
// if there are any tapes, attempt to get data from the first
if(!media.tapes.empty()) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
auto serialiser = tape->serialiser();
std::vector<File> files = GetFiles(*serialiser);
std::vector<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
if(!files.empty()) {
bool is_basic = true;
// If a file is execute-only, that means *RUN.
if(files.front().flags & File::Flags::ExecuteOnly) {
is_basic = false;
}
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
// Check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN.
if(is_basic) {
std::size_t pointer = 0;
uint8_t *const data = &files.front().data[0];
const std::size_t data_size = files.front().data.size();
while(true) {
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
is_basic = false;
break;
}
if((data[pointer+1]&0x7f) == 0x7f) break;
pointer += data[pointer+3];
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
std::size_t pointer = 0;
uint8_t *const data = &files.front().data[0];
const std::size_t data_size = files.front().data.size();
while(1) {
if(pointer >= data_size-1 || data[pointer] != 13) {
is_basic = false;
break;
}
if((data[pointer+1]&0x7f) == 0x7f) break;
pointer += data[pointer+3];
}
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target8bit->media.tapes = media.tapes;
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target->media.tapes = media.tapes;
}
}
if(!media.disks.empty()) {
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
// Get any sort of catalogue that can be found.
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
// 8-bit options: DFS and Hugo-style ADFS.
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target8bit->media.disks = media.disks;
target8bit->has_dfs = bool(dfs_catalogue);
target8bit->has_pres_adfs = bool(adfs_catalogue);
target->media.disks = media.disks;
target->has_dfs = bool(dfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None) {
target8bit->should_shift_restart = true;
target->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
target->loading_command = "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
@@ -149,79 +133,39 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
target->has_ap6_rom = true;
target->has_sideways_ram = true;
}
}
}
} else if(adfs_catalogue) {
// Archimedes options, implicitly: ADFS, non-Hugo.
targetArchimedes->media.disks = media.disks;
// Also look for the best possible startup program name, if it can be discerned.
std::multimap<double, std::string, std::greater<double>> options;
for(const auto &file: adfs_catalogue->files) {
// Skip non-Pling files.
if(file.name[0] != '!') continue;
// Take whatever else comes with a preference for things that don't
// have 'boot' or 'read' in them (the latter of which will tend to be
// read_me or read_this or similar).
constexpr char read[] = "read";
constexpr char boot[] = "boot";
const auto has = [&](const char *begin, const char *end) {
return std::search(
file.name.begin(), file.name.end(),
begin, end - 1, // i.e. don't compare the trailing NULL.
[](char lhs, char rhs) {
return std::tolower(lhs) == rhs;
}
) != file.name.end();
};
const auto has_read = has(std::begin(read), std::end(read));
const auto has_boot = has(std::begin(boot), std::end(boot));
const auto probability =
Numeric::similarity(file.name, adfs_catalogue->name) +
Numeric::similarity(file.name, file_name) -
((has_read || has_boot) ? 0.2 : 0.0);
options.emplace(probability, file.name);
}
if(!options.empty()) {
targetArchimedes->main_program = options.begin()->second;
}
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
target8bit->has_acorn_adfs = true;
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target8bit->media.mass_storage_devices = media.mass_storage_devices;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target8bit->should_shift_restart = true;
target->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
target->loading_command = "*CAT\n";
}
}
TargetList targets;
if(!target8bit->media.empty()) {
targets.push_back(std::move(target8bit));
}
if(!targetArchimedes->media.empty()) {
targets.push_back(std::move(targetArchimedes));
if(!target->media.empty()) {
targets.push_back(std::move(target));
}
return targets;
}

View File

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

View File

@@ -10,68 +10,70 @@
#include <deque>
#include "Numeric/CRC.hpp"
#include "Storage/Tape/Parsers/Acorn.hpp"
#include "../../../Numeric/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
using namespace Analyser::Static::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(
Storage::Tape::TapeSerialiser &serialiser,
Storage::Tape::Acorn::Parser &parser
) {
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
auto new_chunk = std::make_unique<File::Chunk>();
int shift_register = 0;
// TODO: move this into the parser
const auto find = [&](int target) {
while(!serialiser.is_at_end() && (shift_register != target)) {
shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
}
};
// TODO: move this into the parser
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
// find next area of high tone
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
shift();
}
// find next 0x2a (swallowing stop bit)
while(!tape->is_at_end() && (shift_register != 0x254)) {
shift();
}
#undef shift
// Find first sync byte that follows high tone.
find(0x3ff);
find(0x254); // i.e. 0x2a wrapped in a 1 start bit and a 0 stop bit.
parser.reset_crc();
parser.reset_error_flag();
// Read name.
char name[11]{};
// read out name
char name[11];
std::size_t name_ptr = 0;
while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = char(parser.get_next_byte(serialiser));
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = char(parser.get_next_byte(tape));
if(!name[name_ptr]) break;
++name_ptr;
}
name[sizeof(name)-1] = '\0';
new_chunk->name = name;
// Read rest of header fields.
new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
// addresses
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
const auto matched_crc = [&]() {
const uint16_t calculated_crc = parser.get_crc();
uint16_t stored_crc = uint16_t(parser.get_next_short(serialiser));
stored_crc = uint16_t((stored_crc >> 8) | (stored_crc << 8));
return stored_crc == calculated_crc;
};
new_chunk->header_crc_matched = matched_crc();
uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
if(!new_chunk->header_crc_matched) return nullptr;
// Bit 6 of the block flag means 'empty block'; allow it to override declared block length.
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
}
new_chunk->data_crc_matched = matched_crc();
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
} else {
new_chunk->data_crc_matched = true;
}
@@ -80,62 +82,67 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
}
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
// Find next chunk with a block number of 0.
while(!chunks.empty() && chunks.front().block_number) {
// find next chunk with a block number of 0
while(chunks.size() && chunks.front().block_number) {
chunks.pop_front();
}
if(chunks.empty()) return nullptr;
// Accumulate sequential blocks until end-of-file bit is set.
if(!chunks.size()) return nullptr;
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
auto file = std::make_unique<File>();
uint16_t block_number = 0;
while(!chunks.empty()) {
while(chunks.size()) {
if(chunks.front().block_number != block_number) return nullptr;
const bool was_last = chunks.front().block_flag & 0x80;
bool was_last = chunks.front().block_flag & 0x80;
file->chunks.push_back(chunks.front());
chunks.pop_front();
++block_number;
block_number++;
if(was_last) break;
}
// Grab metadata flags.
// accumulate total data, copy flags appropriately
file->name = file->chunks.front().name;
file->load_address = file->chunks.front().load_address;
file->execution_address = file->chunks.front().execution_address;
// I think the final chunk's flags are the ones that count; TODO: check.
if(file->chunks.back().block_flag & 0x01) {
// File is locked i.e. for execution only.
// File is locked, which in more generalised terms means it is
// for execution only.
file->flags |= File::Flags::ExecuteOnly;
}
// Copy data into a single big block.
file->data.reserve(file->chunks.size() * 256);
for(auto &chunk : file->chunks) {
// copy all data into a single big block
for(File::Chunk chunk : file->chunks) {
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
}
return file;
}
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Acorn::Parser parser;
// Read all chunks.
// populate chunk list
std::deque<File::Chunk> chunk_list;
while(!serialiser.is_at_end()) {
const std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
while(!tape->is_at_end()) {
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
if(chunk) {
chunk_list.push_back(std::move(*chunk));
chunk_list.push_back(*chunk);
}
}
// Convert to files.
// decompose into file list
std::vector<File> file_list;
while(!chunk_list.empty()) {
const std::unique_ptr<File> next_file = GetNextFile(chunk_list);
while(chunk_list.size()) {
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
if(next_file) {
file_list.push_back(std::move(*next_file));
file_list.push_back(*next_file);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,15 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Amiga_Target_h
#define Analyser_Static_Amiga_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Amiga {
namespace Analyser {
namespace Static {
namespace Amiga {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ChipRAM,
@@ -28,16 +31,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
FastRAM fast_ram = FastRAM::EightMegabytes;
Target() : Analyser::Static::Target(Machine::Amiga) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(fast_ram);
DeclareField(chip_ram);
AnnounceEnum(FastRAM);
AnnounceEnum(ChipRAM);
Target() : Analyser::Static::Target(Machine::Amiga) {
if(needs_declare()) {
DeclareField(fast_ram);
DeclareField(chip_ram);
AnnounceEnum(FastRAM);
AnnounceEnum(ChipRAM);
}
}
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

View File

@@ -8,15 +8,15 @@
#include "StaticAnalyser.hpp"
#include "Storage/Disk/Parsers/CPM.hpp"
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
#include "Storage/Tape/Parsers/Spectrum.hpp"
#include "Target.hpp"
#include <algorithm>
#include <cstring>
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
#include "Target.hpp"
namespace {
bool strcmp_insensitive(const char *a, const char *b) {
@@ -63,8 +63,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue,
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
) {
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size());
for(const auto &file : catalogue.files) {
@@ -158,12 +158,9 @@ void InspectCatalogue(
target->loading_command = "cat\n";
}
bool CheckBootSector(
const std::shared_ptr<Storage::Disk::Disk> &disk,
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
) {
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::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() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.
@@ -185,7 +182,7 @@ bool CheckBootSector(
return false;
}
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
// Limited sophistication here; look for a CPC-style file header, that is
// any Spectrum-esque block with a synchronisation character of 0x2c.
//
@@ -194,7 +191,7 @@ bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
Parser parser(Parser::MachineType::AmstradCPC);
while(true) {
const auto block = parser.find_block(serialiser);
const auto block = parser.find_block(tape);
if(!block) break;
if(block->type == 0x2c) {
@@ -207,12 +204,7 @@ bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
} // namespace
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
auto target = std::make_unique<Target>();
target->confidence = 0.5;
@@ -222,8 +214,7 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
if(!media.tapes.empty()) {
bool has_cpc_tape = false;
for(auto &tape: media.tapes) {
const auto serialiser = tape->serialiser();
has_cpc_tape |= IsAmstradTape(*serialiser);
has_cpc_tape |= IsAmstradTape(tape);
}
if(has_cpc_tape) {
@@ -237,14 +228,26 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
}
if(!media.disks.empty()) {
const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();
const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
data_format.block_size = 1024;
data_format.first_sector = 0xc1;
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
for(auto &disk: media.disks) {
// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
// Check for an ordinary catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
target->media.disks.push_back(disk);
continue;
@@ -257,9 +260,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
}
// Failing that check for a system catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
target->media.disks.push_back(disk);
continue;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,13 +6,16 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_AppleIIgs_Target_h
#define Analyser_Static_AppleIIgs_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::AppleIIgs {
namespace Analyser {
namespace Static {
namespace AppleIIgs {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
@@ -29,16 +32,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
Model model = Model::ROM01;
MemoryModel memory_model = MemoryModel::EightMB;
Target() : Analyser::Static::Target(Machine::AppleIIgs) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(model);
DeclareField(memory_model);
AnnounceEnum(Model);
AnnounceEnum(MemoryModel);
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
if(needs_declare()) {
DeclareField(model);
DeclareField(memory_model);
AnnounceEnum(Model);
AnnounceEnum(MemoryModel);
}
}
};
}
}
}
#endif /* Analyser_Static_AppleIIgs_Target_h */

View File

@@ -10,7 +10,7 @@
#include "Target.hpp"
#include "Analyser/Static/Disassembler/6502.hpp"
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari2600;
using Target = Analyser::Static::Atari2600::Target;
@@ -33,13 +33,11 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
// 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(const auto &entry : high_location_disassembly.instructions_by_address) {
using Instruction = Analyser::Static::MOS6502::Instruction;
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 == Instruction::Indirect ||
entry.second.addressing_mode == Instruction::IndexedIndirectX ||
entry.second.addressing_mode == Instruction::IndirectIndexedY;
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;
}
@@ -52,21 +50,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(
Target &target,
const Storage::Cartridge::Cartridge::Segment &segment,
const Analyser::Static::MOS6502::Disassembly &disassembly
) {
static void DeterminePagingFor8kCartridge(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?).
if(
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(
segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
segment.data[8190] != 0x00 || segment.data[8188] != 0x00
) &&
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.paging_model = Target::PagingModel::ActivisionStack;
@@ -98,11 +88,7 @@ static void DeterminePagingFor8kCartridge(
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(
Target &target,
const Storage::Cartridge::Cartridge::Segment &,
const Analyser::Static::MOS6502::Disassembly &disassembly
) {
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is the Atari paging model.
target.paging_model = Target::PagingModel::Atari16k;
@@ -122,11 +108,7 @@ static void DeterminePagingFor16kCartridge(
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(
Target &target,
const Storage::Cartridge::Cartridge::Segment &,
const Analyser::Static::MOS6502::Disassembly &disassembly
) {
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is a Tigervision if there is a write to 3F.
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
@@ -139,12 +121,8 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
return;
}
const auto word = [](const uint8_t low, const uint8_t high) {
return uint16_t(low | (high << 8));
};
const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return size_t(-1);
@@ -152,16 +130,27 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
};
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
const auto disassembly =
Analyser::Static::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.paging_model = Target::PagingModel::Pitfall2; break;
case 12288: target.paging_model = Target::PagingModel::CBSRamPlus; break;
case 16384: DeterminePagingFor16kCartridge(target, segment, disassembly); break;
case 32768: target.paging_model = Target::PagingModel::Atari32k; break;
case 65536: DeterminePagingFor64kCartridge(target, segment, disassembly); break;
case 8192:
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.paging_model = Target::PagingModel::Pitfall2;
break;
case 12288:
target.paging_model = Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.paging_model = Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
break;
default:
break;
}
@@ -188,12 +177,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
}
}
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// TODO: sanity checking; is this image really for an Atari 2600?
auto target = std::make_unique<Target>();
target->confidence = 0.5;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
#include "StaticAnalyser.hpp"
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
for(const auto &cartridge : cartridges) {
@@ -52,20 +52,11 @@ ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartr
return coleco_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
const bool is_confident
) {
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList targets;
auto target = std::make_unique<Target>(Machine::ColecoVision);
target->confidence = 1.0f - 1.0f / 32768.0f;
if(is_confident) {
target->media = media;
} else {
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
}
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())
targets.push_back(std::move(target));
return targets;

View File

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

View File

@@ -7,168 +7,166 @@
//
#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 <array>
#include <limits>
#include <unordered_map>
#include <vector>
#include <array>
using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
emplace_drive(4000000, 300, 2);
set_drive(1);
get_drive().set_motor_on(true);
}
public:
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
emplace_drive(4000000, 300, 2);
set_drive(1);
get_drive().set_motor_on(true);
}
struct Sector {
uint8_t sector, track;
std::array<uint8_t, 256> data;
bool header_checksum_matched;
bool data_checksum_matched;
};
struct Sector {
uint8_t sector, track;
std::array<uint8_t, 256> data;
bool header_checksum_matched;
bool data_checksum_matched;
};
/*!
Attempts to read the sector located at @c track and @c sector.
/*!
Attempts to read the sector located at @c track and @c sector.
@returns a sector if one was found; @c nullptr otherwise.
*/
const Sector *sector(const uint8_t track, const uint8_t sector) {
int difference = int(track) - int(track_);
track_ = track;
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = int(track) - int(track_);
track_ = track;
if(difference) {
const int direction = difference < 0 ? -1 : 1;
difference *= direction;
if(difference) {
int direction = difference < 0 ? -1 : 1;
difference *= direction;
for(int c = 0; c < difference; c++) {
get_drive().step(Storage::Disk::HeadPosition(direction));
for(int c = 0; c < difference; c++) {
get_drive().step(Storage::Disk::HeadPosition(direction));
}
unsigned int zone = 3;
if(track >= 18) zone = 2;
else if(track >= 25) zone = 1;
else if(track >= 31) zone = 0;
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
}
unsigned int zone = 3;
if(track >= 18) zone = 2;
else if(track >= 25) zone = 1;
else if(track >= 31) zone = 0;
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
return get_sector(sector);
}
return get_sector(sector);
}
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
get_drive().set_disk(disk);
}
private:
unsigned int shift_register_;
int index_count_;
int bit_count_;
uint8_t track_;
std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
void process_input_bit(const int value) override {
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
bit_count_++;
}
unsigned int proceed_to_next_block(const int max_index_count) {
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
run_for(Cycles(1));
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
get_drive().set_disk(disk);
}
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < max_index_count) {
run_for(Cycles(1));
private:
unsigned int shift_register_;
int index_count_;
int bit_count_;
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
bit_count_++;
}
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
unsigned int proceed_to_next_block(int max_index_count) {
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
unsigned int get_next_byte() {
bit_count_ = 0;
while(bit_count_ < 10) run_for(Cycles(1));
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
run_for(Cycles(1));
}
void proceed_to_shift_value(const unsigned int shift_value) {
const int max_index_count = index_count_ + 2;
while(shift_register_ != shift_value && index_count_ < max_index_count) {
run_for(Cycles(1));
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < max_index_count) {
run_for(Cycles(1));
}
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
}
void process_index_hole() override {
index_count_++;
}
const Sector *get_sector(const uint8_t sector) {
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
auto existing = sector_cache_.find(sector_address);
if(existing != sector_cache_.end()) return existing->second.get();
const auto first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(true) {
const auto next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
unsigned int get_next_byte() {
bit_count_ = 0;
while(bit_count_ < 10) run_for(Cycles(1));
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
}
const Sector *get_next_sector() {
auto sector = std::make_unique<Sector>();
const int max_index_count = index_count_ + 2;
void proceed_to_shift_value(unsigned int shift_value) {
const int max_index_count = index_count_ + 2;
while(shift_register_ != shift_value && index_count_ < max_index_count) {
run_for(Cycles(1));
}
}
void process_index_hole() {
index_count_++;
}
std::shared_ptr<Sector> get_sector(uint8_t sector) {
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
const std::shared_ptr<Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(index_count_ < max_index_count) {
// look for a sector header
while(1) {
if(proceed_to_next_block(max_index_count) == 0x08) break;
if(index_count_ >= max_index_count) return nullptr;
}
// get sector details, skip if this looks malformed
uint8_t checksum = uint8_t(get_next_byte());
sector->sector = uint8_t(get_next_byte());
sector->track = uint8_t(get_next_byte());
uint8_t disk_id[2];
disk_id[0] = uint8_t(get_next_byte());
disk_id[1] = uint8_t(get_next_byte());
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
while(1) {
if(proceed_to_next_block(max_index_count) == 0x07) break;
if(index_count_ >= max_index_count) return nullptr;
}
checksum = 0;
for(std::size_t c = 0; c < 256; c++) {
sector->data[c] = uint8_t(get_next_byte());
checksum ^= sector->data[c];
}
if(checksum == get_next_byte()) {
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
auto pair = sector_cache_.emplace(sector_address, std::move(sector));
return pair.first->second.get();
const std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
return nullptr;
}
std::shared_ptr<Sector> get_next_sector() {
auto sector = std::make_shared<Sector>();
const int max_index_count = index_count_ + 2;
while(index_count_ < max_index_count) {
// look for a sector header
while(1) {
if(proceed_to_next_block(max_index_count) == 0x08) break;
if(index_count_ >= max_index_count) return nullptr;
}
// get sector details, skip if this looks malformed
uint8_t checksum = uint8_t(get_next_byte());
sector->sector = uint8_t(get_next_byte());
sector->track = uint8_t(get_next_byte());
uint8_t disk_id[2];
disk_id[0] = uint8_t(get_next_byte());
disk_id[1] = uint8_t(get_next_byte());
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
while(1) {
if(proceed_to_next_block(max_index_count) == 0x07) break;
if(index_count_ >= max_index_count) return nullptr;
}
checksum = 0;
for(std::size_t c = 0; c < 256; c++) {
sector->data[c] = uint8_t(get_next_byte());
checksum ^= sector->data[c];
}
if(checksum == get_next_byte()) {
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
}
}
return nullptr;
}
};
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
@@ -176,26 +174,20 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
CommodoreGCRParser parser;
parser.set_disk(disk);
// Assemble directory.
// find any sector whatsoever to establish the current track
std::shared_ptr<CommodoreGCRParser::Sector> sector;
// assemble directory
std::vector<uint8_t> directory;
uint8_t next_track = 18;
uint8_t next_sector = 1;
directory.reserve(20 * 1024); // Probably more than plenty.
std::set<std::pair<uint8_t, uint8_t>> visited;
while(true) {
// Don't be fooled by disks that are encoded with a looping directory.
const auto key = std::make_pair(next_track, next_sector);
if(visited.find(key) != visited.end()) break;
visited.insert(key);
// Append sector to directory and follow next link.
const auto sector = parser.sector(next_track, next_sector);
while(1) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
next_track = sector->data[0];
next_sector = sector->data[1];
// Check for end-of-directory.
if(!next_track) break;
}
@@ -224,36 +216,24 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
}
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
const std::size_t number_of_sectors =
size_t(directory[header_pointer + 0x1e]) +
(size_t(directory[header_pointer + 0x1f]) << 8);
if(number_of_sectors) {
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
while(next_track) {
const auto sector = parser.sector(next_track, next_sector);
if(!sector) break;
bool is_first_sector = true;
while(next_track) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];
next_sector = sector->data[1];
next_track = sector->data[0];
next_sector = sector->data[1];
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
if(next_track)
new_file.data.insert(
new_file.data.end(),
sector->data.begin() + (is_first_sector ? 4 : 2),
sector->data.end()
);
else
new_file.data.insert(
new_file.data.end(),
sector->data.begin() + 2,
sector->data.begin() + next_sector
);
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
if(next_track)
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
else
new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
is_first_sector = false;
}
is_first_sector = false;
}
if(!next_track) files.push_back(new_file);

View File

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

View File

@@ -0,0 +1,47 @@
//
// File.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/09/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include "File.hpp"
bool Analyser::Static::Commodore::File::is_basic() {
// BASIC files are always relocatable (?)
if(type != File::RelocatableProgram) return false;
uint16_t line_address = starting_address;
int line_number = -1;
// decide whether this is a BASIC file based on the proposition that:
// (1) they're always relocatable; and
// (2) they have a per-line structure of:
// [4 bytes: address of start of next line]
// [4 bytes: this line number]
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)
while(1) {
if(size_t(line_address - starting_address) >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
if(!next_line_address) {
return true;
}
if(next_line_address < line_address + 5) break;
if(size_t(line_address - starting_address) >= data.size() + 5) break;
uint16_t next_line_number = data[line_address - starting_address + 2];
next_line_number |= data[line_address - starting_address + 3] << 8;
if(next_line_number <= line_number) break;
line_number = uint16_t(next_line_number);
line_address = next_line_address;
}
return false;
}

View File

@@ -6,13 +6,15 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef File_hpp
#define File_hpp
#include <cstdint>
#include <string>
#include <vector>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct File {
std::wstring name;
@@ -29,6 +31,12 @@ struct File {
Relative
} type;
std::vector<uint8_t> data;
bool is_basic();
};
}
}
}
#endif /* File_hpp */

View File

@@ -12,33 +12,26 @@
#include "File.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include "Outputs/Log.hpp"
#include "Analyser/Static/Disassembler/6502.hpp"
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
#include <cstring>
#include <optional>
#include <sstream>
#include <unordered_set>
using namespace Analyser::Static::Commodore;
namespace {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &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();
// Only one mapped item is allowed ...
// only one mapped item is allowed
if(segments.size() != 1) continue;
// ... which must be 16 kb in size.
// which must be 16 kb in size
Storage::Cartridge::Cartridge::Segment segment = segments.front();
if(segment.start_address != 0xa000) continue;
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
@@ -46,312 +39,126 @@ Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
vic20_cartridges.push_back(cartridge);
}
// TODO: other machines?
return vic20_cartridges;
}
struct BASICAnalysis {
enum class Version {
NotBASIC,
BASIC2,
BASIC4,
BASIC3_5,
} minimum_version = Version::NotBASIC;
std::vector<uint16_t> machine_code_addresses;
};
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
TargetList destination;
std::optional<BASICAnalysis> analyse(const File &file) {
BASICAnalysis analysis;
auto target = std::make_unique<Target>();
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 0.5; // TODO: a proper estimation
switch(file.type) {
// For 'program' types, proceed with analysis below.
case File::RelocatableProgram:
case File::NonRelocatableProgram:
break;
// For sequential and relative data stop right now.
case File::DataSequence:
case File::Relative:
return std::nullopt;
// For user data, try decoding from the starting point.
case File::User:
analysis.machine_code_addresses.push_back(file.starting_address);
return analysis;
}
// Don't form an opinion if file is empty.
if(file.data.empty()) {
return std::nullopt;
}
uint16_t line_address = file.starting_address;
// int previous_line_number = -1;
const auto byte = [&](uint16_t address) {
return file.data[address - file.starting_address];
};
const auto word = [&](uint16_t address) {
return uint16_t(byte(address) | byte(address + 1) << 8);
};
// BASIC programs have a per-line structure of:
// [2 bytes: address of start of next line]
// [2 bytes: this line number]
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)
//
// If a SYS is encountered that jumps into the BASIC program then treat that as
// a machine code entry point.
std::unordered_set<uint16_t> visited_lines;
while(true) {
// Analysis has failed if there isn't at least one complete BASIC line from here.
// Fall back on guessing the start address as a machine code entrypoint.
if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
analysis.machine_code_addresses.push_back(file.starting_address);
break;
}
const auto next_line_address = word(line_address);
// const auto line_number = word(line_address + 2);
uint16_t code = line_address + 4;
const auto next = [&]() -> uint8_t {
if(code >= file.starting_address + file.data.size()) {
return 0;
}
return byte(code++);
};
// TODO: sanity check on apparent line contents.
// TODO: observe token set (and possibly parameters?) to guess BASIC version.
while(true) {
const auto token = next();
if(!token || token == 0x8f) break;
switch(token) {
case 0x9e: { // SYS; parse following ASCII argument.
uint16_t address = 0;
while(true) {
const auto c = next();
if(c < '0' || c > '9') {
break;
}
address = (address * 10) + (c - '0');
};
analysis.machine_code_addresses.push_back(address);
} break;
}
}
// Exit if a formal end of the program has been declared or if, as some copy protections do,
// the linked list of line contents has been made circular.
visited_lines.insert(line_address);
if(!next_line_address || visited_lines.find(next_line_address) != visited_lines.end()) {
break;
}
// previous_line_number = line_number;
line_address = next_line_address;
}
return analysis;
}
template <typename TargetT>
void set_loading_command(TargetT &target) {
if(target.media.disks.empty()) {
target.loading_command = "LOAD\"\",1,1\nRUN\n";
} else {
target.loading_command = "LOAD\"*\",8,1\nRUN\n";
}
}
bool obviously_uses_ted(const File &file) {
const auto analysis = analyse(file);
if(!analysis) return false;
// Disassemble.
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
file.data,
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
analysis->machine_code_addresses
);
// Check for interrupt status and paging touches.
for(const auto address: {0xff3e, 0xff3f, 0xff09}) {
for(const auto &collection: {
disassembly.external_loads,
disassembly.external_stores,
disassembly.external_modifies
}) {
if(collection.find(uint16_t(address)) != collection.end()) {
return true;
}
}
}
return false;
}
struct FileAnalysis {
int device = 0;
std::vector<File> files;
bool is_disk = false;
Analyser::Static::Media media;
};
template <TargetPlatform::Type platform>
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
FileAnalysis analysis;
// strip out inappropriate cartridges
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
// Find all valid Commodore files on disks.
// check disks
for(auto &disk : media.disks) {
std::vector<File> disk_files = GetFiles(disk);
if(!disk_files.empty()) {
analysis.is_disk = true;
analysis.files.insert(
analysis.files.end(),
std::make_move_iterator(disk_files.begin()),
std::make_move_iterator(disk_files.end())
);
analysis.media.disks.push_back(disk);
if(!analysis.device) analysis.device = 8;
is_disk = true;
files.insert(files.end(), disk_files.begin(), disk_files.end());
target->media.disks.push_back(disk);
if(!device) device = 8;
}
}
// Find all valid Commodore files on tapes.
// check tapes
for(auto &tape : media.tapes) {
auto serialiser = tape->serialiser();
std::vector<File> tape_files = GetFiles(*serialiser, platform);
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(!tape_files.empty()) {
analysis.files.insert(
analysis.files.end(),
std::make_move_iterator(tape_files.begin()),
std::make_move_iterator(tape_files.end())
);
analysis.media.tapes.push_back(tape);
if(!analysis.device) analysis.device = 1;
files.insert(files.end(), tape_files.begin(), tape_files.end());
target->media.tapes.push_back(tape);
if(!device) device = 1;
}
}
return analysis;
}
std::string loading_command(const FileAnalysis &file_analysis) {
std::ostringstream string_stream;
string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
const auto analysis = analyse(file_analysis.files[0]);
if(analysis && !analysis->machine_code_addresses.empty()) {
string_stream << ",1";
}
string_stream << "\nRUN\n";
return string_stream.str();
}
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
analyse_starting_address(uint16_t starting_address) {
switch(starting_address) {
case 0x1c01:
// TODO: assume C128.
default:
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
"Unrecognised loading address for Commodore program: %04x", starting_address);
[[fallthrough]];
case 0x1001:
return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
case 0x1201: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
case 0x0401: return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
case 0x0801: return std::make_pair(TargetPlatform::C64, std::nullopt);
}
}
template <TargetPlatform::IntType platform>
std::unique_ptr<Analyser::Static::Target> get_target(
const Analyser::Static::Media &media,
const std::string &file_name,
bool is_confident
);
template<>
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
const Analyser::Static::Media &media,
const std::string &,
bool is_confident
) {
auto target = std::make_unique<Plus4Target>();
if(is_confident) {
target->media = media;
set_loading_command(*target);
} else {
const auto files = analyse_files<TargetPlatform::Plus4>(media);
if(!files.files.empty()) {
target->loading_command = loading_command(files);
if(!files.empty()) {
auto memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
string_stream << "0";
} else {
string_stream << "1";
}
target->media.disks = media.disks;
target->media.tapes = media.tapes;
}
string_stream << "\nRUN\n";
target->loading_command = string_stream.str();
// Attach a 1541 if there are any disks here.
target->has_c1541 = !target->media.disks.empty();
return target;
}
template<>
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
const Analyser::Static::Media &media,
const std::string &file_name,
bool is_confident
) {
auto target = std::make_unique<Vic20Target>();
const auto files = analyse_files<TargetPlatform::Vic20>(media);
if(!files.files.empty()) {
target->loading_command = loading_command(files);
const auto model = analyse_starting_address(files.files[0].starting_address);
if(model.second.has_value()) {
target->set_memory_model(*model.second);
}
}
if(is_confident) {
target->media = media;
set_loading_command(*target);
} else {
// Strip out inappropriate cartridges but retain all tapes and disks.
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
target->media.disks = media.disks;
target->media.tapes = media.tapes;
}
for(const auto &file : files.files) {
// The Vic-20 never has RAM after 0x8000.
if(file.ending_address >= 0x8000) {
return nullptr;
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
[[fallthrough]];
case 0x1001:
memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201:
memory_model = Target::MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
memory_model = Target::MemoryModel::EightKB;
break;
}
if(obviously_uses_ted(file)) {
return nullptr;
}
target->set_memory_model(memory_model);
// General approach: increase memory size conservatively such that the largest file found will fit.
// for(File &file : files) {
// std::size_t file_size = file.data.size();
// bool is_basic = file.is_basic();
/*if(is_basic)
{
// BASIC files may be relocated, so the only limit is size.
//
// 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;
}
else
{*/
// if(!file.type == File::NonRelocatableProgram)
// {
// Non-BASIC files may be relocatable but, if so, by what logic?
// Given that this is unknown, take starting address as literal
// and check against memory windows.
//
// (ignoring colour memory...)
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
// A 32kb expanded Vic has memory in the entire low 32kb.
// uint16_t starting_address = file.starting_address;
// 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->memory_model = Target::MemoryModel::ThirtyTwoKB;
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// }
// }
}
// Inspect filename for configuration hints.
if(!target->media.empty()) {
using Region = Analyser::Static::Commodore::Vic20Target::Region;
// Inspect filename for configuration hints.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
// Hint 1: 'ntsc' anywhere in the name implies America.
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Region::American;
target->region = Analyser::Static::Commodore::Target::Region::American;
}
// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
// Potential additional hints: check for TheC64 tags.
auto final_underscore = lowercase_name.find_last_of('_');
if(final_underscore != std::string::npos) {
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
@@ -373,50 +180,25 @@ std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
target->region = Region::American;
target->region = Analyser::Static::Commodore::Target::Region::American;
}
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
target->region = Region::European;
target->region = Analyser::Static::Commodore::Target::Region::European;
}
// Unhandled:
//
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
// RO: this disk image should be treated as read-only.
}
}
}
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();
return target;
}
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();
}
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType platforms,
bool is_confident
) {
TargetList destination;
if(platforms & TargetPlatform::Vic20) {
auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
if(vic20) {
destination.push_back(std::move(vic20));
}
}
if(platforms & TargetPlatform::Plus4) {
auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
if(plus4) {
destination.push_back(std::move(plus4));
}
destination.push_back(std::move(target));
}
return destination;

View File

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

View File

@@ -7,27 +7,26 @@
//
#include "Tape.hpp"
#include "Storage/Tape/Parsers/Commodore.hpp"
#include <algorithm>
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
using namespace Analyser::Static::Commodore;
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
Storage::Tape::Commodore::Parser parser(type);
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Commodore::Parser parser;
std::vector<File> file_list;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
while(!serialiser.is_at_end()) {
while(!tape->is_at_end()) {
if(!header) {
header = parser.get_next_header(serialiser);
header = parser.get_next_header(tape);
continue;
}
switch(header->type) {
case Storage::Tape::Commodore::Header::DataSequenceHeader: {
File &new_file = file_list.emplace_back();
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
new_file.starting_address = header->starting_address;
@@ -35,36 +34,38 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSeria
new_file.type = File::DataSequence;
new_file.data.swap(header->data);
while(!serialiser.is_at_end()) {
header = parser.get_next_header(serialiser);
while(!tape->is_at_end()) {
header = parser.get_next_header(tape);
if(!header) continue;
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
std::ranges::copy(header->data, std::back_inserter(new_file.data));
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
}
file_list.push_back(new_file);
}
break;
case Storage::Tape::Commodore::Header::RelocatableProgram:
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
if(data) {
File &new_file = file_list.emplace_back();
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
new_file.starting_address = header->starting_address;
new_file.ending_address = header->ending_address;
new_file.data.swap(data->data);
new_file.type =
header->type == Storage::Tape::Commodore::Header::RelocatableProgram
? File::RelocatableProgram : File::NonRelocatableProgram;
new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
file_list.push_back(new_file);
}
header = parser.get_next_header(serialiser);
header = parser.get_next_header(tape);
}
break;
default:
header = parser.get_next_header(serialiser);
header = parser.get_next_header(tape);
break;
}
}

View File

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

View File

@@ -6,30 +6,19 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Commodore {
namespace Analyser {
namespace Static {
namespace Commodore {
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
// TODO: region, etc.
std::string loading_command;
bool has_c1541 = false;
Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
private:
friend Reflection::StructImpl<Plus4Target>;
void declare_fields() {
DeclareField(has_c1541);
}
};
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class MemoryModel {
Unexpanded,
EightKB,
@@ -68,20 +57,22 @@ struct Vic20Target: public Analyser::Static::Target, public Reflection::StructIm
bool has_c1540 = false;
std::string loading_command;
Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
private:
friend Reflection::StructImpl<Vic20Target>;
void declare_fields() {
DeclareField(enabled_ram.bank0);
DeclareField(enabled_ram.bank1);
DeclareField(enabled_ram.bank2);
DeclareField(enabled_ram.bank3);
DeclareField(enabled_ram.bank5);
DeclareField(region);
DeclareField(has_c1540);
AnnounceEnum(Region);
Target() : Analyser::Static::Target(Machine::Vic20) {
if(needs_declare()) {
DeclareField(enabled_ram.bank0);
DeclareField(enabled_ram.bank1);
DeclareField(enabled_ram.bank2);
DeclareField(enabled_ram.bank3);
DeclareField(enabled_ram.bank5);
DeclareField(region);
DeclareField(has_c1540);
AnnounceEnum(Region);
}
}
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -11,18 +11,13 @@
#include "Kernel.hpp"
using namespace Analyser::Static::MOS6502;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
struct MOS6502Disassembler {
static void AddToDisassembly(
PartialDisassembly &disassembly,
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
uint16_t entry_point
) {
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
disassembly.disassembly.internal_calls.insert(entry_point);
uint16_t address = entry_point;
while(true) {
@@ -80,25 +75,23 @@ static void AddToDisassembly(
}
// Decode operation.
#define RM_INSTRUCTION(base, op) \
case base+0x09: case base+0x05: case base+0x15: case base+0x01: \
case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
instruction.operation = op; \
#define RM_INSTRUCTION(base, op) \
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
instruction.operation = op; \
break;
#define URM_INSTRUCTION(base, op) \
#define URM_INSTRUCTION(base, op) \
case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b: \
instruction.operation = op; \
instruction.operation = op; \
break;
#define M_INSTRUCTION(base, op) \
#define M_INSTRUCTION(base, op) \
case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e: \
instruction.operation = op; \
instruction.operation = op; \
break;
#define IM_INSTRUCTION(base, op) \
#define IM_INSTRUCTION(base, op) \
case base: instruction.operation = op; break;
switch(operation) {
default:
instruction.operation = Instruction::KIL;
@@ -243,7 +236,7 @@ static void AddToDisassembly(
case Instruction::Relative: {
std::size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
++address;
address++;
instruction.operand = memory[operand_address];
}
@@ -266,10 +259,7 @@ static void AddToDisassembly(
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// TODO: something wider-ranging than this
if(
instruction.addressing_mode == Instruction::Absolute ||
instruction.addressing_mode == Instruction::ZeroPage
) {
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
const size_t mapped_address = address_mapper(instruction.operand);
const bool is_external = mapped_address >= memory.size();
@@ -282,56 +272,39 @@ static void AddToDisassembly(
case Instruction::ADC: case Instruction::SBC:
case Instruction::LAS:
case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
.insert(instruction.operand);
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
break;
case Instruction::STY: case Instruction::STX: case Instruction::STA:
case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
case Instruction::TAS:
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
.insert(instruction.operand);
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
break;
case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
case Instruction::DCP: case Instruction::ISC:
case Instruction::INC: case Instruction::DEC:
case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
.insert(instruction.operand);
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
break;
}
}
// Decide on overall flow control.
// All relative instructions are flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
switch(instruction.operation) {
default: break;
case Instruction::KIL:
case Instruction::RTS:
case Instruction::RTI:
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
disassembly.implicit_entry_points.push_back(address);
return;
case Instruction::JMP:
// Adding a new entry point for relative jumps was handled above.
if(instruction.addressing_mode == Instruction::Absolute) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
return;
case Instruction::JSR:
disassembly.remaining_entry_points.push_back(instruction.operand);
break;
}
}
}
@@ -343,10 +316,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
memory,
address_mapper,
entry_points,
false
);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
}

View File

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

View File

@@ -6,20 +6,27 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef AddressMapper_hpp
#define AddressMapper_hpp
#include <functional>
namespace Analyser::Static::Disassembler {
namespace Analyser {
namespace Static {
namespace Disassembler {
/*!
Provides an address mapper that relocates a chunk of memory so that it starts at
address @c start_address.
*/
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
return [start_address](T argument) {
return size_t(argument - start_address);
};
}
}
}
}
#endif /* AddressMapper_hpp */

View File

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

View File

@@ -11,60 +11,56 @@
#include "Kernel.hpp"
using namespace Analyser::Static::Z80;
namespace {
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
class Accessor {
public:
Accessor(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
uint16_t address
) :
memory_(memory), address_mapper_(address_mapper), address_(address) {}
public:
Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
memory_(memory), address_mapper_(address_mapper), address_(address) {}
uint8_t byte() {
std::size_t mapped_address = address_mapper_(address_);
++address_;
if(mapped_address >= memory_.size()) {
overrun_ = true;
return 0xff;
uint8_t byte() {
std::size_t mapped_address = address_mapper_(address_);
address_++;
if(mapped_address >= memory_.size()) {
overrun_ = true;
return 0xff;
}
return memory_[mapped_address];
}
return memory_[mapped_address];
}
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return uint16_t(low | (high << 8));
}
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return uint16_t(low | (high << 8));
}
bool overrun() const {
return overrun_;
}
bool overrun() {
return overrun_;
}
bool at_end() const {
std::size_t mapped_address = address_mapper_(address_);
return mapped_address >= memory_.size();
}
bool at_end() {
std::size_t mapped_address = address_mapper_(address_);
return mapped_address >= memory_.size();
}
uint16_t address() const {
return address_;
}
uint16_t address() {
return address_;
}
private:
const std::vector<uint8_t> &memory_;
const std::function<std::size_t(uint16_t)> &address_mapper_;
uint16_t address_;
bool overrun_ = false;
private:
const std::vector<uint8_t> &memory_;
const std::function<std::size_t(uint16_t)> &address_mapper_;
uint16_t address_;
bool overrun_ = false;
};
constexpr uint8_t x(uint8_t v) { return v >> 6; }
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
constexpr uint8_t z(uint8_t v) { return v & 7; }
#define x(v) (v >> 6)
#define y(v) ((v >> 3) & 7)
#define q(v) ((v >> 3) & 1)
#define p(v) ((v >> 4) & 3)
#define z(v) (v & 7)
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
@@ -87,12 +83,8 @@ Instruction::Location register_pair_table2[] = {
Instruction::Location::AF
};
Instruction::Location RegisterTableEntry(
const int offset, Accessor &accessor,
Instruction &instruction,
const bool needs_indirect_offset
) {
constexpr Instruction::Location register_table[] = {
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
Instruction::Location register_table[] = {
Instruction::Location::B, Instruction::Location::C,
Instruction::Location::D, Instruction::Location::E,
Instruction::Location::H, Instruction::Location::L,
@@ -100,7 +92,7 @@ Instruction::Location RegisterTableEntry(
Instruction::Location::A
};
const Instruction::Location location = register_table[offset];
Instruction::Location location = register_table[offset];
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
instruction.offset = accessor.byte() - 128;
}
@@ -108,7 +100,7 @@ Instruction::Location RegisterTableEntry(
return location;
}
constexpr Instruction::Operation alu_table[] = {
Instruction::Operation alu_table[] = {
Instruction::Operation::ADD,
Instruction::Operation::ADC,
Instruction::Operation::SUB,
@@ -119,7 +111,7 @@ constexpr Instruction::Operation alu_table[] = {
Instruction::Operation::CP
};
constexpr Instruction::Operation rotation_table[] = {
Instruction::Operation rotation_table[] = {
Instruction::Operation::RLC,
Instruction::Operation::RRC,
Instruction::Operation::RL,
@@ -130,32 +122,19 @@ constexpr Instruction::Operation rotation_table[] = {
Instruction::Operation::SRL
};
constexpr Instruction::Operation block_table[][4] = {
{
Instruction::Operation::LDI, Instruction::Operation::CPI,
Instruction::Operation::INI, Instruction::Operation::OUTI
},
{
Instruction::Operation::LDD, Instruction::Operation::CPD,
Instruction::Operation::IND, Instruction::Operation::OUTD
},
{
Instruction::Operation::LDIR, Instruction::Operation::CPIR,
Instruction::Operation::INIR, Instruction::Operation::OTIR
},
{
Instruction::Operation::LDDR, Instruction::Operation::CPDR,
Instruction::Operation::INDR, Instruction::Operation::OTDR
},
Instruction::Operation block_table[][4] = {
{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
};
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
if(!x(operation)) {
instruction.operation = rotation_table[y(operation)];
instruction.source = instruction.destination =
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
} else {
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
@@ -169,7 +148,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool
}
}
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
switch(x(operation)) {
@@ -191,8 +170,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
if(y(operation) == 6) {
instruction.destination = Instruction::Location::None;
} else {
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 1:
@@ -201,8 +179,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
if(y(operation) == 6) {
instruction.source = Instruction::Location::None;
} else {
instruction.source =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
@@ -213,13 +190,11 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
case 3:
instruction.operation = Instruction::Operation::LD;
if(q(operation)) {
instruction.destination =
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand_Indirect;
} else {
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source =
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
}
instruction.operand = accessor.word();
break;
@@ -227,8 +202,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
instruction.operation = Instruction::Operation::NEG;
break;
case 5:
instruction.operation =
y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
break;
case 6:
instruction.operation = Instruction::Operation::IM;
@@ -279,7 +253,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
} hl_substitution = None;
while(true) {
const uint8_t operation = accessor.byte();
uint8_t operation = accessor.byte();
switch(x(operation)) {
case 0:
@@ -369,18 +343,15 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
break;
case 4:
instruction.operation = Instruction::Operation::INC;
instruction.source = instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 5:
instruction.operation = Instruction::Operation::DEC;
instruction.source = instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 6:
instruction.operation = Instruction::Operation::LD;
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.byte();
break;
@@ -403,10 +374,8 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
instruction.operation = Instruction::Operation::HALT;
} else {
instruction.operation = Instruction::Operation::LD;
instruction.source =
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
@@ -548,14 +517,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
instruction.destination == Instruction::Location::HL_Indirect) {
if(instruction.source == Instruction::Location::HL_Indirect) {
instruction.source =
hl_substitution == IX ?
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
if(instruction.destination == Instruction::Location::HL_Indirect) {
instruction.destination =
hl_substitution == IX ?
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
return;
}
@@ -577,12 +542,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
}
struct Z80Disassembler {
static void AddToDisassembly(
PartialDisassembly &disassembly,
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
const uint16_t entry_point
) {
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
disassembly.disassembly.internal_calls.insert(entry_point);
Accessor accessor(memory, address_mapper, entry_point);
@@ -629,7 +589,7 @@ struct Z80Disassembler {
break;
}
// Add any (potentially) newly-discovered entry point.
// Add any (potentially) newly discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
@@ -638,37 +598,22 @@ struct Z80Disassembler {
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
switch(instruction.operation) {
default: break;
if(instruction.condition != Instruction::Condition::None) continue;
case Instruction::Operation::RET:
case Instruction::Operation::RETI:
case Instruction::Operation::RETN:
case Instruction::Operation::JP:
case Instruction::Operation::JR:
if(instruction.condition == Instruction::Condition::None) {
disassembly.implicit_entry_points.push_back(accessor.address());
return;
}
}
if(instruction.operation == Instruction::Operation::RET) return;
if(instruction.operation == Instruction::Operation::RETI) return;
if(instruction.operation == Instruction::Operation::RETN) return;
if(instruction.operation == Instruction::Operation::JP) return;
if(instruction.operation == Instruction::Operation::JR) return;
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach)
{
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
memory,
address_mapper,
entry_points,
approach == Approach::Exhaustive
);
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
}

View File

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

View File

@@ -8,14 +8,14 @@
#include "StaticAnalyser.hpp"
#include "Analyser/Static/AppleII/Target.hpp"
#include "Analyser/Static//AppleIIgs/Target.hpp"
#include "Analyser/Static//Oric/Target.hpp"
#include "Analyser/Static//Disassembler/6502.hpp"
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
#include "../AppleII/Target.hpp"
#include "../AppleIIgs/Target.hpp"
#include "../Oric/Target.hpp"
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include "Storage/Disk/Track/TrackSerialiser.hpp"
#include "Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
namespace {
@@ -47,12 +47,7 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
}
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
@@ -60,15 +55,14 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
TargetList targets;
// If the disk image is too large for a 5.25" disk, map this to the IIgs.
if(disk->maximum_head_position() > Storage::Disk::HeadPosition(40)) {
if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
targets.back()->media = media;
return targets;
}
// Grab track 0, sector 0: the boot sector.
const auto track_zero =
disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
@@ -95,8 +89,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
// If the boot sector looks like it's intended for the Oric, create an Oric.
// Otherwise go with the Apple II.
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
bool did_read_shift_register = false;
bool is_oric = false;

View File

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

View File

@@ -9,7 +9,7 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "Storage/Disk/Parsers/FAT.hpp"
#include "../../../Storage/Disk/Parsers/FAT.hpp"
#include <algorithm>
@@ -26,12 +26,7 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
}
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
@@ -45,8 +40,6 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
target->basic_version = Target::BASICVersion::Any;
// Inspect any supplied disks.
//
// TODO: how best can these be discerned from MS-DOS and MSX disks?
if(!media.disks.empty()) {
// DOS will be needed.
target->dos = Target::DOS::EXDOS;
@@ -77,8 +70,7 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
if(!has_exdos_ini) {
if(did_pick_file) {
target->loading_command =
std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
} else {
target->loading_command = ":dir\n";
}

View File

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

View File

@@ -6,15 +6,18 @@
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Enterprise_Target_h
#define Analyser_Static_Enterprise_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Enterprise {
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
@@ -30,23 +33,25 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
Speed speed = Speed::FourMHz;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Enterprise) {}
Target() : Analyser::Static::Target(Machine::Enterprise) {
if(needs_declare()) {
AnnounceEnum(Model);
AnnounceEnum(EXOSVersion);
AnnounceEnum(BASICVersion);
AnnounceEnum(DOS);
AnnounceEnum(Speed);
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
AnnounceEnum(Model);
AnnounceEnum(EXOSVersion);
AnnounceEnum(BASICVersion);
AnnounceEnum(DOS);
AnnounceEnum(Speed);
DeclareField(model);
DeclareField(exos_version);
DeclareField(basic_version);
DeclareField(dos);
DeclareField(speed);
DeclareField(model);
DeclareField(exos_version);
DeclareField(basic_version);
DeclareField(dos);
DeclareField(speed);
}
}
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

View File

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

View File

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

View File

@@ -6,11 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Cartridge_hpp
#define Cartridge_hpp
#include "Storage/Cartridge/Cartridge.hpp"
#include "../../../Storage/Cartridge/Cartridge.hpp"
namespace Analyser::Static::MSX {
namespace Analyser {
namespace Static {
namespace MSX {
/*!
Extends the base cartridge class by adding a (guess at) the banking scheme.
@@ -26,8 +29,12 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
};
const Type type;
Cartridge(const std::vector<Segment> &segments, const Type type) :
Cartridge(const std::vector<Segment> &segments, Type type) :
Storage::Cartridge::Cartridge(segments), type(type) {}
};
}
}
}
#endif /* Cartridge_hpp */

View File

@@ -12,8 +12,8 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "Analyser/Static/Disassembler/Z80.hpp"
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
#include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include <algorithm>
@@ -27,8 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
const auto truncated_size =
std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
std::vector<uint8_t>::difference_type truncated_size = 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 {
@@ -38,11 +37,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
auto target = std::make_unique<Analyser::Static::MSX::Target>();
target->confidence = confidence;
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
if(segment.data.size() < 48*1024) {
target->model = Analyser::Static::MSX::Target::Model::MSX1;
}
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
} else {
@@ -83,7 +77,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
if(segments.size() != 1) continue;
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
Storage::Cartridge::Cartridge::Segment segment = segments.front();
const size_t data_size = segment.data.size();
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
@@ -102,11 +96,10 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// Reject cartridge if the ROM header wasn't found.
if(!found_start) continue;
const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
uint16_t init_address = 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.
// Bonus observation: all such ROMs are from the MSX 1 era.
if(data_size <= 0xc000) {
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
continue;
@@ -116,16 +109,97 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// be at play; disassemble to try to figure it out.
std::vector<uint8_t> first_8k;
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
const Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ init_address },
Analyser::Static::Z80::Approach::Exhaustive
{ init_address }
);
// // Look for a indirect store followed by an unconditional JP or CALL into another
// // segment, that's a fairly explicit sign where found.
using Instruction = Analyser::Static::Z80::Instruction;
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
bool is_ascii = false;
// auto iterator = instructions.begin();
// while(iterator != instructions.end()) {
// auto next_iterator = iterator;
// next_iterator++;
// if(next_iterator == instructions.end()) break;
//
// if( iterator->second.operation == Instruction::Operation::LD &&
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
// (
// iterator->second.operand == 0x5000 ||
// iterator->second.operand == 0x6000 ||
// iterator->second.operand == 0x6800 ||
// iterator->second.operand == 0x7000 ||
// iterator->second.operand == 0x77ff ||
// iterator->second.operand == 0x7800 ||
// iterator->second.operand == 0x8000 ||
// iterator->second.operand == 0x9000 ||
// iterator->second.operand == 0xa000
// ) &&
// (
// next_iterator->second.operation == Instruction::Operation::CALL ||
// next_iterator->second.operation == Instruction::Operation::JP
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x6800:
// if(address >= 0x6000 && address < 0x6800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x7000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// if(address >= 0x7000 && address < 0x7800) {
// is_ascii = true;
// }
// break;
// case 0x77ff:
// if(address >= 0x7000 && address < 0x7800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
// }
// break;
// case 0x7800:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x8000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x9000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0xa000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
// }
// break;
// case 0xb000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// }
// }
//
// iterator = next_iterator;
// Look for LD (nnnn), A instructions, and collate those addresses.
std::map<uint16_t, int> address_counts;
@@ -137,60 +211,56 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
}
}
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
const auto ascii_8kb_total =
address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
const auto konami_with_scc_total =
address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
// Weight confidences by number of observed hits.
float total_hits =
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
address_counts[0xa000] + address_counts[0x5000] +
address_counts[0x9000] + address_counts[0xb000]
);
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
const bool is_konami = (konami_total * 5) / (total_hits * 3);
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float(ascii_8kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float(ascii_16kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
float(konami_total) / float(total_hits)));
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
float(konami_with_scc_total) / float(total_hits)));
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}
return targets;
}
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
// Append targets for any cartridges that look correct.
@@ -202,7 +272,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
// Check tapes for loadable files.
for(auto &tape : media.tapes) {
const std::vector<File> files_on_tape = GetFiles(tape);
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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,14 +9,9 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool is_confident
) {
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
@@ -29,7 +24,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
if(media.mass_storage_devices.empty()) {
bool has_800kb_disks = false;
for(const auto &disk: media.disks) {
has_800kb_disks |= disk->head_count() > 1;
has_800kb_disks |= disk->get_head_count() > 1;
}
if(!has_800kb_disks) {

View File

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

View File

@@ -6,26 +6,32 @@
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Macintosh {
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
Model model = Model::MacPlus;
Target() : Analyser::Static::Target(Machine::Macintosh) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(model);
AnnounceEnum(Model);
Target() : Analyser::Static::Target(Machine::Macintosh) {
// Boilerplate for declaring fields and potential values.
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}
}
}
#endif /* Analyser_Static_Macintosh_Target_h */

View File

@@ -11,10 +11,10 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "Analyser/Static/Disassembler/6502.hpp"
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include <cstring>
@@ -22,22 +22,12 @@ using namespace Analyser::Static::Oric;
namespace {
int score(
const Analyser::Static::MOS6502::Disassembly &disassembly,
const std::set<uint16_t> &rom_functions,
const std::set<uint16_t> &variable_locations
) {
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(const auto address : disassembly.outward_calls) {
score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
}
for(const auto address : disassembly.external_stores) {
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
}
for(const auto address : disassembly.external_loads) {
score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
}
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
for(const auto address : disassembly.external_stores) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
for(const auto address : disassembly.external_loads) score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
return score;
}
@@ -45,32 +35,19 @@ int score(
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524,
0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f,
0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f,
0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0,
0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a,
0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f,
0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f,
0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e,
0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563,
0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974,
0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf,
0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a,
0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7,
0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
0xc773, 0xc824, 0xc832, 0xc841, 0xc8c1, 0xc8fe, 0xc91f, 0xc93f, 0xc941, 0xc91e, 0xc98b, 0xc996, 0xc9b3, 0xc9e0, 0xca0a, 0xca1c,
0xca1f, 0xca3e, 0xca61, 0xca78, 0xca98, 0xcad2, 0xcb61, 0xcb9f, 0xcc59, 0xcbed, 0xcc0a, 0xcc8c, 0xcc8f, 0xccba, 0xccc9, 0xccfd,
0xce0c, 0xce77, 0xce8b, 0xcfac, 0xcf74, 0xd03c, 0xd059, 0xcff0, 0xd087, 0xd0f2, 0xd0fc, 0xd361, 0xd3eb, 0xd47e, 0xd4a6, 0xd401,
0xd593, 0xd5a3, 0xd4fa, 0xd595, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd80a, 0xd867, 0xd938, 0xd894,
0xd89d, 0xd8ac, 0xd983, 0xd993, 0xd9b5, 0xd93d, 0xd965, 0xda3f, 0xd9c6, 0xda16, 0xdaab, 0xdada, 0xda6b, 0xdb92, 0xdbb9, 0xdc79,
0xdd4d, 0xdda3, 0xddbf, 0xd0d0, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf04, 0xdf12, 0xdf31, 0xdf4c, 0xdf8c, 0xdfa5, 0xdfcf, 0xe076,
0xe0c1, 0xe22a, 0xe27c, 0xe2a6, 0xe313, 0xe34b, 0xe387, 0xe38e, 0xe3d7, 0xe407, 0xe43b, 0xe46f, 0xe4a8, 0xe4f2, 0xe554, 0xe57d,
0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe563, 0xe5c6, 0xe630, 0xe696, 0xe6ba, 0xe6ca, 0xe725, 0xe7aa, 0xe903,
0xe7db, 0xe80d, 0xe987, 0xe9d1, 0xe87d, 0xe905, 0xe965, 0xe974, 0xe994, 0xe9a9, 0xe9bb, 0xec45, 0xeccc, 0xedc4, 0xecc7, 0xed01,
0xed09, 0xed70, 0xed81, 0xed8f, 0xe0ad, 0xeee8, 0xeef8, 0xebdf, 0xebe2, 0xebe5, 0xebeb, 0xebee, 0xebf4, 0xebf7, 0xebfa, 0xebe8,
0xf43c, 0xf4ef, 0xf523, 0xf561, 0xf535, 0xf57b, 0xf5d3, 0xf71a, 0xf73f, 0xf7e4, 0xf7e0, 0xf82f, 0xf88f, 0xf8af, 0xf8b5, 0xf920,
0xf967, 0xf960, 0xf9c9, 0xfa14, 0xfa85, 0xfa9b, 0xfab1, 0xfac7, 0xfafa, 0xfb10, 0xfb26, 0xfbb6, 0xfbfe
};
const std::set<uint16_t> variable_locations = {
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
@@ -82,32 +59,19 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const 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,
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915,
0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c,
0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c,
0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767,
0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de,
0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4,
0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f,
0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab,
0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1,
0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab,
0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561,
0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14,
0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
0xc748, 0xc7fd, 0xc809, 0xc816, 0xc82f, 0xc855, 0xc8c1, 0xc915, 0xc952, 0xc971, 0xc973, 0xc9a0, 0xc9bd, 0xc9c8, 0xc9e5, 0xca12,
0xca3c, 0xca4e, 0xca51, 0xca70, 0xca99, 0xcac2, 0xcae2, 0xcb1c, 0xcbab, 0xcbf0, 0xcc59, 0xccb0, 0xccce, 0xcd16, 0xcd19, 0xcd46,
0xcd55, 0xcd89, 0xce98, 0xcf03, 0xcf17, 0xcfac, 0xd000, 0xd03c, 0xd059, 0xd07c, 0xd113, 0xd17e, 0xd188, 0xd361, 0xd3eb, 0xd47e,
0xd4a6, 0xd4ba, 0xd593, 0xd5a3, 0xd5b5, 0xd650, 0xd730, 0xd767, 0xd816, 0xd82a, 0xd856, 0xd861, 0xd8a6, 0xd8b5, 0xd8c5, 0xd922,
0xd938, 0xd94f, 0xd958, 0xd967, 0xd983, 0xd993, 0xd9b5, 0xd9de, 0xda0c, 0xda3f, 0xda51, 0xdaa1, 0xdaab, 0xdada, 0xdaf6, 0xdb92,
0xdbb9, 0xdcaf, 0xdd51, 0xdda7, 0xddc3, 0xddd4, 0xde77, 0xdef4, 0xdf0b, 0xdf0f, 0xdf13, 0xdf21, 0xdf49, 0xdf4c, 0xdf8c, 0xdfbd,
0xdfe7, 0xe076, 0xe0c5, 0xe22e, 0xe27c, 0xe2aa, 0xe313, 0xe34f, 0xe38b, 0xe392, 0xe3db, 0xe407, 0xe43f, 0xe46f, 0xe4ac, 0xe4e0,
0xe4f2, 0xe56c, 0xe57d, 0xe585, 0xe58c, 0xe594, 0xe5a4, 0xe5ab, 0xe5b6, 0xe5ea, 0xe5f5, 0xe607, 0xe65e, 0xe6c9, 0xe735, 0xe75a,
0xe76a, 0xe7b2, 0xe85b, 0xe903, 0xe909, 0xe946, 0xe987, 0xe9d1, 0xeaf0, 0xeb78, 0xebce, 0xebe7, 0xec0c, 0xec21, 0xec33, 0xec45,
0xeccc, 0xedc4, 0xede0, 0xee1a, 0xee22, 0xee8c, 0xee9d, 0xeeab, 0xeec9, 0xeee8, 0xeef8, 0xf0c8, 0xf0fd, 0xf110, 0xf11d, 0xf12d,
0xf204, 0xf210, 0xf268, 0xf37f, 0xf495, 0xf4ef, 0xf523, 0xf561, 0xf590, 0xf5c1, 0xf602, 0xf71a, 0xf77c, 0xf7e4, 0xf816, 0xf865,
0xf88f, 0xf8af, 0xf8b5, 0xf920, 0xf967, 0xf9aa, 0xf9c9, 0xfa14, 0xfa9f, 0xfab5, 0xfacb, 0xfae1, 0xfb14, 0xfb2a, 0xfb40, 0xfbd0,
0xfc18
};
const std::set<uint16_t> variable_locations = {
@@ -121,7 +85,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 2);
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
@@ -138,13 +102,13 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
/*
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
use disassembly to test for likely matches.
*/
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 1);
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
@@ -156,8 +120,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range
}
// Grab a disassembly.
const auto disassembly = Analyser::Static::MOS6502::Disassemble(
first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
const auto disassembly =
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
// Check for references to the Jasmin registers.
int register_hits = 0;
@@ -181,12 +145,7 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->confidence = 0.5;
@@ -194,18 +153,14 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
int basic11_votes = 0;
for(auto &tape : media.tapes) {
auto serialiser = tape->serialiser();
std::vector<File> tape_files = GetFiles(*serialiser);
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(!tape_files.empty()) {
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
const Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(
file.data,
Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
entry_points
);
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
}
@@ -220,7 +175,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
Storage::Encodings::MFM::Parser parser(true, disk);
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::Oric {
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ROM,
@@ -41,18 +44,20 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
std::string loading_command;
bool should_start_jasmin = false;
Target(): Analyser::Static::Target(Machine::Oric) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(rom);
DeclareField(disk_interface);
DeclareField(processor);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
AnnounceEnum(Processor);
Target(): Analyser::Static::Target(Machine::Oric) {
if(needs_declare()) {
DeclareField(rom);
DeclareField(disk_interface);
DeclareField(processor);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
AnnounceEnum(Processor);
}
}
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

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

View File

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

View File

@@ -1,50 +0,0 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#pragma once
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Struct.hpp"
namespace Analyser::Static::PCCompatible {
ReflectableEnum(Model,
XT,
TurboXT,
AT
);
constexpr bool is_xt(const Model model) {
return model <= Model::TurboXT;
}
constexpr bool is_at(const Model model) {
return model >= Model::AT;
}
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(VideoAdaptor,
MDA,
CGA,
);
VideoAdaptor adaptor = VideoAdaptor::CGA;
Model model = Model::TurboXT;
Target() : Analyser::Static::Target(Machine::PCCompatible) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
AnnounceEnum(VideoAdaptor);
AnnounceEnum(Model);
DeclareField(adaptor);
DeclareField(model);
}
};
}

View File

@@ -13,12 +13,7 @@
#include <algorithm>
#include <cstring>
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
const Media &media,
const std::string &file_name,
TargetPlatform::IntType,
bool
) {
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
if(media.cartridges.empty())
return {};
@@ -59,8 +54,7 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
if(lowercase_name.find("(jp)") == std::string::npos) {
target->region =
(lowercase_name.find("(us)") == std::string::npos &&
lowercase_name.find("(ntsc)") == std::string::npos) ?
Target::Region::Europe : Target::Region::USA;
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
}
} break;
}
@@ -69,9 +63,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
// If one is found, set the paging scheme appropriately.
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
if(
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
(inverse_checksum&0xff) == data[0x7fe8] &&
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present.
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
) {
target->paging_scheme = Target::PagingScheme::Codemasters;

View File

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

View File

@@ -6,13 +6,16 @@
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser::Static::Sega {
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class Model {
@@ -37,18 +40,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
Region region = Region::Japan;
PagingScheme paging_scheme = PagingScheme::Sega;
Target() : Analyser::Static::Target(Machine::MasterSystem) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(region);
AnnounceEnum(Region);
Target() : Analyser::Static::Target(Machine::MasterSystem) {
if(needs_declare()) {
DeclareField(region);
AnnounceEnum(Region);
}
}
};
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
}
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
}
}
}
#endif /* Analyser_Static_Sega_Target_h */

View File

@@ -9,89 +9,79 @@
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <bit>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <iterator>
// Analysers
#include "Analyser/Static/Acorn/StaticAnalyser.hpp"
#include "Analyser/Static/Amiga/StaticAnalyser.hpp"
#include "Analyser/Static/AmstradCPC/StaticAnalyser.hpp"
#include "Analyser/Static/AppleII/StaticAnalyser.hpp"
#include "Analyser/Static/AppleIIgs/StaticAnalyser.hpp"
#include "Analyser/Static/Atari2600/StaticAnalyser.hpp"
#include "Analyser/Static/AtariST/StaticAnalyser.hpp"
#include "Analyser/Static/Coleco/StaticAnalyser.hpp"
#include "Analyser/Static/Commodore/StaticAnalyser.hpp"
#include "Analyser/Static/DiskII/StaticAnalyser.hpp"
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
#include "Analyser/Static/FAT12/StaticAnalyser.hpp"
#include "Analyser/Static/Macintosh/StaticAnalyser.hpp"
#include "Analyser/Static/MSX/StaticAnalyser.hpp"
#include "Analyser/Static/Oric/StaticAnalyser.hpp"
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
#include "Analyser/Static/Sega/StaticAnalyser.hpp"
#include "Analyser/Static/ZX8081/StaticAnalyser.hpp"
#include "Analyser/Static/ZXSpectrum/StaticAnalyser.hpp"
#include "Acorn/StaticAnalyser.hpp"
#include "Amiga/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "AppleIIgs/StaticAnalyser.hpp"
#include "Atari2600/StaticAnalyser.hpp"
#include "AtariST/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Enterprise/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "Sega/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
#include "ZXSpectrum/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/2MG.hpp"
#include "Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
#include "Storage/Disk/DiskImage/Formats/AppleDSK.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/FAT12.hpp"
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "Storage/Disk/DiskImage/Formats/PCBooter.hpp"
#include "Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.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/FAT12.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "Storage/MassStorage/Formats/DAT.hpp"
#include "Storage/MassStorage/Formats/DSK.hpp"
#include "Storage/MassStorage/Formats/HDV.hpp"
#include "Storage/MassStorage/Formats/HFV.hpp"
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/DSK.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
#include "Storage/State/SNA.hpp"
#include "Storage/State/SZX.hpp"
#include "Storage/State/Z80.hpp"
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/SZX.hpp"
#include "../../Storage/State/Z80.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/ZXSpectrumTAP.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"
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
// Target Platform Types
#include "Storage/TargetPlatforms.hpp"
template<class> inline constexpr bool always_false_v = false;
#include "../../Storage/TargetPlatforms.hpp"
using namespace Analyser::Static;
using namespace Storage;
namespace {
@@ -105,206 +95,126 @@ std::string get_extension(const std::string &name) {
return extension;
}
class MediaAccumulator {
public:
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
template <typename InstanceT>
void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
media.disks.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
media.tapes.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::Cartridge::Cartridge, InstanceT>) {
media.cartridges.push_back(instance);
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
media.mass_storage_devices.push_back(instance);
} else {
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
}
potential_platforms_ |= platforms;
// Check whether the instance itself has any input on target platforms.
TargetPlatform::Distinguisher *const distinguisher =
dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
if(distinguisher) {
was_distinguished = true;
potential_platforms_ &= distinguisher->target_platforms();
}
}
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
template <typename InstanceT, typename... Args>
void insert(const TargetPlatform::IntType platforms, Args &&... args) {
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
}
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
template <typename InstanceT, typename... Args>
void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
try {
insert<InstanceT>(platforms, std::forward<Args>(args)...);
} catch(...) {}
}
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
/// providing the file name as the only construction argument.
template <typename InstanceT>
void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
if(name_matches(extension)) {
try_insert<InstanceT>(platforms, file_name_);
}
}
bool name_matches(const char *const extension) {
return extension_ == extension;
}
Media media;
bool was_distinguished = false;
private:
const std::string &file_name_;
TargetPlatform::IntType &potential_platforms_;
const std::string extension_;
};
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
MediaAccumulator accumulator(file_name, potential_platforms);
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
potential_platforms |= platforms;\
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
#define Insert(list, class, platforms, ...) \
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
#define TryInsert(list, class, platforms, ...) \
try {\
Insert(list, class, platforms, __VA_ARGS__) \
} catch(...) {}
#define Format(ext, list, class, platforms) \
if(extension == ext) { \
TryInsert(list, class, platforms, file_name) \
}
// 2MG
if(accumulator.name_matches("2mg")) {
if(extension == "2mg") {
// 2MG uses a factory method; defer to it.
try {
const auto media = Disk::Disk2MG::open(file_name);
std::visit([&](auto &&arg) {
using Type = typename std::decay<decltype(arg)>::type;
if constexpr (std::is_same<Type, std::nullptr_t>::value) {
// It's valid for no media to be returned.
} else if constexpr (std::is_same<Type, Disk::DiskImageHolderBase *>::value) {
accumulator.insert(TargetPlatform::DiskII, std::shared_ptr<Disk::DiskImageHolderBase>(arg));
} else if constexpr (std::is_same<Type, MassStorage::MassStorageDevice *>::value) {
// TODO: or is it Apple IIgs?
accumulator.insert(TargetPlatform::AppleII, std::shared_ptr<MassStorage::MassStorageDevice>(arg));
} else {
static_assert(always_false_v<Type>, "Unexpected type encountered.");
}
}, media);
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
} catch(...) {}
}
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "80");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "81");
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format( "dsk",
result.disks,
Disk::DiskImageHolder<Storage::Disk::CPCDSK>,
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format( "ipf",
result.disks,
Disk::DiskImageHolder<Storage::Disk::IPF>,
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO (original Apple II kind)
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Atari2600, "a26");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "dsd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::CPCDSK>>(
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "dsk");
accumulator.try_standard<MassStorage::HFV>(TargetPlatform::Macintosh, "dsk");
accumulator.try_standard<MassStorage::DSK>(TargetPlatform::Macintosh, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "image");
accumulator.try_standard<Disk::DiskImageHolder<Disk::IMD>>(TargetPlatform::PCCompatible, "imd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "img");
// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
if(accumulator.name_matches("img")) {
try {
accumulator.insert<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::FAT12, file_name);
} catch(...) {
accumulator.try_standard<Disk::DiskImageHolder<Disk::PCBooter>>(TargetPlatform::PCCompatible, "img");
}
// PO (Apple IIgs kind)
if(extension == "po") {
TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
}
accumulator.try_standard<Disk::DiskImageHolder<Disk::IPF>>(
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
"ipf");
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "o");
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "po");
if(accumulator.name_matches("po")) {
accumulator.try_insert<Disk::DiskImageHolder<Disk::MacintoshIMG>>(
TargetPlatform::AppleIIgs,
file_name, Disk::MacintoshIMG::FixedType::GCR);
}
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
static constexpr auto PRGTargets = TargetPlatform::Vic20; //Commodore8bit; // Disabled until analysis improves.
if(accumulator.name_matches("prg")) {
// Try instantiating as a ROM; failing that accept as a tape.
// PRG
if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape
try {
accumulator.insert<Cartridge::PRG>(PRGTargets, file_name);
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {
try {
accumulator.insert<Tape::PRG>(PRGTargets, file_name);
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {}
}
}
accumulator.try_standard<Cartridge::BinaryDump>(
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX,
"rom");
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX) // ROM
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sg");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sms");
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "ssd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
#undef Format
#undef Insert
#undef TryInsert
#undef InsertInstance
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
accumulator.try_standard<Tape::TZX>(TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum, "tzx");
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
return accumulator.media;
return result;
}
Media Analyser::Static::GetMedia(const std::string &file_name) {
@@ -313,28 +223,26 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
}
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
const std::string extension = get_extension(file_name);
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
const auto try_snapshot = [&](const char *ext, auto loader) -> bool {
if(extension != ext) {
return false;
}
try {
auto target = loader(file_name);
if(target) {
targets.push_back(std::move(target));
return true;
}
} catch(...) {}
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
return false;
};
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
if(try_snapshot("sna", Storage::State::SNA::load)) return targets;
if(try_snapshot("szx", Storage::State::SZX::load)) return targets;
if(try_snapshot("z80", Storage::State::Z80::load)) return targets;
#undef TryInsert
// Otherwise:
//
@@ -343,45 +251,37 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
int total_options = std::popcount(potential_platforms);
const bool is_confident = total_options == 1;
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
if(!(potential_platforms & platform)) {
return;
}
auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
targets.insert(
targets.end(),
std::make_move_iterator(new_targets.begin()),
std::make_move_iterator(new_targets.end())
);
};
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
}
Append(Acorn);
Append(AmstradCPC);
Append(AppleII);
Append(AppleIIgs);
Append(Amiga);
Append(Atari2600);
Append(AtariST);
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Enterprise);
Append(Macintosh);
Append(MSX);
Append(Oric);
Append(Sega);
Append(ZX8081);
Append(ZXSpectrum);
#undef Append
append(TargetPlatform::Acorn, Acorn::GetTargets);
append(TargetPlatform::AmstradCPC, AmstradCPC::GetTargets);
append(TargetPlatform::AppleII, AppleII::GetTargets);
append(TargetPlatform::AppleIIgs, AppleIIgs::GetTargets);
append(TargetPlatform::Amiga, Amiga::GetTargets);
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
append(TargetPlatform::AtariST, AtariST::GetTargets);
append(TargetPlatform::Coleco, Coleco::GetTargets);
append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
append(TargetPlatform::DiskII, DiskII::GetTargets);
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
append(TargetPlatform::FAT12, FAT12::GetTargets);
append(TargetPlatform::Macintosh, Macintosh::GetTargets);
append(TargetPlatform::MSX, MSX::GetTargets);
append(TargetPlatform::Oric, Oric::GetTargets);
append(TargetPlatform::PCCompatible, PCCompatible::GetTargets);
append(TargetPlatform::Sega, Sega::GetTargets);
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
// Reset any tapes to their initial position.
for(const auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();
}
}
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
// picked their insertion order carefully.

View File

@@ -6,22 +6,23 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef StaticAnalyser_hpp
#define StaticAnalyser_hpp
#include "Analyser/Machines.hpp"
#include "../Machines.hpp"
#include "Storage/Cartridge/Cartridge.hpp"
#include "Storage/Disk/Disk.hpp"
#include "Storage/MassStorage/MassStorageDevice.hpp"
#include "Storage/Tape/Tape.hpp"
#include "Storage/TargetPlatforms.hpp"
#include "Reflection/Struct.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Reflection/Struct.hpp"
#include <memory>
#include <string>
#include <vector>
namespace Analyser::Static {
namespace Analyser {
namespace Static {
struct State;
@@ -39,15 +40,12 @@ struct Media {
}
Media &operator +=(const Media &rhs) {
const auto append = [&](auto &destination, auto &source) {
destination.insert(destination.end(), source.begin(), source.end());
};
append(disks, rhs.disks);
append(tapes, rhs.tapes);
append(cartridges, rhs.cartridges);
append(mass_storage_devices, rhs.mass_storage_devices);
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
append(disks);
append(tapes);
append(cartridges);
append(mass_storage_devices);
#undef append
return *this;
}
};
@@ -58,16 +56,16 @@ struct Media {
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() = default;
virtual ~Target() {}
// This field is entirely optional.
std::unique_ptr<Reflection::Struct> state;
Machine machine;
Media media;
float confidence = 0.5f;
float confidence = 0.0f;
};
using TargetList = std::vector<std::unique_ptr<Target>>;
typedef std::vector<std::unique_ptr<Target>> TargetList;
/*!
Attempts, through any available means, to return a list of potential targets for the file with the given name.
@@ -82,3 +80,6 @@ TargetList GetTargets(const std::string &file_name);
Media GetMedia(const std::string &file_name);
}
}
#endif /* StaticAnalyser_hpp */

View File

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

View File

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

View File

@@ -6,14 +6,17 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#pragma once
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser::Static::ZX8081 {
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemoryModel,
@@ -27,16 +30,18 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
bool ZX80_uses_ZX81_ROM = false;
std::string loading_command;
Target(): Analyser::Static::Target(Machine::ZX8081) {}
private:
friend Reflection::StructImpl<Target>;
void declare_fields() {
DeclareField(memory_model);
DeclareField(is_ZX81);
DeclareField(ZX80_uses_ZX81_ROM);
AnnounceEnum(MemoryModel);
Target(): Analyser::Static::Target(Machine::ZX8081) {
if(needs_declare()) {
DeclareField(memory_model);
DeclareField(is_ZX81);
DeclareField(ZX80_uses_ZX81_ROM);
AnnounceEnum(MemoryModel);
}
}
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

View File

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

View File

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

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