mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-12 02:24:31 +00:00
Compare commits
6 Commits
2024-05-27
...
SoftwareOu
Author | SHA1 | Date | |
---|---|---|---|
|
4078baa424 | ||
|
3179d0d963 | ||
|
6a8c792c63 | ||
|
678e1a38fa | ||
|
f4004baff8 | ||
|
f04e4faae2 |
87
.github/workflows/build.yml
vendored
87
.github/workflows/build.yml
vendored
@@ -1,87 +0,0 @@
|
||||
name: Build
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
build-mac-xcodebuild:
|
||||
name: Mac UI / xcodebuild / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Make
|
||||
working-directory: OSBindings/Mac
|
||||
run: xcodebuild CODE_SIGN_IDENTITY=-
|
||||
build-sdl-cmake:
|
||||
name: SDL UI / cmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
sudo apt-get --allow-releaseinfo-change update
|
||||
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
|
||||
;;
|
||||
macOS)
|
||||
brew install cmake sdl2
|
||||
;;
|
||||
esac
|
||||
- name: Make
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
jobs=$(nproc --all)
|
||||
;;
|
||||
macOS)
|
||||
jobs=$(sysctl -n hw.activecpu)
|
||||
;;
|
||||
*)
|
||||
jobs=1
|
||||
esac
|
||||
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -v -j"$jobs"
|
||||
build-sdl-scons:
|
||||
name: SDL UI / scons / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
sudo apt-get --allow-releaseinfo-change update
|
||||
sudo apt-get --fix-missing install gcc-10 libsdl2-dev scons
|
||||
;;
|
||||
macOS)
|
||||
brew install scons sdl2
|
||||
;;
|
||||
esac
|
||||
- name: Make
|
||||
working-directory: OSBindings/SDL
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
jobs=$(nproc --all)
|
||||
;;
|
||||
macOS)
|
||||
jobs=$(sysctl -n hw.activecpu)
|
||||
;;
|
||||
*)
|
||||
jobs=1
|
||||
esac
|
||||
scons -j"$jobs"
|
16
.github/workflows/ccpp.yml
vendored
Normal file
16
.github/workflows/ccpp.yml
vendored
Normal 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)
|
@@ -6,9 +6,9 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ActivityObserver_h
|
||||
#define ActivityObserver_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Activity {
|
||||
@@ -23,8 +23,6 @@ namespace Activity {
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
@@ -57,3 +55,5 @@ class Observer {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ActivityObserver_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ActivitySource_h
|
||||
#define ActivitySource_h
|
||||
|
||||
#include "Observer.hpp"
|
||||
|
||||
@@ -18,3 +19,6 @@ class Source {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* ActivitySource_h */
|
||||
|
@@ -6,11 +6,13 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ConfidenceCounter_hpp
|
||||
#define ConfidenceCounter_hpp
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a confidence source that calculates its probability by virtual of a history of events.
|
||||
@@ -40,3 +42,6 @@ class ConfidenceCounter: public ConfidenceSource {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceCounter_hpp */
|
||||
|
@@ -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 */
|
||||
|
@@ -6,13 +6,15 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ConfidenceSummary_hpp
|
||||
#define ConfidenceSummary_hpp
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
@@ -39,3 +41,6 @@ class ConfidenceSummary: public ConfidenceSource {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSummary_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiConfigurable_hpp
|
||||
#define MultiConfigurable_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Configurable/Configurable.hpp"
|
||||
@@ -14,7 +15,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configurable interface to multiple machines.
|
||||
@@ -35,3 +37,6 @@ class MultiConfigurable: public Configurable::Device {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurable_hpp */
|
||||
|
@@ -6,14 +6,16 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiJoystickMachine_hpp
|
||||
#define MultiJoystickMachine_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the joystick machine interface to multiple machines.
|
||||
@@ -33,3 +35,6 @@ class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiJoystickMachine_hpp */
|
||||
|
@@ -56,10 +56,10 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTy
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
|
||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||
bool was_consumed = false;
|
||||
for(const auto &machine: machines_) {
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
|
||||
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
|
||||
}
|
||||
return was_consumed;
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiKeyboardMachine_hpp
|
||||
#define MultiKeyboardMachine_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Machines/KeyboardMachine.hpp"
|
||||
@@ -14,7 +15,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the keyboard machine interface to multiple machines.
|
||||
@@ -30,7 +32,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
public:
|
||||
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final;
|
||||
void reset_all_keys() final;
|
||||
const std::set<Key> &observed_keys() const final;
|
||||
bool is_exclusive() const final;
|
||||
@@ -54,3 +56,6 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiKeyboardMachine_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiMediaTarget_hpp
|
||||
#define MultiMediaTarget_hpp
|
||||
|
||||
#include "../../../../Machines/MediaTarget.hpp"
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
@@ -14,7 +15,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the media target interface to multiple machines.
|
||||
@@ -34,3 +36,6 @@ struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMediaTarget_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiProducer_hpp
|
||||
#define MultiProducer_hpp
|
||||
|
||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../../Machines/MachineTypes.hpp"
|
||||
@@ -18,7 +19,8 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
@@ -45,7 +47,7 @@ template <typename MachineType> class MultiInterface {
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
@@ -114,3 +116,7 @@ class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, pu
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* MultiProducer_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiSpeaker_hpp
|
||||
#define MultiSpeaker_hpp
|
||||
|
||||
#include "../../../../Machines/DynamicMachine.hpp"
|
||||
#include "../../../../Outputs/Speaker/Speaker.hpp"
|
||||
@@ -15,7 +16,8 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
|
||||
@@ -54,3 +56,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiSpeaker_hpp */
|
||||
|
@@ -11,12 +11,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::MultiMachine> logger;
|
||||
|
||||
}
|
||||
|
||||
using namespace Analyser::Dynamic;
|
||||
|
||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
|
||||
@@ -67,14 +61,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
|
||||
|
||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
|
||||
std::lock_guard machines_lock(machines_mutex_);
|
||||
|
||||
if constexpr (logger.enabled) {
|
||||
auto line = logger.info();
|
||||
for(const auto &machine: machines_) {
|
||||
auto timed_machine = machine->timed_machine();
|
||||
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
for(const auto &machine: machines_) {
|
||||
auto timed_machine = machine->timed_machine();
|
||||
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
|
||||
}
|
||||
LOGNBR(std::endl);
|
||||
#endif
|
||||
|
||||
DynamicMachine *front = machines_.front().get();
|
||||
std::stable_sort(machines_.begin(), machines_.end(),
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef MultiMachine_hpp
|
||||
#define MultiMachine_hpp
|
||||
|
||||
#include "../../../Machines/DynamicMachine.hpp"
|
||||
|
||||
@@ -21,7 +22,8 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Dynamic {
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
|
||||
/*!
|
||||
Provides the same interface as to a single machine, while multiplexing all
|
||||
@@ -79,3 +81,6 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMachine_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Machines_h
|
||||
#define Machines_h
|
||||
|
||||
namespace Analyser {
|
||||
|
||||
@@ -17,7 +18,6 @@ enum class Machine {
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
Archimedes,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
@@ -25,10 +25,11 @@ enum class Machine {
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
PCCompatible,
|
||||
Vic20,
|
||||
ZX8081,
|
||||
ZXSpectrum,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_h */
|
||||
|
@@ -13,17 +13,16 @@
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Single, disk);
|
||||
Storage::Encodings::MFM::Parser parser(false, disk);
|
||||
|
||||
const Storage::Encodings::MFM::Sector *const names = parser.sector(0, 0, 0);
|
||||
const Storage::Encodings::MFM::Sector *const details = parser.sector(0, 0, 1);
|
||||
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
|
||||
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
|
||||
|
||||
if(!names || !details) return nullptr;
|
||||
if(names->samples.empty() || details->samples.empty()) return nullptr;
|
||||
@@ -66,7 +65,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
uint8_t track = uint8_t(start_sector / 10);
|
||||
start_sector++;
|
||||
|
||||
const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
|
||||
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
|
||||
if(!next_sector) break;
|
||||
|
||||
long length_from_sector = std::min(data_length, 256l);
|
||||
@@ -85,58 +84,36 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
*/
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
// Grab the second half of the free-space map because it has the boot option in it.
|
||||
const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
|
||||
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
catalogue->has_large_sectors = free_space_map_second_half->samples[0].size() == 1024;
|
||||
|
||||
// Possibility: this is a large-sector disk with an old-style free space map. In which
|
||||
// case the above just read the start of the root directory.
|
||||
uint8_t first_directory_sector = 2;
|
||||
if(catalogue->has_large_sectors && !memcmp(&free_space_map_second_half->samples[0][1], "Hugo", 4)) {
|
||||
free_space_map_second_half = parser.sector(0, 0, 0);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
first_directory_sector = 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(catalogue->has_large_sectors ? 2*1024 : 5*256);
|
||||
|
||||
for(uint8_t c = first_directory_sector; c < first_directory_sector + (catalogue->has_large_sectors ? 2 : 5); c++) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
||||
}
|
||||
|
||||
// Check for end of directory marker.
|
||||
if(root_directory[catalogue->has_large_sectors ? 0x7d7 : 0x4cb]) return nullptr;
|
||||
// Quick sanity checks.
|
||||
if(root_directory[0x4cb]) return nullptr;
|
||||
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
|
||||
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
|
||||
|
||||
// Check for both directory identifiers.
|
||||
const uint8_t *const start_id = &root_directory[1];
|
||||
const uint8_t *const end_id = &root_directory[root_directory.size() - 5];
|
||||
catalogue->is_hugo = !memcmp(start_id, "Hugo", 4) && !memcmp(end_id, "Hugo", 4);
|
||||
const bool is_nick = !memcmp(start_id, "Nick", 4) && !memcmp(end_id, "Nick", 4);
|
||||
if(!catalogue->is_hugo && !is_nick) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!catalogue->has_large_sectors) {
|
||||
// TODO: I don't know where the boot option rests with large sectors.
|
||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < (catalogue->has_large_sectors ? 0x7d7 : 0x4cb); file_offset += 0x1a) {
|
||||
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11]{};
|
||||
char name[11];
|
||||
std::size_t c = 0;
|
||||
for(; c < 10; c++) {
|
||||
const char next = root_directory[file_offset + c] & 0x7f;
|
||||
@@ -145,9 +122,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
}
|
||||
name[c] = '\0';
|
||||
|
||||
// An empty name implies the directory has ended; files are always listed in case-insensitive
|
||||
// sorted order, with that list being terminated by a '\0'.
|
||||
if(name[0] == '\0') break;
|
||||
// Skip if the name is empty.
|
||||
if(name[0] == '\0') continue;
|
||||
|
||||
// Populate a file then.
|
||||
File new_file;
|
||||
@@ -190,7 +166,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
@@ -201,20 +177,5 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
// Include the directory title.
|
||||
const char *title, *name;
|
||||
if(catalogue->has_large_sectors) {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x7dd]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x7f0]);
|
||||
} else {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x4d9]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x4cc]);
|
||||
}
|
||||
|
||||
catalogue->name = std::string(title, strnlen(title, 19));
|
||||
if(catalogue->name.empty() || catalogue->name == "$") {
|
||||
catalogue->name = std::string(name, strnlen(name, 10));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
@@ -6,17 +6,18 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Acorn_Disk_hpp
|
||||
#define StaticAnalyser_Acorn_Disk_hpp
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||
struct Catalogue {
|
||||
bool is_hugo = false;
|
||||
bool has_large_sectors = false;
|
||||
std::string name;
|
||||
std::vector<File> files;
|
||||
enum class BootOption {
|
||||
@@ -31,3 +32,7 @@ std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Acorn_File_hpp
|
||||
#define StaticAnalyser_Acorn_File_hpp
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -58,3 +61,7 @@ struct File {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@@ -12,10 +12,7 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Numeric/StringSimilarity.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
@@ -62,14 +59,13 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// If there are any tapes, attempt to get data from the first.
|
||||
// if there are any tapes, attempt to get data from the first
|
||||
if(!media.tapes.empty()) {
|
||||
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
|
||||
std::vector<File> files = GetFiles(tape);
|
||||
@@ -98,34 +94,30 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
|
||||
target8bit->media.tapes = media.tapes;
|
||||
target->media.tapes = media.tapes;
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
|
||||
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
|
||||
|
||||
// Get any sort of catalogue that can be found.
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
|
||||
// 8-bit options: DFS and Hugo-style ADFS.
|
||||
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
|
||||
if(dfs_catalogue || adfs_catalogue) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
target8bit->media.disks = media.disks;
|
||||
target8bit->has_dfs = bool(dfs_catalogue);
|
||||
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||
target->media.disks = media.disks;
|
||||
target->has_dfs = bool(dfs_catalogue);
|
||||
target->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target8bit->should_shift_restart = true;
|
||||
target->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
|
||||
// Check whether adding the AP6 ROM is justified.
|
||||
@@ -141,79 +133,39 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
targetArchimedes->media.disks = media.disks;
|
||||
|
||||
// Also look for the best possible startup program name, if it can be discerned.
|
||||
std::multimap<double, std::string, std::greater<double>> options;
|
||||
for(const auto &file: adfs_catalogue->files) {
|
||||
// Skip non-Pling files.
|
||||
if(file.name[0] != '!') continue;
|
||||
|
||||
// Take whatever else comes with a preference for things that don't
|
||||
// have 'boot' or 'read' in them (the latter of which will tend to be
|
||||
// read_me or read_this or similar).
|
||||
constexpr char read[] = "read";
|
||||
constexpr char boot[] = "boot";
|
||||
const auto has = [&](const char *begin, const char *end) {
|
||||
return std::search(
|
||||
file.name.begin(), file.name.end(),
|
||||
begin, end - 1, // i.e. don't compare the trailing NULL.
|
||||
[](char lhs, char rhs) {
|
||||
return std::tolower(lhs) == rhs;
|
||||
}
|
||||
) != file.name.end();
|
||||
};
|
||||
const auto has_read = has(std::begin(read), std::end(read));
|
||||
const auto has_boot = has(std::begin(boot), std::end(boot));
|
||||
|
||||
const auto probability =
|
||||
Numeric::similarity(file.name, adfs_catalogue->name) +
|
||||
Numeric::similarity(file.name, file_name) -
|
||||
((has_read || has_boot) ? 0.2 : 0.0);
|
||||
options.emplace(probability, file.name);
|
||||
}
|
||||
|
||||
if(!options.empty()) {
|
||||
targetArchimedes->main_program = options.begin()->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target8bit->has_acorn_adfs = true;
|
||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
|
||||
target8bit->media.mass_storage_devices = media.mass_storage_devices;
|
||||
target->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
|
||||
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target8bit->should_shift_restart = true;
|
||||
target->should_shift_restart = true;
|
||||
} else {
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
if(!target8bit->media.empty()) {
|
||||
targets.push_back(std::move(target8bit));
|
||||
}
|
||||
if(!targetArchimedes->media.empty()) {
|
||||
targets.push_back(std::move(targetArchimedes));
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::move(target));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AcornAnalyser_hpp */
|
||||
|
@@ -19,10 +19,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
auto new_chunk = std::make_unique<File::Chunk>();
|
||||
int shift_register = 0;
|
||||
|
||||
// TODO: move this into the parser
|
||||
const auto shift = [&] {
|
||||
shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
|
||||
};
|
||||
// TODO: move this into the parser
|
||||
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
|
||||
|
||||
// find next area of high tone
|
||||
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
|
||||
@@ -34,6 +32,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
|
||||
shift();
|
||||
}
|
||||
|
||||
#undef shift
|
||||
|
||||
parser.reset_crc();
|
||||
parser.reset_error_flag();
|
||||
|
||||
|
@@ -6,15 +6,22 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Acorn_Tape_hpp
|
||||
#define StaticAnalyser_Acorn_Tape_hpp
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -6,15 +6,18 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Acorn_Target_h
|
||||
#define Analyser_Static_Acorn_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_acorn_adfs = false;
|
||||
bool has_pres_adfs = false;
|
||||
bool has_dfs = false;
|
||||
@@ -23,7 +26,7 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
@@ -34,10 +37,8 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
|
||||
}
|
||||
};
|
||||
|
||||
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
|
||||
std::string main_program;
|
||||
|
||||
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Acorn_Target_h */
|
||||
|
@@ -6,14 +6,22 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
|
||||
|
@@ -6,12 +6,15 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Amiga_Target_h
|
||||
#define Analyser_Static_Amiga_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::Amiga {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ChipRAM,
|
||||
@@ -39,3 +42,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Amiga_Target_h */
|
||||
|
@@ -159,8 +159,8 @@ void InspectCatalogue(
|
||||
}
|
||||
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
|
||||
// this disk was formatted and the filler byte never replaced.
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AmstradCPC_Target_h
|
||||
#define Analyser_Static_AmstradCPC_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
|
||||
@@ -29,3 +32,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_Target_h */
|
||||
|
@@ -13,17 +13,8 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
// If any disks are present, attach a Disk II.
|
||||
if(!target->media.disks.empty()) {
|
||||
if(!target->media.disks.empty())
|
||||
target->disk_controller = Target::DiskController::SixteenSector;
|
||||
}
|
||||
|
||||
// The emulated SCSI card requires a IIe, so upgrade to that if
|
||||
// any mass storage is present.
|
||||
if(!target->media.mass_storage_devices.empty()) {
|
||||
target->model = Target::Model::EnhancedIIe;
|
||||
target->scsi_controller = Target::SCSIController::AppleSCSI;
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
targets.push_back(std::move(target));
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AppleII_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AppleII_Target_h
|
||||
#define Analyser_Static_AppleII_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::AppleII {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -26,32 +29,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
SixteenSector,
|
||||
ThirteenSector
|
||||
);
|
||||
ReflectableEnum(SCSIController,
|
||||
None,
|
||||
AppleSCSI
|
||||
);
|
||||
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
SCSIController scsi_controller = SCSIController::None;
|
||||
bool has_mockingboard = true;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
DeclareField(has_mockingboard);
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_iie(Target::Model model) {
|
||||
return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif /* Analyser_Static_AppleII_Target_h */
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AppleIIgs_Target_h
|
||||
#define Analyser_Static_AppleIIgs_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -40,3 +43,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_Target_h */
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Atari_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,11 +6,14 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Atari2600_Target_h
|
||||
#define Analyser_Static_Atari2600_Target_h
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class PagingModel {
|
||||
@@ -36,3 +39,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Atari_Target_h */
|
||||
|
@@ -6,14 +6,22 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
|
||||
#define Analyser_Static_AtariST_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */
|
||||
|
@@ -6,26 +6,22 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_AtariST_Target_h
|
||||
#define Analyser_Static_AtariST_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::AtariST {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemorySize,
|
||||
FiveHundredAndTwelveKilobytes,
|
||||
OneMegabyte,
|
||||
FourMegabytes);
|
||||
MemorySize memory_size = MemorySize::OneMegabyte;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
}
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AtariST_Target_h */
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Coleco {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -37,7 +37,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
|
||||
@returns a sector if one was found; @c nullptr otherwise.
|
||||
*/
|
||||
std::shared_ptr<Sector> sector(uint8_t track, uint8_t sector) {
|
||||
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
|
||||
int difference = int(track) - int(track_);
|
||||
track_ = track;
|
||||
|
||||
@@ -182,7 +182,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
uint8_t next_track = 18;
|
||||
uint8_t next_sector = 1;
|
||||
while(1) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
sector = parser.get_sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
|
||||
next_track = sector->data[0];
|
||||
@@ -221,7 +221,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
sector = parser.get_sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
|
||||
next_track = sector->data[0];
|
||||
|
@@ -6,15 +6,22 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
|
||||
#define StaticAnalyser_Commodore_Disk_hpp
|
||||
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@@ -6,13 +6,15 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef File_hpp
|
||||
#define File_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
struct File {
|
||||
std::wstring name;
|
||||
@@ -34,3 +36,7 @@ struct File {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@@ -93,7 +93,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address) {
|
||||
default:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append("Unrecognised loading address for Commodore program: %04x", files.front().starting_address);
|
||||
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
|
||||
[[fallthrough]];
|
||||
case 0x1001:
|
||||
memory_model = Target::MemoryModel::Unexpanded;
|
||||
@@ -188,8 +188,8 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
|
||||
|
||||
// Unhandled:
|
||||
//
|
||||
// M6: this is a C64 file.
|
||||
// MV: this is a Vic-20 file.
|
||||
// M6: this is a C64 file.
|
||||
// MV: this is a Vic-20 file.
|
||||
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
|
||||
// RO: this disk image should be treated as read-only.
|
||||
}
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CommodoreAnalyser_hpp */
|
||||
|
@@ -6,13 +6,20 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
|
||||
#define StaticAnalyser_Commodore_Tape_hpp
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Commodore_Target_h
|
||||
#define Analyser_Static_Commodore_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Commodore {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class MemoryModel {
|
||||
@@ -69,3 +72,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Commodore_Target_h */
|
||||
|
@@ -11,7 +11,7 @@
|
||||
#include "Kernel.hpp"
|
||||
|
||||
using namespace Analyser::Static::MOS6502;
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
@@ -236,7 +236,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
case Instruction::Relative: {
|
||||
std::size_t operand_address = address_mapper(address);
|
||||
if(operand_address >= memory.size()) return;
|
||||
++address;
|
||||
address++;
|
||||
|
||||
instruction.operand = memory[operand_address];
|
||||
}
|
||||
@@ -291,34 +291,20 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
|
||||
// Decide on overall flow control.
|
||||
|
||||
// All relative instructions are flow control.
|
||||
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
|
||||
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
|
||||
if(instruction.operation == Instruction::JSR) {
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
}
|
||||
if(instruction.operation == Instruction::JMP) {
|
||||
if(instruction.addressing_mode == Instruction::Absolute)
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
return;
|
||||
}
|
||||
if(instruction.addressing_mode == Instruction::Relative) {
|
||||
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
|
||||
disassembly.remaining_entry_points.push_back(destination);
|
||||
}
|
||||
|
||||
switch(instruction.operation) {
|
||||
default: break;
|
||||
|
||||
case Instruction::KIL:
|
||||
case Instruction::RTS:
|
||||
case Instruction::RTI:
|
||||
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
|
||||
disassembly.implicit_entry_points.push_back(address);
|
||||
return;
|
||||
|
||||
case Instruction::JMP:
|
||||
// Adding a new entry point for relative jumps was handled above.
|
||||
if(instruction.addressing_mode == Instruction::Absolute) {
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
}
|
||||
return;
|
||||
|
||||
case Instruction::JSR:
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,5 +316,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -6,11 +6,14 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef AddressMapper_hpp
|
||||
#define AddressMapper_hpp
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Analyser::Static::Disassembler {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembler {
|
||||
|
||||
/*!
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
@@ -23,3 +26,7 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AddressMapper_hpp */
|
||||
|
@@ -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 */
|
||||
|
@@ -11,7 +11,7 @@
|
||||
#include "Kernel.hpp"
|
||||
|
||||
using namespace Analyser::Static::Z80;
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
|
||||
|
||||
@@ -56,11 +56,11 @@ class Accessor {
|
||||
bool overrun_ = false;
|
||||
};
|
||||
|
||||
constexpr uint8_t x(uint8_t v) { return v >> 6; }
|
||||
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
|
||||
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
|
||||
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
|
||||
constexpr uint8_t z(uint8_t v) { return v & 7; }
|
||||
#define x(v) (v >> 6)
|
||||
#define y(v) ((v >> 3) & 7)
|
||||
#define q(v) ((v >> 3) & 1)
|
||||
#define p(v) ((v >> 4) & 3)
|
||||
#define z(v) (v & 7)
|
||||
|
||||
Instruction::Condition condition_table[] = {
|
||||
Instruction::Condition::NZ, Instruction::Condition::Z,
|
||||
@@ -589,7 +589,7 @@ struct Z80Disassembler {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add any (potentially) newly-discovered entry point.
|
||||
// Add any (potentially) newly discovered entry point.
|
||||
if( instruction.operation == Instruction::Operation::JP ||
|
||||
instruction.operation == Instruction::Operation::JR ||
|
||||
instruction.operation == Instruction::Operation::CALL ||
|
||||
@@ -598,37 +598,22 @@ struct Z80Disassembler {
|
||||
}
|
||||
|
||||
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
|
||||
switch(instruction.operation) {
|
||||
default: break;
|
||||
if(instruction.condition != Instruction::Condition::None) continue;
|
||||
|
||||
case Instruction::Operation::RET:
|
||||
case Instruction::Operation::RETI:
|
||||
case Instruction::Operation::RETN:
|
||||
case Instruction::Operation::JP:
|
||||
case Instruction::Operation::JR:
|
||||
if(instruction.condition == Instruction::Condition::None) {
|
||||
disassembly.implicit_entry_points.push_back(accessor.address());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(instruction.operation == Instruction::Operation::RET) return;
|
||||
if(instruction.operation == Instruction::Operation::RETI) return;
|
||||
if(instruction.operation == Instruction::Operation::RETN) return;
|
||||
if(instruction.operation == Instruction::Operation::JP) return;
|
||||
if(instruction.operation == Instruction::Operation::JR) return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
|
||||
|
||||
Disassembly Analyser::Static::Z80::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points,
|
||||
Approach approach)
|
||||
{
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
|
||||
memory,
|
||||
address_mapper,
|
||||
entry_points,
|
||||
approach == Approach::Exhaustive
|
||||
);
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
|
||||
}
|
||||
|
@@ -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 */
|
||||
|
@@ -6,14 +6,22 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||
#define Analyser_Static_DiskII_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::DiskII {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
|
||||
|
@@ -40,8 +40,6 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi
|
||||
target->basic_version = Target::BASICVersion::Any;
|
||||
|
||||
// Inspect any supplied disks.
|
||||
//
|
||||
// TODO: how best can these be discerned from MS-DOS and MSX disks?
|
||||
if(!media.disks.empty()) {
|
||||
// DOS will be needed.
|
||||
target->dos = Target::DOS::EXDOS;
|
||||
|
@@ -6,14 +6,22 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Enterprise {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Enterprise_Target_h
|
||||
#define Analyser_Static_Enterprise_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
@@ -14,7 +15,9 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Enterprise {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
@@ -48,3 +51,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
|
@@ -1,100 +0,0 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/12/2023.
|
||||
// Copyright 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include "../Enterprise/StaticAnalyser.hpp"
|
||||
#include "../PCCompatible/StaticAnalyser.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/SegmentParser.hpp"
|
||||
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType platforms) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
auto &disk = media.disks.front();
|
||||
TargetList targets;
|
||||
|
||||
// Total list of potential platforms is:
|
||||
//
|
||||
// * the Enterprise (and, by extension, CP/M-targetted software);
|
||||
// * the Atari ST;
|
||||
// * the MSX (ditto on CP/M); and
|
||||
// * the PC.
|
||||
//
|
||||
// (though the MSX and Atari ST don't currently call in here for now)
|
||||
|
||||
// If the disk image is very small or large, map it to the PC. That's the only option old enough
|
||||
// to have used 5.25" media.
|
||||
if(disk->get_maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
}
|
||||
|
||||
// Attempt to grab MFM track 0, sector 1: the boot sector.
|
||||
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
|
||||
const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(
|
||||
*track_zero,
|
||||
Storage::Encodings::MFM::MFMBitLength
|
||||
), Storage::Encodings::MFM::Density::Double);
|
||||
|
||||
// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
|
||||
if(sector_map.empty() || sector_map.size() > 10) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
}
|
||||
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
for(const auto &pair: sector_map) {
|
||||
if(pair.second.address.sector == 1) {
|
||||
boot_sector = &pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This shouldn't technically be possible since the disk has been identified as FAT12, but be safe.
|
||||
if(!boot_sector) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Check for key phrases that imply a PC disk.
|
||||
const auto &sample = boot_sector->samples[0];
|
||||
const std::vector<std::string> pc_strings = {
|
||||
// MS-DOS strings.
|
||||
"MSDOS",
|
||||
"Non-System disk or disk error",
|
||||
// DOS Plus strings.
|
||||
"Insert a SYSTEM disk",
|
||||
};
|
||||
for(const auto &string: pc_strings) {
|
||||
if(
|
||||
std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
|
||||
) {
|
||||
return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: look for a COM, EXE or BAT, inspect. AUTOEXEC.BAT and/or CONFIG.SYS could be either PC or MSX.
|
||||
// Disassembling the boot sector doesn't necessarily work, as several Enterprise titles out there in the wild seem
|
||||
// to have been created by WINIMAGE which adds an x86 PC-style boot sector.
|
||||
|
||||
// Enterprise notes: EXOS files all start with a 16-byte header which should begin with a 0 and then have a type
|
||||
// byte that will be 0xa or lower; cf http://epbas.lgb.hu/readme.html
|
||||
//
|
||||
// Some disks commonly passed around as Enterprise software are actually CP/M software, expecting IS-DOS (the CP/M
|
||||
// clone) to be present. It's certainly possible the same could be true of MSX disks and MSX-DOS. So analysing COM
|
||||
// files probably means searching for CALL 5s and/or INT 21hs, if not a more rigorous disassembly.
|
||||
//
|
||||
// I have not been able to locate a copy of IS-DOS so there's probably not much that can be done here; perhaps I
|
||||
// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
|
||||
|
||||
// Being unable to prove that this is a PC disk, throw it to the Enterprise.
|
||||
return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms);
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/12/2023.
|
||||
// Copyright 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::FAT12 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
@@ -6,11 +6,14 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Cartridge_hpp
|
||||
#define Cartridge_hpp
|
||||
|
||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
/*!
|
||||
Extends the base cartridge class by adding a (guess at) the banking scheme.
|
||||
@@ -31,3 +34,7 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Cartridge_hpp */
|
||||
|
@@ -37,11 +37,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
auto target = std::make_unique<Analyser::Static::MSX::Target>();
|
||||
target->confidence = confidence;
|
||||
|
||||
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
|
||||
if(segment.data.size() < 48*1024) {
|
||||
target->model = Analyser::Static::MSX::Target::Model::MSX1;
|
||||
}
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
} else {
|
||||
@@ -105,7 +100,6 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
// Bonus observation: all such ROMs are from the MSX 1 era.
|
||||
if(data_size <= 0xc000) {
|
||||
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||
continue;
|
||||
@@ -115,16 +109,97 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// be at play; disassemble to try to figure it out.
|
||||
std::vector<uint8_t> first_8k;
|
||||
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
|
||||
const Analyser::Static::Z80::Disassembly disassembly =
|
||||
Analyser::Static::Z80::Disassembly disassembly =
|
||||
Analyser::Static::Z80::Disassemble(
|
||||
first_8k,
|
||||
Analyser::Static::Disassembler::OffsetMapper(start_address),
|
||||
{ init_address },
|
||||
Analyser::Static::Z80::Approach::Exhaustive
|
||||
{ init_address }
|
||||
);
|
||||
|
||||
// // Look for a indirect store followed by an unconditional JP or CALL into another
|
||||
// // segment, that's a fairly explicit sign where found.
|
||||
using Instruction = Analyser::Static::Z80::Instruction;
|
||||
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||
bool is_ascii = false;
|
||||
// auto iterator = instructions.begin();
|
||||
// while(iterator != instructions.end()) {
|
||||
// auto next_iterator = iterator;
|
||||
// next_iterator++;
|
||||
// if(next_iterator == instructions.end()) break;
|
||||
//
|
||||
// if( iterator->second.operation == Instruction::Operation::LD &&
|
||||
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
|
||||
// (
|
||||
// iterator->second.operand == 0x5000 ||
|
||||
// iterator->second.operand == 0x6000 ||
|
||||
// iterator->second.operand == 0x6800 ||
|
||||
// iterator->second.operand == 0x7000 ||
|
||||
// iterator->second.operand == 0x77ff ||
|
||||
// iterator->second.operand == 0x7800 ||
|
||||
// iterator->second.operand == 0x8000 ||
|
||||
// iterator->second.operand == 0x9000 ||
|
||||
// iterator->second.operand == 0xa000
|
||||
// ) &&
|
||||
// (
|
||||
// next_iterator->second.operation == Instruction::Operation::CALL ||
|
||||
// next_iterator->second.operation == Instruction::Operation::JP
|
||||
// ) &&
|
||||
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
|
||||
// ) {
|
||||
// const uint16_t address = uint16_t(next_iterator->second.operand);
|
||||
// switch(iterator->second.operand) {
|
||||
// case 0x6000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x6800:
|
||||
// if(address >= 0x6000 && address < 0x6800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// is_ascii = true;
|
||||
// }
|
||||
// break;
|
||||
// case 0x77ff:
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7800:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x8000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x9000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0xa000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
|
||||
// }
|
||||
// break;
|
||||
// case 0xb000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// iterator = next_iterator;
|
||||
|
||||
// Look for LD (nnnn), A instructions, and collate those addresses.
|
||||
std::map<uint16_t, int> address_counts;
|
||||
@@ -136,46 +211,49 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
}
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
// Weight confidences by number of observed hits.
|
||||
float total_hits =
|
||||
float(
|
||||
address_counts[0x6000] + address_counts[0x6800] +
|
||||
address_counts[0x7000] + address_counts[0x7800] +
|
||||
address_counts[0x77ff] + address_counts[0x8000] +
|
||||
address_counts[0xa000] + address_counts[0x5000] +
|
||||
address_counts[0x9000] + address_counts[0xb000]
|
||||
);
|
||||
|
||||
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
|
||||
|
||||
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
|
||||
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
|
||||
const bool is_konami = (konami_total * 5) / (total_hits * 3);
|
||||
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
|
||||
|
||||
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
float(ascii_8kb_total) / float(total_hits)));
|
||||
}
|
||||
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
float(ascii_16kb_total) / float(total_hits)));
|
||||
}
|
||||
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x6800] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x7800]) / total_hits));
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x77ff]) / total_hits));
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::Konami,
|
||||
float(konami_total) / float(total_hits)));
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x8000] +
|
||||
address_counts[0xa000]) / total_hits));
|
||||
}
|
||||
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
|
||||
if(!is_ascii) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||
float(konami_with_scc_total) / float(total_hits)));
|
||||
float( address_counts[0x5000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x9000] +
|
||||
address_counts[0xb000]) / total_hits));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_MSX_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_MSX_Tape_hpp
|
||||
#define StaticAnalyser_MSX_Tape_hpp
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -35,3 +38,7 @@ struct File {
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_Tape_hpp */
|
||||
|
@@ -6,26 +6,22 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_MSX_Target_h
|
||||
#define Analyser_Static_MSX_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::MSX {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_disk_drive = false;
|
||||
bool has_msx_music = true;
|
||||
std::string loading_command;
|
||||
|
||||
ReflectableEnum(Model,
|
||||
MSX1,
|
||||
MSX2
|
||||
);
|
||||
Model model = Model::MSX2;
|
||||
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
@@ -36,13 +32,14 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_MSX_Target_h */
|
||||
|
@@ -6,14 +6,22 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Macintosh_Target_h
|
||||
#define Analyser_Static_Macintosh_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::Macintosh {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
@@ -28,3 +31,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_Target_h */
|
||||
|
@@ -85,7 +85,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
|
||||
/*
|
||||
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
|
||||
*/
|
||||
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 2);
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
|
||||
if(!sector) return false;
|
||||
if(sector->samples.empty()) return false;
|
||||
|
||||
@@ -108,7 +108,7 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start
|
||||
use disassembly to test for likely matches.
|
||||
*/
|
||||
|
||||
const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 1);
|
||||
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
|
||||
if(!sector) return false;
|
||||
if(sector->samples.empty()) return false;
|
||||
|
||||
@@ -175,7 +175,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
|
||||
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
|
||||
// Jasmin and BD-DOS formats here.
|
||||
for(auto &disk: media.disks) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
if(is_microdisc(parser)) {
|
||||
target->disk_interface = Target::DiskInterface::Microdisc;
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Oric_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Oric_Tape_hpp
|
||||
#define StaticAnalyser_Oric_Tape_hpp
|
||||
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -31,3 +34,7 @@ struct File {
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Oric_Target_h
|
||||
#define Analyser_Static_Oric_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Oric {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ROM,
|
||||
@@ -54,3 +57,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Oric_Target_h */
|
||||
|
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/10/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// No analysis is applied yet.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
auto *const target = new Target();
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/11/2019.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/11/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::PCCompatible {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(VideoAdaptor,
|
||||
MDA,
|
||||
CGA);
|
||||
VideoAdaptor adaptor = VideoAdaptor::CGA;
|
||||
|
||||
ReflectableEnum(Speed,
|
||||
ApproximatelyOriginal,
|
||||
Fast);
|
||||
Speed speed = Speed::Fast;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(VideoAdaptor);
|
||||
AnnounceEnum(Speed);
|
||||
DeclareField(adaptor);
|
||||
DeclareField(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@@ -6,14 +6,21 @@
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp
|
||||
#define StaticAnalyser_Sega_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_Sega_Target_h
|
||||
#define Analyser_Static_Sega_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::Sega {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class Model {
|
||||
@@ -45,8 +48,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Sega_Target_h */
|
||||
|
@@ -9,7 +9,6 @@
|
||||
#include "StaticAnalyser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
@@ -26,11 +25,9 @@
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Enterprise/StaticAnalyser.hpp"
|
||||
#include "FAT12/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "PCCompatible/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
#include "ZX8081/StaticAnalyser.hpp"
|
||||
#include "ZXSpectrum/StaticAnalyser.hpp"
|
||||
@@ -51,20 +48,18 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IMD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/PCBooter.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
@@ -86,10 +81,7 @@
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
|
||||
template<class> inline constexpr bool always_false_v = false;
|
||||
|
||||
using namespace Analyser::Static;
|
||||
using namespace Storage;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -103,199 +95,126 @@ std::string get_extension(const std::string &name) {
|
||||
return extension;
|
||||
}
|
||||
|
||||
class MediaAccumulator {
|
||||
public:
|
||||
MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
|
||||
file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
|
||||
|
||||
/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
|
||||
/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
|
||||
template <typename InstanceT>
|
||||
void insert(TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
|
||||
if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
|
||||
media.disks.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
|
||||
media.tapes.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::Cartridge::Cartridge, InstanceT>) {
|
||||
media.cartridges.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
|
||||
media.mass_storage_devices.push_back(instance);
|
||||
} else {
|
||||
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
|
||||
}
|
||||
|
||||
potential_platforms_ |= platforms;
|
||||
|
||||
// Check whether the instance itself has any input on target platforms.
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher =
|
||||
dynamic_cast<TargetPlatform::TypeDistinguisher *>(instance.get());
|
||||
if(distinguisher) potential_platforms_ &= distinguisher->target_platform_type();
|
||||
}
|
||||
|
||||
/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
|
||||
template <typename InstanceT, typename... Args>
|
||||
void try_insert(TargetPlatform::IntType platforms, Args &&... args) {
|
||||
try {
|
||||
insert<InstanceT>(platforms, std::forward<Args>(args)...);
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
|
||||
/// providing the file name as the only construction argument.
|
||||
template <typename InstanceT>
|
||||
void try_standard(TargetPlatform::IntType platforms, const char *extension) {
|
||||
if(name_matches(extension)) {
|
||||
try_insert<InstanceT>(platforms, file_name_);
|
||||
}
|
||||
}
|
||||
|
||||
bool name_matches(const char *extension) {
|
||||
return extension_ == extension;
|
||||
}
|
||||
|
||||
Media media;
|
||||
|
||||
private:
|
||||
const std::string &file_name_;
|
||||
TargetPlatform::IntType &potential_platforms_;
|
||||
const std::string extension_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
MediaAccumulator accumulator(file_name, potential_platforms);
|
||||
Media result;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
#define InsertInstance(list, instance, platforms) \
|
||||
list.emplace_back(instance);\
|
||||
potential_platforms |= platforms;\
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
|
||||
|
||||
#define Insert(list, class, platforms, ...) \
|
||||
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
|
||||
|
||||
#define TryInsert(list, class, platforms, ...) \
|
||||
try {\
|
||||
Insert(list, class, platforms, __VA_ARGS__) \
|
||||
} catch(...) {}
|
||||
|
||||
#define Format(ext, list, class, platforms) \
|
||||
if(extension == ext) { \
|
||||
TryInsert(list, class, platforms, file_name) \
|
||||
}
|
||||
|
||||
// 2MG
|
||||
if(accumulator.name_matches("2mg")) {
|
||||
if(extension == "2mg") {
|
||||
// 2MG uses a factory method; defer to it.
|
||||
try {
|
||||
const auto media = Disk::Disk2MG::open(file_name);
|
||||
std::visit([&](auto &&arg) {
|
||||
using Type = typename std::decay<decltype(arg)>::type;
|
||||
|
||||
if constexpr (std::is_same<Type, std::nullptr_t>::value) {
|
||||
// It's valid for no media to be returned.
|
||||
} else if constexpr (std::is_same<Type, Disk::DiskImageHolderBase *>::value) {
|
||||
accumulator.insert(TargetPlatform::DiskII, std::shared_ptr<Disk::DiskImageHolderBase>(arg));
|
||||
} else if constexpr (std::is_same<Type, MassStorage::MassStorageDevice *>::value) {
|
||||
// TODO: or is it Apple IIgs?
|
||||
accumulator.insert(TargetPlatform::AppleII, std::shared_ptr<MassStorage::MassStorageDevice>(arg));
|
||||
} else {
|
||||
static_assert(always_false_v<Type>, "Unexpected type encountered.");
|
||||
}
|
||||
}, media);
|
||||
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "80");
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "81");
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
|
||||
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format( "dsk",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::CPCDSK>,
|
||||
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
|
||||
Format( "ipf",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::IPF>,
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO (original Apple II kind)
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Atari2600, "a26");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
||||
|
||||
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
||||
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore, "d64");
|
||||
accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "dsd");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::CPCDSK>>(
|
||||
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "dsk");
|
||||
accumulator.try_standard<MassStorage::HFV>(TargetPlatform::Macintosh, "dsk");
|
||||
accumulator.try_standard<MassStorage::DSK>(TargetPlatform::Macintosh, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore, "g64");
|
||||
|
||||
accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
|
||||
"hfe"); // TODO: switch to AllDisk once the MSX stops being so greedy.
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "image");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::IMD>>(TargetPlatform::PCCompatible, "imd");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "img");
|
||||
|
||||
// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
|
||||
if(accumulator.name_matches("img")) {
|
||||
try {
|
||||
accumulator.insert<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::FAT12, file_name);
|
||||
} catch(...) {
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::PCBooter>>(TargetPlatform::PCCompatible, "img");
|
||||
}
|
||||
// PO (Apple IIgs kind)
|
||||
if(extension == "po") {
|
||||
TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
|
||||
}
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::IPF>>(
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
|
||||
"ipf");
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
|
||||
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "o");
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "po");
|
||||
|
||||
if(accumulator.name_matches("po")) {
|
||||
accumulator.try_insert<Disk::DiskImageHolder<Disk::MacintoshIMG>>(
|
||||
TargetPlatform::AppleIIgs,
|
||||
file_name, Disk::MacintoshIMG::FixedType::GCR);
|
||||
}
|
||||
|
||||
accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
|
||||
|
||||
if(accumulator.name_matches("prg")) {
|
||||
// Try instantiating as a ROM; failing that accept as a tape.
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
// try instantiating as a ROM; failing that accept as a tape
|
||||
try {
|
||||
accumulator.insert<Cartridge::PRG>(TargetPlatform::Commodore, file_name);
|
||||
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
|
||||
} catch(...) {
|
||||
try {
|
||||
accumulator.insert<Tape::PRG>(TargetPlatform::Commodore, file_name);
|
||||
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(
|
||||
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX,
|
||||
"rom");
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
|
||||
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sg");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sms");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "ssd");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
|
||||
#undef Format
|
||||
#undef Insert
|
||||
#undef TryInsert
|
||||
#undef InsertInstance
|
||||
|
||||
accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore, "tap");
|
||||
accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
|
||||
accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum, "tzx");
|
||||
|
||||
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
|
||||
|
||||
return accumulator.media;
|
||||
return result;
|
||||
}
|
||||
|
||||
Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
@@ -304,28 +223,26 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
}
|
||||
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
const std::string extension = get_extension(file_name);
|
||||
TargetList targets;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
// Check whether the file directly identifies a target; if so then just return that.
|
||||
const auto try_snapshot = [&](const char *ext, auto loader) -> bool {
|
||||
if(extension != ext) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
auto target = loader(file_name);
|
||||
if(target) {
|
||||
targets.push_back(std::move(target));
|
||||
return true;
|
||||
}
|
||||
} catch(...) {}
|
||||
#define Format(ext, class) \
|
||||
if(extension == ext) { \
|
||||
try { \
|
||||
auto target = Storage::State::class::load(file_name); \
|
||||
if(target) { \
|
||||
targets.push_back(std::move(target)); \
|
||||
return targets; \
|
||||
} \
|
||||
} catch(...) {} \
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
Format("sna", SNA);
|
||||
Format("szx", SZX);
|
||||
Format("z80", Z80);
|
||||
|
||||
if(try_snapshot("sna", Storage::State::SNA::load)) return targets;
|
||||
if(try_snapshot("szx", Storage::State::SZX::load)) return targets;
|
||||
if(try_snapshot("z80", Storage::State::Z80::load)) return targets;
|
||||
#undef TryInsert
|
||||
|
||||
// Otherwise:
|
||||
//
|
||||
@@ -336,33 +253,28 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
|
||||
if(!(potential_platforms & platform)) {
|
||||
return;
|
||||
}
|
||||
auto new_targets = evaluator(media, file_name, potential_platforms);
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));
|
||||
};
|
||||
|
||||
append(TargetPlatform::Acorn, Acorn::GetTargets);
|
||||
append(TargetPlatform::AmstradCPC, AmstradCPC::GetTargets);
|
||||
append(TargetPlatform::AppleII, AppleII::GetTargets);
|
||||
append(TargetPlatform::AppleIIgs, AppleIIgs::GetTargets);
|
||||
append(TargetPlatform::Amiga, Amiga::GetTargets);
|
||||
append(TargetPlatform::Atari2600, Atari2600::GetTargets);
|
||||
append(TargetPlatform::AtariST, AtariST::GetTargets);
|
||||
append(TargetPlatform::Coleco, Coleco::GetTargets);
|
||||
append(TargetPlatform::Commodore, Commodore::GetTargets);
|
||||
append(TargetPlatform::DiskII, DiskII::GetTargets);
|
||||
append(TargetPlatform::Enterprise, Enterprise::GetTargets);
|
||||
append(TargetPlatform::FAT12, FAT12::GetTargets);
|
||||
append(TargetPlatform::Macintosh, Macintosh::GetTargets);
|
||||
append(TargetPlatform::MSX, MSX::GetTargets);
|
||||
append(TargetPlatform::Oric, Oric::GetTargets);
|
||||
append(TargetPlatform::PCCompatible, PCCompatible::GetTargets);
|
||||
append(TargetPlatform::Sega, Sega::GetTargets);
|
||||
append(TargetPlatform::ZX8081, ZX8081::GetTargets);
|
||||
append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
|
||||
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
}
|
||||
Append(Acorn);
|
||||
Append(AmstradCPC);
|
||||
Append(AppleII);
|
||||
Append(AppleIIgs);
|
||||
Append(Amiga);
|
||||
Append(Atari2600);
|
||||
Append(AtariST);
|
||||
Append(Coleco);
|
||||
Append(Commodore);
|
||||
Append(DiskII);
|
||||
Append(Enterprise);
|
||||
Append(Macintosh);
|
||||
Append(MSX);
|
||||
Append(Oric);
|
||||
Append(Sega);
|
||||
Append(ZX8081);
|
||||
Append(ZXSpectrum);
|
||||
#undef Append
|
||||
|
||||
// Reset any tapes to their initial position.
|
||||
for(const auto &target : targets) {
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef StaticAnalyser_hpp
|
||||
#define StaticAnalyser_hpp
|
||||
|
||||
#include "../Machines.hpp"
|
||||
|
||||
@@ -20,7 +21,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser::Static {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
|
||||
struct State;
|
||||
|
||||
@@ -38,15 +40,12 @@ struct Media {
|
||||
}
|
||||
|
||||
Media &operator +=(const Media &rhs) {
|
||||
const auto append = [&](auto &destination, auto &source) {
|
||||
destination.insert(destination.end(), source.begin(), source.end());
|
||||
};
|
||||
|
||||
append(disks, rhs.disks);
|
||||
append(tapes, rhs.tapes);
|
||||
append(cartridges, rhs.cartridges);
|
||||
append(mass_storage_devices, rhs.mass_storage_devices);
|
||||
|
||||
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
|
||||
append(disks);
|
||||
append(tapes);
|
||||
append(cartridges);
|
||||
append(mass_storage_devices);
|
||||
#undef append
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@@ -57,7 +56,7 @@ struct Media {
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() = default;
|
||||
virtual ~Target() {}
|
||||
|
||||
// This field is entirely optional.
|
||||
std::unique_ptr<Reflection::Struct> state;
|
||||
@@ -81,3 +80,6 @@ TargetList GetTargets(const std::string &file_name);
|
||||
Media GetMedia(const std::string &file_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,14 +6,17 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_ZX8081_Target_h
|
||||
#define Analyser_Static_ZX8081_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemoryModel,
|
||||
@@ -38,3 +41,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_ZX8081_Target_h */
|
||||
|
@@ -33,14 +33,14 @@ bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
}
|
||||
|
||||
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
|
||||
// Get logical sector 1; the Spectrum appears to support various physical
|
||||
// sectors as sector 1.
|
||||
const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
Storage::Encodings::MFM::Sector *boot_sector = nullptr;
|
||||
uint8_t sector_mask = 0;
|
||||
while(!boot_sector) {
|
||||
boot_sector = parser.sector(0, 0, sector_mask + 1);
|
||||
boot_sector = parser.get_sector(0, 0, sector_mask + 1);
|
||||
sector_mask += 0x40;
|
||||
if(!sector_mask) break;
|
||||
}
|
||||
|
@@ -6,14 +6,21 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
|
||||
#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -6,13 +6,16 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef Analyser_Static_ZXSpectrum_Target_h
|
||||
#define Analyser_Static_ZXSpectrum_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -36,3 +39,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
|
99
BUILD.md
99
BUILD.md
@@ -1,99 +0,0 @@
|
||||

|
||||
# Building Clock Signal
|
||||
|
||||
Clock Signal is available as [a macOS native application using
|
||||
Metal](#macos-app) or as [a cross-platform command-line-driven SDL executable
|
||||
using OpenGL](#sdl-app).
|
||||
|
||||
## macOS app
|
||||
|
||||
The macOS native application requires a Metal-capable Mac running macOS 10.13 or
|
||||
later and has no prerequisites beyond the normal system libraries. It can be
|
||||
built [using Xcode](#building-the-macos-app-using-xcode) or on the command line
|
||||
[using `xcodebuild`](#building-the-macos-app-using-xcodebuild).
|
||||
|
||||
Machine ROMs are intended to be built into the application bundle; populate the
|
||||
dummy folders below ROMImages before building.
|
||||
|
||||
The Xcode project is configured to sign the application using the developer's
|
||||
certificate, but if you are not the developer then you will get a "No signing
|
||||
certificate" error. To avoid this, you'll specify that you want to sign the
|
||||
application to run locally.
|
||||
|
||||
### Building the macOS app using Xcode
|
||||
|
||||
Open the Clock Signal Xcode project in OSBindings/Mac.
|
||||
|
||||
To avoid signing errors, edit the project, select the Signing & Capabilities
|
||||
tab, and change the Signing Certificate drop-down menu from "Development" to
|
||||
"Sign to Run Locally".
|
||||
|
||||
To avoid crashes when running Clock Signal via Xcode on older Macs due to
|
||||
"unrecognized selector sent to instance" errors, edit the scheme, and in the Run
|
||||
section, scroll down to the Metal heading and uncheck the "API Validation"
|
||||
checkbox.
|
||||
|
||||
To build, choose "Build" from Xcode's Product menu or press
|
||||
<kbd>Command</kbd> + <kbd>B</kbd>.
|
||||
|
||||
To build and run, choose "Run" from the Product menu or press
|
||||
<kbd>Command</kbd> + <kbd>R</kbd>.
|
||||
|
||||
To see the folder where the Clock Signal application was built, choose "Show
|
||||
Build Folder in Finder" from the Product menu. Look in the "Products" folder for
|
||||
a folder named after the configuration (e.g. "Debug" or "Release").
|
||||
|
||||
### Building the macOS app using `xcodebuild`
|
||||
|
||||
To build, change to the OSBindings/Mac directory in the Terminal, then run
|
||||
`xcodebuild`, specifying `-` as the code sign identity to sign the application
|
||||
to run locally to avoid signing errors:
|
||||
|
||||
cd OSBindings/Mac
|
||||
xcodebuild CODE_SIGN_IDENTITY=-
|
||||
|
||||
`xcodebuild` will create a "build" folder in this directory which is where you
|
||||
can find the Clock Signal application after it's compiled, in a directory named
|
||||
after the configuration (e.g. "Debug" or "Release").
|
||||
|
||||
## SDL app
|
||||
|
||||
The SDL app can be built on Linux, BSD, macOS, and other Unix-like operating
|
||||
systems. Prerequisites are SDL 2, ZLib and OpenGL (or Mesa). OpenGL 3.2 or
|
||||
better is required at runtime. It can be built [using
|
||||
SCons](#building-the-sdl-app-using-scons).
|
||||
|
||||
### Building the SDL app using SCons
|
||||
|
||||
To build, change to the OSBindings/SDL directory and run `scons`. You can add a
|
||||
`-j` flag to build in parallel. For example, if you have 8 processor cores:
|
||||
|
||||
cd OSBindings/SDL
|
||||
scons -j8
|
||||
|
||||
The `clksignal` executable will be created in this directory. You can run it
|
||||
from here or install it by copying it where you want it, for example:
|
||||
|
||||
cp clksignal /usr/local/bin
|
||||
|
||||
To start an emulator with a particular disk image `file`, if you've installed
|
||||
`clksignal` to a directory in your `PATH`, run:
|
||||
|
||||
clksignal file
|
||||
|
||||
Or if you're running it from the current directory:
|
||||
|
||||
./clksignal file
|
||||
|
||||
Other options are availble. Run `clksignal` or `./clksignal` with no arguments
|
||||
to learn more.
|
||||
|
||||
Setting up `clksignal` as the associated program for supported file types in
|
||||
your favoured filesystem browser is recommended; it has no file navigation
|
||||
abilities of its own.
|
||||
|
||||
Some emulated systems require the provision of original machine ROMs. These are
|
||||
not included and may be located in either /usr/local/share/CLK/ or
|
||||
/usr/share/CLK/. You will be prompted for them if they are found to be missing.
|
||||
The structure should mirror that under OSBindings in the source archive; see the
|
||||
readme.txt in each folder to determine the proper files and names ahead of time.
|
30
BUILD.txt
Normal file
30
BUILD.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
Linux, BSD
|
||||
==========
|
||||
|
||||
Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime.
|
||||
|
||||
Build:
|
||||
|
||||
cd OSBindings/SDL
|
||||
scons
|
||||
|
||||
Optionally:
|
||||
|
||||
cp clksignal /usr/bin
|
||||
|
||||
To launch:
|
||||
|
||||
clksignal file
|
||||
|
||||
Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own.
|
||||
|
||||
Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time.
|
||||
|
||||
macOS
|
||||
=====
|
||||
|
||||
There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application.
|
||||
|
||||
Build: open the Xcode project in OSBindings/Mac and press command+b.
|
||||
|
||||
Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building.
|
@@ -1,70 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
|
||||
project(CLK
|
||||
LANGUAGES CXX
|
||||
VERSION 24.01.22
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(CLK_UIS "SDL")
|
||||
#list(PREPEND CLK_UIS "QT")
|
||||
#if(APPLE)
|
||||
# list(PREPEND CLK_UIS "MAC")
|
||||
# set(CLK_DEFAULT_UI "MAC")
|
||||
#else()
|
||||
set(CLK_DEFAULT_UI "SDL")
|
||||
#endif()
|
||||
|
||||
set(CLK_UI ${CLK_DEFAULT_UI} CACHE STRING "User interface")
|
||||
set_property(CACHE CLK_UI PROPERTY STRINGS ${CLK_UIS})
|
||||
|
||||
if(NOT CLK_UI IN_LIST CLK_UIS)
|
||||
list(JOIN CLK_UIS ", " CLK_UIS_PRETTY)
|
||||
message(FATAL_ERROR "Invalid value for 'CLK_UI'; must be one of ${CLK_UIS_PRETTY}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Configuring for ${CLK_UI} UI")
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include("CLK_SOURCES")
|
||||
|
||||
add_executable(clksignal ${CLK_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(clksignal PRIVATE /W4)
|
||||
else()
|
||||
# TODO: Add -Wpedandic.
|
||||
target_compile_options(clksignal PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE ZLIB::ZLIB)
|
||||
|
||||
if(CLK_UI STREQUAL "MAC")
|
||||
enable_language(OBJC OBJCXX SWIFT)
|
||||
# TODO: Build the Mac version.
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE OpenGL::GL)
|
||||
if(APPLE)
|
||||
target_compile_definitions(clksignal PRIVATE "GL_SILENCE_DEPRECATION" "IGNORE_APPLE")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CLK_UI STREQUAL "QT")
|
||||
# TODO: Build the Qt version.
|
||||
elseif(APPLE)
|
||||
set(BLA_VENDOR Apple)
|
||||
find_package(BLAS REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE BLAS::BLAS)
|
||||
endif()
|
||||
|
||||
if(CLK_UI STREQUAL "SDL")
|
||||
find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2)
|
||||
target_link_libraries(clksignal PRIVATE SDL2::SDL2)
|
||||
endif()
|
||||
|
||||
# TODO: Investigate building on Windows.
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ClockReceiver_hpp
|
||||
#define ClockReceiver_hpp
|
||||
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
@@ -276,3 +277,5 @@ template <class T> class HalfClockReceiver: public T {
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
};
|
||||
|
||||
#endif /* ClockReceiver_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ClockingHintSource_hpp
|
||||
#define ClockingHintSource_hpp
|
||||
|
||||
namespace ClockingHint {
|
||||
|
||||
@@ -83,3 +84,5 @@ class Source {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockingHintSource_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef DeferredQueue_h
|
||||
#define DeferredQueue_h
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
@@ -73,11 +74,6 @@ template <typename TimeUnit> class DeferredQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns @c true if no actions are enqueued; @c false otherwise.
|
||||
bool empty() const {
|
||||
return pending_actions_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
// The list of deferred actions.
|
||||
struct DeferredAction {
|
||||
@@ -124,3 +120,5 @@ template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<
|
||||
private:
|
||||
std::function<void(TimeUnit)> target_;
|
||||
};
|
||||
|
||||
#endif /* DeferredQueue_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef DeferredValue_h
|
||||
#define DeferredValue_h
|
||||
|
||||
/*!
|
||||
Provides storage for a single deferred value: one with a current value and a certain number
|
||||
@@ -43,3 +44,5 @@ template <int DeferredDepth, typename ValueT> class DeferredValue {
|
||||
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* DeferredValue_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ForceInline_hpp
|
||||
#define ForceInline_hpp
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -21,3 +22,5 @@
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* ForceInline_h */
|
||||
|
@@ -6,15 +6,14 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef JustInTime_h
|
||||
#define JustInTime_h
|
||||
|
||||
#include "ClockReceiver.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
/*!
|
||||
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
|
||||
of time since run_for was last called.
|
||||
@@ -25,7 +24,7 @@
|
||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||
separate @c TargetTimeScale at template declaration.
|
||||
|
||||
If the held object implements @c next_sequence_point() then it'll be used to flush implicitly
|
||||
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
|
||||
as and when sequence points are hit. Callers can use will_flush() to predict these.
|
||||
|
||||
If the held object is a subclass of ClockingHint::Source, this template will register as an
|
||||
@@ -39,7 +38,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
private:
|
||||
/*!
|
||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
||||
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
|
||||
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
|
||||
|
||||
**Does not delete the object.**
|
||||
|
||||
@@ -122,13 +121,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
@@ -137,13 +130,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
@@ -246,9 +233,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.next_sequence_point();
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.next_sequence_point();
|
||||
const auto time = object_.get_next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
@@ -271,16 +258,12 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
bool did_flush_ = false;
|
||||
|
||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
|
||||
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -293,7 +276,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
object_(std::forward<Args>(args)...),
|
||||
threshold_(threshold) {}
|
||||
threshold_(threshold) {}
|
||||
|
||||
/// Adds time to the actor.
|
||||
inline void operator += (const LocalTimeScale &rhs) {
|
||||
@@ -332,5 +315,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue<true> task_queue_;
|
||||
Concurrency::AsyncTaskQueue task_queue_;
|
||||
};
|
||||
|
||||
#endif /* JustInTime_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ScanSynchroniser_h
|
||||
#define ScanSynchroniser_h
|
||||
|
||||
#include "../Outputs/ScanTarget.hpp"
|
||||
|
||||
@@ -83,3 +84,5 @@ class ScanSynchroniser {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ScanSynchroniser_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef TimeTypes_h
|
||||
#define TimeTypes_h
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -19,8 +20,6 @@ inline Nanos nanos_now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
inline Seconds seconds(Nanos nanos) {
|
||||
return double(nanos) / 1e9;
|
||||
}
|
||||
|
||||
}
|
||||
#endif /* TimeTypes_h */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef VSyncPredictor_hpp
|
||||
#define VSyncPredictor_hpp
|
||||
|
||||
#include "TimeTypes.hpp"
|
||||
#include <cassert>
|
||||
@@ -150,3 +151,5 @@ class VSyncPredictor {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* VSyncPredictor_hpp */
|
||||
|
@@ -9,11 +9,9 @@
|
||||
#include "1770.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
namespace {
|
||||
Log::Logger<Log::Source::WDFDC> logger;
|
||||
}
|
||||
#define LOG_PREFIX "[WD FDC] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace WD;
|
||||
|
||||
@@ -31,10 +29,10 @@ void WD1770::write(int address, uint8_t value) {
|
||||
if((value&0xf0) == 0xd0) {
|
||||
if(value == 0xd0) {
|
||||
// Force interrupt **immediately**.
|
||||
logger.info().append("Force interrupt immediately");
|
||||
LOG("Force interrupt immediately");
|
||||
posit_event(int(Event1770::ForceInterrupt));
|
||||
} else {
|
||||
logger.error().append("TODO: force interrupt");
|
||||
ERROR("!!!TODO: force interrupt!!!");
|
||||
update_status([] (Status &status) {
|
||||
status.type = Status::One;
|
||||
});
|
||||
@@ -68,7 +66,7 @@ uint8_t WD1770::read(int address) {
|
||||
|
||||
// Per Jean Louis-Guérin's documentation:
|
||||
//
|
||||
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
|
||||
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
|
||||
// read live after a type 1.
|
||||
// * the track 0 bit is captured during a type 1 instruction and lost upon any other type,
|
||||
// it is not live sampled.
|
||||
@@ -101,14 +99,14 @@ uint8_t WD1770::read(int address) {
|
||||
if(status_.type == Status::One)
|
||||
status |= (status_.spin_up ? Flag::SpinUp : 0);
|
||||
}
|
||||
// logger.info().append("Returned status %02x of type %d", status, 1+int(status_.type));
|
||||
// LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type));
|
||||
return status;
|
||||
}
|
||||
case 1:
|
||||
logger.info().append("Returned track %d", track_);
|
||||
LOG("Returned track " << int(track_));
|
||||
return track_;
|
||||
case 2:
|
||||
logger.info().append("Returned sector %d", sector_);
|
||||
LOG("Returned sector " << int(sector_));
|
||||
return sector_;
|
||||
case 3:
|
||||
update_status([] (Status &status) {
|
||||
@@ -214,7 +212,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
// Wait for a new command, branch to the appropriate handler.
|
||||
case 0:
|
||||
wait_for_command:
|
||||
logger.info().append("Idle...");
|
||||
LOG("Idle...");
|
||||
set_data_mode(DataMode::Scanning);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
@@ -231,7 +229,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
|
||||
});
|
||||
|
||||
logger.info().append("Starting %02x", command_);
|
||||
LOG("Starting " << PADHEX(2) << int(command_));
|
||||
|
||||
if(!(command_ & 0x80)) goto begin_type_1;
|
||||
if(!(command_ & 0x40)) goto begin_type_2;
|
||||
@@ -261,7 +259,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
status.data_request = false;
|
||||
});
|
||||
|
||||
logger.info().append("Step/Seek/Restore with track %d data %d", track_, data_);
|
||||
LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_));
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
|
||||
|
||||
if(has_motor_on_line()) goto begin_type1_spin_up;
|
||||
@@ -341,7 +339,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 6) {
|
||||
logger.info().append("Nothing found to verify");
|
||||
LOG("Nothing found to verify");
|
||||
update_status([] (Status &status) {
|
||||
status.seek_error = true;
|
||||
});
|
||||
@@ -359,7 +357,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
}
|
||||
|
||||
if(header_[0] == track_) {
|
||||
logger.info().append("Reached track %d", track_);
|
||||
LOG("Reached track " << std::dec << int(track_));
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = false;
|
||||
});
|
||||
@@ -432,7 +430,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
READ_ID();
|
||||
|
||||
if(index_hole_count_ == 5) {
|
||||
logger.info().append("Failed to find sector %d", sector_);
|
||||
LOG("Failed to find sector " << std::dec << int(sector_));
|
||||
update_status([] (Status &status) {
|
||||
status.record_not_found = true;
|
||||
});
|
||||
@@ -442,12 +440,12 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
set_data_mode(DataMode::Scanning);
|
||||
|
||||
logger.info().append("Considering %d/%d", header_[0], header_[2]);
|
||||
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
|
||||
if( header_[0] == track_ && header_[2] == sector_ &&
|
||||
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
|
||||
logger.info().append("Found %d/%d", header_[0], header_[2]);
|
||||
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
|
||||
if(get_crc_generator().get_value()) {
|
||||
logger.info().append("CRC error; back to searching");
|
||||
LOG("CRC error; back to searching");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
@@ -505,18 +503,18 @@ void WD1770::posit_event(int new_event_type) {
|
||||
set_data_mode(DataMode::Scanning);
|
||||
|
||||
if(get_crc_generator().get_value()) {
|
||||
logger.info().append("CRC error; terminating");
|
||||
LOG("CRC error; terminating");
|
||||
update_status([] (Status &status) {
|
||||
status.crc_error = true;
|
||||
});
|
||||
goto wait_for_command;
|
||||
}
|
||||
|
||||
logger.info().append("Finished reading sector %d", sector_);
|
||||
LOG("Finished reading sector " << std::dec << int(sector_));
|
||||
|
||||
if(command_ & 0x10) {
|
||||
sector_++;
|
||||
logger.info().append("Advancing to search for sector %d", sector_);
|
||||
LOG("Advancing to search for sector " << std::dec << int(sector_));
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
goto wait_for_command;
|
||||
@@ -600,7 +598,7 @@ void WD1770::posit_event(int new_event_type) {
|
||||
sector_++;
|
||||
goto test_type2_write_protection;
|
||||
}
|
||||
logger.info().append("Wrote sector %d", sector_);
|
||||
LOG("Wrote sector " << std::dec << int(sector_));
|
||||
goto wait_for_command;
|
||||
|
||||
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef _770_hpp
|
||||
#define _770_hpp
|
||||
|
||||
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
|
||||
|
||||
@@ -30,7 +31,7 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
WD1770(Personality p);
|
||||
virtual ~WD1770() = default;
|
||||
virtual ~WD1770() {}
|
||||
|
||||
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
|
||||
using Storage::Disk::MFMController::set_is_double_density;
|
||||
@@ -140,3 +141,5 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* _770_hpp */
|
||||
|
@@ -10,14 +10,6 @@
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
namespace {
|
||||
Log::Logger<Log::Source::NCR5380> logger;
|
||||
}
|
||||
// TODO:
|
||||
//
|
||||
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
|
||||
|
||||
|
||||
using namespace NCR::NCR5380;
|
||||
using SCSI::Line;
|
||||
|
||||
@@ -36,16 +28,20 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
|
||||
void NCR5380::write(int address, uint8_t value, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
logger.info().append("[0] Set current SCSI bus state to %02x", value);
|
||||
|
||||
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
data_bus_ = value;
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
|
||||
dma_acknowledge(value);
|
||||
// printf("w %02x\n", value);
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
logger.info().append("[1] Initiator command register set: %02x", value);
|
||||
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
initiator_command_ = value;
|
||||
|
||||
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
|
||||
@@ -61,7 +57,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
logger.info().append("[2] Set mode: %02x", value);
|
||||
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
|
||||
mode_ = value;
|
||||
|
||||
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
|
||||
@@ -73,7 +69,6 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
// bit 1: 1 = use DMA mode
|
||||
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
|
||||
arbitration_in_progress_ = false;
|
||||
phase_mismatch_ = false;
|
||||
switch(mode_ & 0x3) {
|
||||
case 0x0:
|
||||
bus_output_ &= ~SCSI::Line::Busy;
|
||||
@@ -93,36 +88,31 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
bus_.update_observers();
|
||||
break;
|
||||
}
|
||||
|
||||
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
|
||||
// is reset (0) in the Mode Register".
|
||||
end_of_dma_ &= bool(value & 0x2);
|
||||
|
||||
update_control_output();
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
logger.info().append("[3] Set target command: %02x", value);
|
||||
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
|
||||
target_command_ = value;
|
||||
update_control_output();
|
||||
} break;
|
||||
|
||||
case 4:
|
||||
logger.info().append("[4] Set select enabled: %02x", value);
|
||||
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
logger.info().append("[5] Start DMA send: %02x", value);
|
||||
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::Send;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
logger.info().append("[6] Start DMA target receive: %02x", value);
|
||||
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::TargetReceive;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
logger.info().append("[7] Start DMA initiator receive: %02x", value);
|
||||
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::InitiatorReceive;
|
||||
break;
|
||||
}
|
||||
@@ -146,15 +136,18 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
uint8_t NCR5380::read(int address, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
logger.info().append("[0] Get current SCSI bus state: %02x", (bus_.get_state() & 0xff));
|
||||
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
|
||||
return dma_acknowledge();
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
return uint8_t(bus_.get_state());
|
||||
|
||||
case 1:
|
||||
logger.info().append("[1] Initiator command register get: %c%c", arbitration_in_progress_ ? 'p' : '-', lost_arbitration_ ? 'l' : '-');
|
||||
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
return
|
||||
// Bits repeated as they were set.
|
||||
(initiator_command_ & ~0x60) |
|
||||
@@ -166,11 +159,11 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
(lost_arbitration_ ? 0x20 : 0x00);
|
||||
|
||||
case 2:
|
||||
logger.info().append("[2] Get mode");
|
||||
// LOG("[SCSI 2] Get mode");
|
||||
return mode_;
|
||||
|
||||
case 3:
|
||||
logger.info().append("[3] Get target command");
|
||||
// LOG("[SCSI 3] Get target command");
|
||||
return target_command_;
|
||||
|
||||
case 4: {
|
||||
@@ -184,38 +177,41 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
((bus_state & Line::Input) ? 0x04 : 0x00) |
|
||||
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Parity) ? 0x01 : 0x00);
|
||||
logger.info().append("[4] Get current bus state: %02x", result);
|
||||
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const bool phase_matches =
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
|
||||
const uint8_t result =
|
||||
(end_of_dma_ ? 0x80 : 0x00) |
|
||||
/* b7 = end of DMA */
|
||||
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
|
||||
/* b5 = parity error */
|
||||
(irq_ ? 0x10 : 0x00) |
|
||||
(phase_matches() ? 0x08 : 0x00) |
|
||||
/* b4 = IRQ active */
|
||||
(phase_matches ? 0x08 : 0x00) |
|
||||
/* b2 = busy error */
|
||||
((bus_state & Line::Attention) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
|
||||
logger.info().append("[5] Get bus and status: %02x", result);
|
||||
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 6:
|
||||
logger.info().append("[6] Get input data");
|
||||
// LOG("[SCSI 6] Get input data");
|
||||
return 0xff;
|
||||
|
||||
case 7:
|
||||
logger.info().append("[7] Reset parity/interrupt");
|
||||
irq_ = false;
|
||||
// LOG("[SCSI 7] Reset parity/interrupt");
|
||||
return 0xff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SCSI::BusState NCR5380::target_output() const {
|
||||
SCSI::BusState NCR5380::target_output() {
|
||||
SCSI::BusState output = SCSI::DefaultBusState;
|
||||
if(target_command_ & 0x08) output |= Line::Request;
|
||||
if(target_command_ & 0x04) output |= Line::Message;
|
||||
@@ -240,17 +236,6 @@ void NCR5380::update_control_output() {
|
||||
}
|
||||
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
|
||||
/*
|
||||
When connected as an Initiator with DMA Mode True,
|
||||
if the phase lines I//O, C//D, and /MSG do not match the
|
||||
phase bits in the Target Command Register, a phase mismatch
|
||||
interrupt is generated when /REQ goes active.
|
||||
*/
|
||||
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
|
||||
irq_ = true;
|
||||
phase_mismatch_ = true;
|
||||
}
|
||||
|
||||
switch(state_) {
|
||||
default: break;
|
||||
|
||||
@@ -311,13 +296,7 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
|
||||
dma_request_ = false;
|
||||
break;
|
||||
case SCSI::Line::Request:
|
||||
// Don't issue a new DMA request if a phase mismatch has
|
||||
// been detected and this is an intiator receiving.
|
||||
// This is a bit of reading between the lines.
|
||||
// (i.e. guesswork, partly)
|
||||
dma_request_ =
|
||||
!phase_mismatch_ ||
|
||||
(dma_operation_ != DMAOperation::InitiatorReceive);
|
||||
dma_request_ = true;
|
||||
break;
|
||||
case SCSI::Line::Request | SCSI::Line::Acknowledge:
|
||||
dma_request_ = false;
|
||||
@@ -337,38 +316,3 @@ void NCR5380::set_execution_state(ExecutionState state) {
|
||||
state_ = state;
|
||||
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
|
||||
}
|
||||
|
||||
size_t NCR5380::scsi_id() {
|
||||
return device_id_;
|
||||
}
|
||||
|
||||
bool NCR5380::dma_request() {
|
||||
return dma_request_;
|
||||
}
|
||||
|
||||
uint8_t NCR5380::dma_acknowledge() {
|
||||
const uint8_t bus_state = uint8_t(bus_.get_state());
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
|
||||
return bus_state;
|
||||
}
|
||||
|
||||
void NCR5380::dma_acknowledge(uint8_t value) {
|
||||
data_bus_ = value;
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
|
||||
bool NCR5380::phase_matches() const {
|
||||
const auto bus_state = bus_.get_state();
|
||||
return
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
}
|
||||
|
@@ -6,14 +6,16 @@
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef ncr5380_hpp
|
||||
#define ncr5380_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
|
||||
|
||||
namespace NCR::NCR5380 {
|
||||
namespace NCR {
|
||||
namespace NCR5380 {
|
||||
|
||||
/*!
|
||||
Models the NCR 5380, a SCSI interface chip.
|
||||
@@ -22,24 +24,12 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
public:
|
||||
NCR5380(SCSI::Bus &bus, int clock_rate);
|
||||
|
||||
/*! Writes @c value to @c address. */
|
||||
/*! Writes @c value to @c address. */
|
||||
void write(int address, uint8_t value, bool dma_acknowledge = false);
|
||||
|
||||
/*! Reads from @c address. */
|
||||
uint8_t read(int address, bool dma_acknowledge = false);
|
||||
|
||||
/*! @returns The SCSI ID assigned to this device. */
|
||||
size_t scsi_id();
|
||||
|
||||
/*! @return @c true if DMA request is active; @c false otherwise. */
|
||||
bool dma_request();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous read. */
|
||||
uint8_t dma_acknowledge();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous write. */
|
||||
void dma_acknowledge(uint8_t);
|
||||
|
||||
private:
|
||||
SCSI::Bus &bus_;
|
||||
|
||||
@@ -56,10 +46,6 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
bool assert_data_bus_ = false;
|
||||
bool dma_request_ = false;
|
||||
bool dma_acknowledge_ = false;
|
||||
bool end_of_dma_ = false;
|
||||
|
||||
bool irq_ = false;
|
||||
bool phase_mismatch_ = false;
|
||||
|
||||
enum class ExecutionState {
|
||||
None,
|
||||
@@ -77,11 +63,13 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
|
||||
void set_execution_state(ExecutionState state);
|
||||
|
||||
SCSI::BusState target_output() const;
|
||||
SCSI::BusState target_output();
|
||||
void update_control_output();
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
|
||||
bool phase_matches() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ncr5380_hpp */
|
||||
|
@@ -6,7 +6,8 @@
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef _522_hpp
|
||||
#define _522_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -14,7 +15,8 @@
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS::MOS6522 {
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
@@ -68,7 +70,7 @@ class IRQDelegatePortHandler: public PortHandler {
|
||||
/// Sets the delegate that will receive notification of changes in the interrupt line.
|
||||
void set_interrupt_delegate(Delegate *delegate);
|
||||
|
||||
/// Overrides @c PortHandler::set_interrupt_status, notifying the delegate if one is set.
|
||||
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
|
||||
void set_interrupt_status(bool new_status);
|
||||
|
||||
private:
|
||||
@@ -136,6 +138,9 @@ template <class BusHandlerT> class MOS6522: public MOS6522Storage {
|
||||
void evaluate_port_b_output();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
||||
#endif /* _522_hpp */
|
||||
|
@@ -12,7 +12,8 @@
|
||||
//
|
||||
// PB6 count-down mode for timer 2.
|
||||
|
||||
namespace MOS::MOS6522 {
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
template <typename T> void MOS6522<T>::access(int address) {
|
||||
switch(address) {
|
||||
@@ -271,7 +272,7 @@ template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line li
|
||||
// TODO: and at least one full clock since the shift register was written?
|
||||
if(port == Port::B) {
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
|
||||
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
|
||||
}
|
||||
@@ -329,7 +330,7 @@ template <typename T> void MOS6522<T>::do_phase2() {
|
||||
|
||||
// If the shift register is shifting according to the input clock, do a shift.
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderPhase2: shift_in(); break;
|
||||
case ShiftMode::OutUnderPhase2: shift_out(); break;
|
||||
}
|
||||
@@ -345,9 +346,9 @@ template <typename T> void MOS6522<T>::do_phase1() {
|
||||
// If the shift register is shifting according to this timer, do a shift.
|
||||
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
default: break;
|
||||
case ShiftMode::InUnderT2: shift_in(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
|
||||
}
|
||||
|
||||
@@ -493,3 +494,4 @@ template <typename T> void MOS6522<T>::shift_out() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user