mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			Tube
			...
			SoftwareOu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4078baa424 | ||
| 
						 | 
					3179d0d963 | ||
| 
						 | 
					6a8c792c63 | ||
| 
						 | 
					678e1a38fa | ||
| 
						 | 
					f4004baff8 | ||
| 
						 | 
					f04e4faae2 | 
							
								
								
									
										139
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										139
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,139 +0,0 @@
 | 
			
		||||
name: Build
 | 
			
		||||
on: [pull_request]
 | 
			
		||||
jobs:
 | 
			
		||||
 | 
			
		||||
  build-mac-xcodebuild:
 | 
			
		||||
    name: Mac UI / xcodebuild / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [macos-latest] #[macos-13, macos-14, macos-15]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
      uses: actions/checkout@v4
 | 
			
		||||
    - name: Install Xcode
 | 
			
		||||
      uses: maxim-lobanov/setup-xcode@v1
 | 
			
		||||
      with:
 | 
			
		||||
        xcode-version: latest-stable
 | 
			
		||||
    - name: Make
 | 
			
		||||
      working-directory: OSBindings/Mac
 | 
			
		||||
      run: |
 | 
			
		||||
        xcodebuild -downloadComponent MetalToolchain
 | 
			
		||||
        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 uninstall cmake
 | 
			
		||||
            brew install cmake sdl2
 | 
			
		||||
            ;;
 | 
			
		||||
        esac
 | 
			
		||||
    - name: Make
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        case $RUNNER_OS in
 | 
			
		||||
          Linux)
 | 
			
		||||
            jobs=$(nproc --all)
 | 
			
		||||
            ;;
 | 
			
		||||
          macOS)
 | 
			
		||||
            jobs=$(sysctl -n hw.activecpu)
 | 
			
		||||
            ;;
 | 
			
		||||
          *)
 | 
			
		||||
            jobs=1
 | 
			
		||||
        esac
 | 
			
		||||
        cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
 | 
			
		||||
        cmake --build build -v -j"$jobs"
 | 
			
		||||
 | 
			
		||||
  build-sdl-scons:
 | 
			
		||||
    name: SDL UI / scons / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [macos-latest, ubuntu-latest]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
      uses: actions/checkout@v4
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        case $RUNNER_OS in
 | 
			
		||||
          Linux)
 | 
			
		||||
            sudo apt-get --allow-releaseinfo-change update
 | 
			
		||||
            sudo apt-get --fix-missing install gcc-10 libsdl2-dev scons
 | 
			
		||||
            ;;
 | 
			
		||||
          macOS)
 | 
			
		||||
            brew install scons sdl2
 | 
			
		||||
            ;;
 | 
			
		||||
        esac
 | 
			
		||||
    - name: Make
 | 
			
		||||
      working-directory: OSBindings/SDL
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        case $RUNNER_OS in
 | 
			
		||||
          Linux)
 | 
			
		||||
            jobs=$(nproc --all)
 | 
			
		||||
            ;;
 | 
			
		||||
          macOS)
 | 
			
		||||
            jobs=$(sysctl -n hw.activecpu)
 | 
			
		||||
            ;;
 | 
			
		||||
          *)
 | 
			
		||||
            jobs=1
 | 
			
		||||
        esac
 | 
			
		||||
        scons -j"$jobs"
 | 
			
		||||
 | 
			
		||||
  build-qt5:
 | 
			
		||||
    name: Qt 5 / ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [ubuntu-latest]
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout
 | 
			
		||||
      uses: actions/checkout@v4
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      uses: jurplel/install-qt-action@v3
 | 
			
		||||
      with:
 | 
			
		||||
        version: '5.15.2'
 | 
			
		||||
        archives: 'qtbase qtmultimedia qtx11extras icu'
 | 
			
		||||
    - name: Make
 | 
			
		||||
      working-directory: OSBindings/Qt
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        qmake -o Makefile clksignal.pro
 | 
			
		||||
        make
 | 
			
		||||
 | 
			
		||||
#  build-qt6:
 | 
			
		||||
#    name: Qt 6 / ${{ matrix.os }}
 | 
			
		||||
#    strategy:
 | 
			
		||||
#      matrix:
 | 
			
		||||
#        os: [ubuntu-latest]
 | 
			
		||||
#    runs-on: ${{ matrix.os }}
 | 
			
		||||
#    steps:
 | 
			
		||||
#    - name: Checkout
 | 
			
		||||
#      uses: actions/checkout@v4
 | 
			
		||||
#    - name: Install dependencies
 | 
			
		||||
#      uses: jurplel/install-qt-action@v4
 | 
			
		||||
#      with:
 | 
			
		||||
#        version: '6.8'
 | 
			
		||||
#        archives: qtbase
 | 
			
		||||
#    - name: Make
 | 
			
		||||
#      working-directory: OSBindings/Qt
 | 
			
		||||
#      shell: bash
 | 
			
		||||
#      run: |
 | 
			
		||||
#        qmake -o Makefile clksignal.pro
 | 
			
		||||
#        make
 | 
			
		||||
							
								
								
									
										16
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										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 {
 | 
			
		||||
@@ -22,38 +22,38 @@ namespace Activity {
 | 
			
		||||
	and/or to show or unshow status indicators.
 | 
			
		||||
*/
 | 
			
		||||
class Observer {
 | 
			
		||||
public:
 | 
			
		||||
	virtual ~Observer() = default;
 | 
			
		||||
	public:
 | 
			
		||||
		/// Provides hints as to the sort of information presented on an LED.
 | 
			
		||||
		enum LEDPresentation: uint8_t {
 | 
			
		||||
			/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
 | 
			
		||||
			/// If this flag is absent then the LED describes an ephemeral state, such as media access.
 | 
			
		||||
			Persistent = (1 << 0),
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	/// Provides hints as to the sort of information presented on an LED.
 | 
			
		||||
	enum LEDPresentation: uint8_t {
 | 
			
		||||
		/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
 | 
			
		||||
		/// If this flag is absent then the LED describes an ephemeral state, such as media access.
 | 
			
		||||
		Persistent = (1 << 0),
 | 
			
		||||
	};
 | 
			
		||||
		/// Announces to the receiver that there is an LED of name @c name.
 | 
			
		||||
		virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
 | 
			
		||||
 | 
			
		||||
	/// Announces to the receiver that there is an LED of name @c name.
 | 
			
		||||
	virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
 | 
			
		||||
		/// Announces to the receiver that there is a drive of name @c name.
 | 
			
		||||
		///
 | 
			
		||||
		/// If a drive has the same name as an LED, that LED goes with this drive.
 | 
			
		||||
		virtual void register_drive([[maybe_unused]] const std::string &name) {}
 | 
			
		||||
 | 
			
		||||
	/// Announces to the receiver that there is a drive of name @c name.
 | 
			
		||||
	///
 | 
			
		||||
	/// If a drive has the same name as an LED, that LED goes with this drive.
 | 
			
		||||
	virtual void register_drive([[maybe_unused]] const std::string &name) {}
 | 
			
		||||
		/// Informs the receiver of the new state of the LED with name @c name.
 | 
			
		||||
		virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
 | 
			
		||||
 | 
			
		||||
	/// Informs the receiver of the new state of the LED with name @c name.
 | 
			
		||||
	virtual void set_led_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool lit) {}
 | 
			
		||||
		enum class DriveEvent {
 | 
			
		||||
			StepNormal,
 | 
			
		||||
			StepBelowZero,
 | 
			
		||||
			StepBeyondMaximum
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	enum class DriveEvent {
 | 
			
		||||
		StepNormal,
 | 
			
		||||
		StepBelowZero,
 | 
			
		||||
		StepBeyondMaximum
 | 
			
		||||
	};
 | 
			
		||||
		/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
			
		||||
		virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
 | 
			
		||||
 | 
			
		||||
	/// Informs the receiver that the named event just occurred for the drive with name @c name.
 | 
			
		||||
	virtual void announce_drive_event([[maybe_unused]] const std::string &name, [[maybe_unused]] DriveEvent event) {}
 | 
			
		||||
 | 
			
		||||
	/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
			
		||||
	virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
 | 
			
		||||
		/// Informs the receiver of the motor-on status of the drive with name @c name.
 | 
			
		||||
		virtual void set_drive_motor_status([[maybe_unused]] const std::string &name, [[maybe_unused]] bool is_on) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* ActivityObserver_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,19 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef ActivitySource_h
 | 
			
		||||
#define ActivitySource_h
 | 
			
		||||
 | 
			
		||||
#include "Observer.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Activity {
 | 
			
		||||
 | 
			
		||||
class Source {
 | 
			
		||||
public:
 | 
			
		||||
	virtual void set_activity_observer(Observer *) = 0;
 | 
			
		||||
	public:
 | 
			
		||||
		virtual void set_activity_observer(Observer *observer) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* ActivitySource_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
float ConfidenceCounter::confidence() const {
 | 
			
		||||
float ConfidenceCounter::get_confidence() {
 | 
			
		||||
	return float(hits_) / float(hits_ + misses_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,13 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef ConfidenceCounter_hpp
 | 
			
		||||
#define ConfidenceCounter_hpp
 | 
			
		||||
 | 
			
		||||
#include "ConfidenceSource.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a confidence source that calculates its probability by virtual of a history of events.
 | 
			
		||||
@@ -18,25 +20,28 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	The initial value of the confidence counter is 0.5.
 | 
			
		||||
*/
 | 
			
		||||
class ConfidenceCounter: public ConfidenceSource {
 | 
			
		||||
public:
 | 
			
		||||
	/*! @returns The computed probability, based on the history of events. */
 | 
			
		||||
	float confidence() const final;
 | 
			
		||||
	public:
 | 
			
		||||
		/*! @returns The computed probability, based on the history of events. */
 | 
			
		||||
		float get_confidence() final;
 | 
			
		||||
 | 
			
		||||
	/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
			
		||||
	void add_hit();
 | 
			
		||||
		/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
 | 
			
		||||
		void add_hit();
 | 
			
		||||
 | 
			
		||||
	/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
 | 
			
		||||
	void add_miss();
 | 
			
		||||
		/*! Records an event that implies this is not the appropriate class: pushes probability down towards 0.0. */
 | 
			
		||||
		void add_miss();
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Records an event that could be correct but isn't necessarily so; which can push probability
 | 
			
		||||
		down towards 0.5, but will never push it upwards.
 | 
			
		||||
	*/
 | 
			
		||||
	void add_equivocal();
 | 
			
		||||
		/*!
 | 
			
		||||
			Records an event that could be correct but isn't necessarily so; which can push probability
 | 
			
		||||
			down towards 0.5, but will never push it upwards.
 | 
			
		||||
		*/
 | 
			
		||||
		void add_equivocal();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	int hits_ = 1;
 | 
			
		||||
	int misses_ = 1;
 | 
			
		||||
	private:
 | 
			
		||||
		int hits_ = 1;
 | 
			
		||||
		int misses_ = 1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* ConfidenceCounter_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -17,7 +19,10 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	program is handed to an Atari 2600 then its confidence should grow towards 1.0.
 | 
			
		||||
*/
 | 
			
		||||
struct ConfidenceSource {
 | 
			
		||||
	virtual float confidence() const = 0;
 | 
			
		||||
	virtual float get_confidence() = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* ConfidenceSource_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -13,19 +13,16 @@
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
ConfidenceSummary::ConfidenceSummary(
 | 
			
		||||
	const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
	const std::vector<float> &weights
 | 
			
		||||
) :
 | 
			
		||||
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
 | 
			
		||||
	sources_(sources), weights_(weights) {
 | 
			
		||||
	assert(weights.size() == sources.size());
 | 
			
		||||
	weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ConfidenceSummary::confidence() const {
 | 
			
		||||
float ConfidenceSummary::get_confidence() {
 | 
			
		||||
	float result = 0.0f;
 | 
			
		||||
	for(std::size_t index = 0; index < sources_.size(); ++index) {
 | 
			
		||||
		result += sources_[index]->confidence() * weights_[index];
 | 
			
		||||
		result += sources_[index]->get_confidence() * weights_[index];
 | 
			
		||||
	}
 | 
			
		||||
	return result / weight_sum_;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,36 +6,41 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef ConfidenceSummary_hpp
 | 
			
		||||
#define ConfidenceSummary_hpp
 | 
			
		||||
 | 
			
		||||
#include "ConfidenceSource.hpp"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Summaries a collection of confidence sources by calculating their weighted sum.
 | 
			
		||||
*/
 | 
			
		||||
class ConfidenceSummary: public ConfidenceSource {
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Instantiates a summary that will produce the weighted sum of
 | 
			
		||||
		@c sources, each using the corresponding entry of @c weights.
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Instantiates a summary that will produce the weighted sum of
 | 
			
		||||
			@c sources, each using the corresponding entry of @c weights.
 | 
			
		||||
 | 
			
		||||
		Requires that @c sources and @c weights are of the same length.
 | 
			
		||||
	*/
 | 
			
		||||
	ConfidenceSummary(
 | 
			
		||||
		const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
		const std::vector<float> &weights);
 | 
			
		||||
			Requires that @c sources and @c weights are of the same length.
 | 
			
		||||
		*/
 | 
			
		||||
		ConfidenceSummary(
 | 
			
		||||
			const std::vector<ConfidenceSource *> &sources,
 | 
			
		||||
			const std::vector<float> &weights);
 | 
			
		||||
 | 
			
		||||
	/*! @returns The weighted sum of all sources. */
 | 
			
		||||
	float confidence() const final;
 | 
			
		||||
		/*! @returns The weighted sum of all sources. */
 | 
			
		||||
		float get_confidence() final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<ConfidenceSource *> sources_;
 | 
			
		||||
	const std::vector<float> weights_;
 | 
			
		||||
	float weight_sum_;
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<ConfidenceSource *> sources_;
 | 
			
		||||
		const std::vector<float> weights_;
 | 
			
		||||
		float weight_sum_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* ConfidenceSummary_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -15,90 +15,90 @@ using namespace Analyser::Dynamic;
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class MultiStruct: public Reflection::Struct {
 | 
			
		||||
public:
 | 
			
		||||
	MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
 | 
			
		||||
		for(auto device: devices) {
 | 
			
		||||
			options_.emplace_back(device->get_options());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void apply() {
 | 
			
		||||
		auto options = options_.begin();
 | 
			
		||||
		for(auto device: devices_) {
 | 
			
		||||
			device->set_options(*options);
 | 
			
		||||
			++options;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<std::string> all_keys() const final {
 | 
			
		||||
		std::set<std::string> keys;
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto new_keys = options->all_keys();
 | 
			
		||||
			keys.insert(new_keys.begin(), new_keys.end());
 | 
			
		||||
		}
 | 
			
		||||
		return std::vector<std::string>(keys.begin(), keys.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<std::string> values_for(const std::string &name) const final {
 | 
			
		||||
		std::set<std::string> values;
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto new_values = options->values_for(name);
 | 
			
		||||
			values.insert(new_values.begin(), new_values.end());
 | 
			
		||||
		}
 | 
			
		||||
		return std::vector<std::string>(values.begin(), values.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const std::type_info *type_of(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto info = options->type_of(name);
 | 
			
		||||
			if(info) return info;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t count_of(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto info = options->type_of(name);
 | 
			
		||||
			if(info) return options->count_of(name);
 | 
			
		||||
		}
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const void *get(const std::string &name) const final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto value = options->get(name);
 | 
			
		||||
			if(value) return value;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void *get(const std::string &name) final {
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			auto value = options->get(name);
 | 
			
		||||
			if(value) return value;
 | 
			
		||||
		}
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set(const std::string &name, const void *value, const size_t offset) final {
 | 
			
		||||
		const auto safe_type = type_of(name);
 | 
			
		||||
		if(!safe_type) return;
 | 
			
		||||
 | 
			
		||||
		// Set this property only where the child's type is the same as that
 | 
			
		||||
		// which was returned from here for type_of.
 | 
			
		||||
		for(auto &options: options_) {
 | 
			
		||||
			const auto type = options->type_of(name);
 | 
			
		||||
			if(!type) continue;
 | 
			
		||||
 | 
			
		||||
			if(*type == *safe_type) {
 | 
			
		||||
				options->set(name, value, offset);
 | 
			
		||||
	public:
 | 
			
		||||
		MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
 | 
			
		||||
			for(auto device: devices) {
 | 
			
		||||
				options_.emplace_back(device->get_options());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<Configurable::Device *> &devices_;
 | 
			
		||||
	std::vector<std::unique_ptr<Reflection::Struct>> options_;
 | 
			
		||||
		void apply() {
 | 
			
		||||
			auto options = options_.begin();
 | 
			
		||||
			for(auto device: devices_) {
 | 
			
		||||
				device->set_options(*options);
 | 
			
		||||
				++options;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::vector<std::string> all_keys() const final {
 | 
			
		||||
			std::set<std::string> keys;
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto new_keys = options->all_keys();
 | 
			
		||||
				keys.insert(new_keys.begin(), new_keys.end());
 | 
			
		||||
			}
 | 
			
		||||
			return std::vector<std::string>(keys.begin(), keys.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::vector<std::string> values_for(const std::string &name) const final {
 | 
			
		||||
			std::set<std::string> values;
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto new_values = options->values_for(name);
 | 
			
		||||
				values.insert(new_values.begin(), new_values.end());
 | 
			
		||||
			}
 | 
			
		||||
			return std::vector<std::string>(values.begin(), values.end());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const std::type_info *type_of(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto info = options->type_of(name);
 | 
			
		||||
				if(info) return info;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t count_of(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto info = options->type_of(name);
 | 
			
		||||
				if(info) return options->count_of(name);
 | 
			
		||||
			}
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const void *get(const std::string &name) const final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto value = options->get(name);
 | 
			
		||||
				if(value) return value;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void *get(const std::string &name) final {
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				auto value = options->get(name);
 | 
			
		||||
				if(value) return value;
 | 
			
		||||
			}
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void set(const std::string &name, const void *value, size_t offset) final {
 | 
			
		||||
			const auto safe_type = type_of(name);
 | 
			
		||||
			if(!safe_type) return;
 | 
			
		||||
 | 
			
		||||
			// Set this property only where the child's type is the same as that
 | 
			
		||||
			// which was returned from here for type_of.
 | 
			
		||||
			for(auto &options: options_) {
 | 
			
		||||
				const auto type = options->type_of(name);
 | 
			
		||||
				if(!type) continue;
 | 
			
		||||
 | 
			
		||||
				if(*type == *safe_type) {
 | 
			
		||||
					options->set(name, value, offset);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<Configurable::Device *> &devices_;
 | 
			
		||||
		std::vector<std::unique_ptr<Reflection::Struct>> options_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -115,6 +115,6 @@ void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &s
 | 
			
		||||
	options->apply();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() const {
 | 
			
		||||
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
 | 
			
		||||
	return std::make_unique<MultiStruct>(devices_);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,17 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiConfigurable_hpp
 | 
			
		||||
#define MultiConfigurable_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Configurable/Configurable.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Configurable/Configurable.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that multiplexes the configurable interface to multiple machines.
 | 
			
		||||
@@ -23,15 +25,18 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiConfigurable: public Configurable::Device {
 | 
			
		||||
public:
 | 
			
		||||
	MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
	public:
 | 
			
		||||
		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard Configurable::Device interface; see there for documentation.
 | 
			
		||||
	void set_options(const std::unique_ptr<Reflection::Struct> &) final;
 | 
			
		||||
	std::unique_ptr<Reflection::Struct> get_options() const final;
 | 
			
		||||
		// Below is the standard Configurable::Device interface; see there for documentation.
 | 
			
		||||
		void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
 | 
			
		||||
		std::unique_ptr<Reflection::Struct> get_options() final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Configurable::Device *> devices_;
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Configurable::Device *> devices_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiConfigurable_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -15,52 +15,52 @@ using namespace Analyser::Dynamic;
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class MultiJoystick: public Inputs::Joystick {
 | 
			
		||||
public:
 | 
			
		||||
	MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, const std::size_t index) {
 | 
			
		||||
		for(const auto &machine: machines) {
 | 
			
		||||
			const auto &joysticks = machine->get_joysticks();
 | 
			
		||||
			if(joysticks.size() > index) {
 | 
			
		||||
				joysticks_.push_back(joysticks[index].get());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const std::vector<Input> &get_inputs() final {
 | 
			
		||||
		if(inputs.empty()) {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
			
		||||
				for(const auto &input: joystick_inputs) {
 | 
			
		||||
					if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
 | 
			
		||||
						inputs.push_back(input);
 | 
			
		||||
					}
 | 
			
		||||
	public:
 | 
			
		||||
		MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
 | 
			
		||||
			for(const auto &machine: machines) {
 | 
			
		||||
				const auto &joysticks = machine->get_joysticks();
 | 
			
		||||
				if(joysticks.size() >= index) {
 | 
			
		||||
					joysticks_.push_back(joysticks[index].get());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return inputs;
 | 
			
		||||
	}
 | 
			
		||||
		const std::vector<Input> &get_inputs() final {
 | 
			
		||||
			if(inputs.empty()) {
 | 
			
		||||
				for(const auto &joystick: joysticks_) {
 | 
			
		||||
					std::vector<Input> joystick_inputs = joystick->get_inputs();
 | 
			
		||||
					for(const auto &input: joystick_inputs) {
 | 
			
		||||
						if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
 | 
			
		||||
							inputs.push_back(input);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	void set_input(const Input &digital_input, const bool is_active) final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->set_input(digital_input, is_active);
 | 
			
		||||
			return inputs;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_input(const Input &digital_input, const float value) final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->set_input(digital_input, value);
 | 
			
		||||
		void set_input(const Input &digital_input, bool is_active) final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->set_input(digital_input, is_active);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void reset_all_inputs() final {
 | 
			
		||||
		for(const auto &joystick: joysticks_) {
 | 
			
		||||
			joystick->reset_all_inputs();
 | 
			
		||||
		void set_input(const Input &digital_input, float value) final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->set_input(digital_input, value);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Input> inputs;
 | 
			
		||||
	std::vector<Inputs::Joystick *> joysticks_;
 | 
			
		||||
		void reset_all_inputs() final {
 | 
			
		||||
			for(const auto &joystick: joysticks_) {
 | 
			
		||||
				joystick->reset_all_inputs();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Input> inputs;
 | 
			
		||||
		std::vector<Inputs::Joystick *> joysticks_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,16 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiJoystickMachine_hpp
 | 
			
		||||
#define MultiJoystickMachine_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that multiplexes the joystick machine interface to multiple machines.
 | 
			
		||||
@@ -22,14 +24,17 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
 | 
			
		||||
public:
 | 
			
		||||
	MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
	public:
 | 
			
		||||
		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
			
		||||
	const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
 | 
			
		||||
		// Below is the standard JoystickMachine::Machine interface; see there for documentation.
 | 
			
		||||
		const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiJoystickMachine_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ void MultiKeyboardMachine::clear_all_keys() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiKeyboardMachine::set_key_state(const uint16_t key, const bool is_pressed) {
 | 
			
		||||
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		machine->set_key_state(key, is_pressed);
 | 
			
		||||
	}
 | 
			
		||||
@@ -36,7 +36,7 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiKeyboardMachine::can_type(const char c) const {
 | 
			
		||||
bool MultiKeyboardMachine::can_type(char c) const {
 | 
			
		||||
	bool can_type = true;
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		can_type &= machine->can_type(c);
 | 
			
		||||
@@ -51,23 +51,15 @@ Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
 | 
			
		||||
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
 | 
			
		||||
	: machines_(machines) {
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		observed_keys_.insert(
 | 
			
		||||
			machine->get_keyboard().observed_keys().begin(),
 | 
			
		||||
			machine->get_keyboard().observed_keys().end()
 | 
			
		||||
		);
 | 
			
		||||
		observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
 | 
			
		||||
		is_exclusive_ |= machine->get_keyboard().is_exclusive();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(
 | 
			
		||||
	const Key key,
 | 
			
		||||
	const char value,
 | 
			
		||||
	const bool is_pressed,
 | 
			
		||||
	const bool is_repeat
 | 
			
		||||
) {
 | 
			
		||||
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
 | 
			
		||||
	bool was_consumed = false;
 | 
			
		||||
	for(const auto &machine: machines_) {
 | 
			
		||||
		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
 | 
			
		||||
		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
 | 
			
		||||
	}
 | 
			
		||||
	return was_consumed;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,17 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiKeyboardMachine_hpp
 | 
			
		||||
#define MultiKeyboardMachine_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Machines/KeyboardMachine.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Machines/KeyboardMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that multiplexes the keyboard machine interface to multiple machines.
 | 
			
		||||
@@ -23,34 +25,37 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::KeyboardMachine *> machines_;
 | 
			
		||||
 | 
			
		||||
	class MultiKeyboard: public Inputs::Keyboard {
 | 
			
		||||
	public:
 | 
			
		||||
		MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &);
 | 
			
		||||
 | 
			
		||||
		bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
 | 
			
		||||
		void reset_all_keys() final;
 | 
			
		||||
		const std::set<Key> &observed_keys() const final;
 | 
			
		||||
		bool is_exclusive() const final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<MachineTypes::KeyboardMachine *> &machines_;
 | 
			
		||||
		std::set<Key> observed_keys_;
 | 
			
		||||
		bool is_exclusive_ = false;
 | 
			
		||||
	};
 | 
			
		||||
	std::unique_ptr<MultiKeyboard> keyboard_;
 | 
			
		||||
		std::vector<MachineTypes::KeyboardMachine *> machines_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
		class MultiKeyboard: public Inputs::Keyboard {
 | 
			
		||||
			public:
 | 
			
		||||
				MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
			
		||||
	void clear_all_keys() final;
 | 
			
		||||
	void set_key_state(uint16_t key, bool is_pressed) final;
 | 
			
		||||
	void type_string(const std::string &) final;
 | 
			
		||||
	bool can_type(char c) const final;
 | 
			
		||||
	Inputs::Keyboard &get_keyboard() final;
 | 
			
		||||
				bool set_key_pressed(Key key, char value, bool is_pressed) final;
 | 
			
		||||
				void reset_all_keys() final;
 | 
			
		||||
				const std::set<Key> &observed_keys() const final;
 | 
			
		||||
				bool is_exclusive() const final;
 | 
			
		||||
 | 
			
		||||
			private:
 | 
			
		||||
				const std::vector<MachineTypes::KeyboardMachine *> &machines_;
 | 
			
		||||
				std::set<Key> observed_keys_;
 | 
			
		||||
				bool is_exclusive_ = false;
 | 
			
		||||
		};
 | 
			
		||||
		std::unique_ptr<MultiKeyboard> keyboard_;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
		// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
 | 
			
		||||
		void clear_all_keys() final;
 | 
			
		||||
		void set_key_state(uint16_t key, bool is_pressed) final;
 | 
			
		||||
		void type_string(const std::string &) final;
 | 
			
		||||
		bool can_type(char c) const final;
 | 
			
		||||
		Inputs::Keyboard &get_keyboard() final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiKeyboardMachine_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MultiMediaTarget.hpp"
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
@@ -19,38 +18,9 @@ MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
 | 
			
		||||
	// TODO: copy media afresh for each target machine; media
 | 
			
		||||
	// generally has mutable state.
 | 
			
		||||
 | 
			
		||||
	bool inserted = false;
 | 
			
		||||
	for(const auto &target : targets_) {
 | 
			
		||||
		inserted |= target->insert_media(media);
 | 
			
		||||
	}
 | 
			
		||||
	return inserted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MultiMediaChangeObserver::MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
 | 
			
		||||
	for(const auto &machine: machines) {
 | 
			
		||||
		auto media_change_observer = machine->media_change_observer();
 | 
			
		||||
		if(media_change_observer) targets_.push_back(media_change_observer);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
using ChangeEffect = MachineTypes::MediaChangeObserver::ChangeEffect;
 | 
			
		||||
 | 
			
		||||
ChangeEffect MultiMediaChangeObserver::effect_for_file_did_change(const std::string &name) const {
 | 
			
		||||
	if(targets_.empty()) {
 | 
			
		||||
		return ChangeEffect::None;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::unordered_set<ChangeEffect> effects;
 | 
			
		||||
	for(const auto &target: targets_) {
 | 
			
		||||
		effects.insert(target->effect_for_file_did_change(name));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No agreement => restart.
 | 
			
		||||
	if(effects.size() > 1) {
 | 
			
		||||
		return ChangeEffect::RestartMachine;
 | 
			
		||||
	}
 | 
			
		||||
	return *effects.begin();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,17 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiMediaTarget_hpp
 | 
			
		||||
#define MultiMediaTarget_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/MediaTarget.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Machines/MediaTarget.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that multiplexes the media target interface to multiple machines.
 | 
			
		||||
@@ -23,25 +25,17 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	order of delivered messages.
 | 
			
		||||
*/
 | 
			
		||||
struct MultiMediaTarget: public MachineTypes::MediaTarget {
 | 
			
		||||
public:
 | 
			
		||||
	MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
	public:
 | 
			
		||||
		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
	bool insert_media(const Analyser::Static::Media &) final;
 | 
			
		||||
		// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
		bool insert_media(const Analyser::Static::Media &media) final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::MediaTarget *> targets_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MultiMediaChangeObserver: public MachineTypes::MediaChangeObserver {
 | 
			
		||||
public:
 | 
			
		||||
	MultiMediaChangeObserver(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard MediaTarget::Machine interface; see there for documentation.
 | 
			
		||||
	ChangeEffect effect_for_file_did_change(const std::string &) const final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<MachineTypes::MediaChangeObserver *> targets_;
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<MachineTypes::MediaTarget *> targets_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiMediaTarget_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ template <typename MachineType>
 | 
			
		||||
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
 | 
			
		||||
	// Apply a blunt force parallelisation of the machines; each run_for is dispatched
 | 
			
		||||
	// to a separate queue and this queue will block until all are done.
 | 
			
		||||
	std::size_t outstanding_machines;
 | 
			
		||||
	volatile std::size_t outstanding_machines;
 | 
			
		||||
	std::condition_variable condition;
 | 
			
		||||
	std::mutex mutex;
 | 
			
		||||
	{
 | 
			
		||||
@@ -33,7 +33,7 @@ void MultiInterface<MachineType>::perform_parallel(const std::function<void(Mach
 | 
			
		||||
				if(machine) function(machine);
 | 
			
		||||
 | 
			
		||||
				std::lock_guard lock(mutex);
 | 
			
		||||
				--outstanding_machines;
 | 
			
		||||
				outstanding_machines--;
 | 
			
		||||
				condition.notify_all();
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
@@ -53,7 +53,7 @@ void MultiInterface<MachineType>::perform_serial(const std::function<void(Machin
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiScanProducer
 | 
			
		||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
 | 
			
		||||
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
 | 
			
		||||
	scan_target_ = scan_target;
 | 
			
		||||
 | 
			
		||||
	std::lock_guard machines_lock(machines_mutex_);
 | 
			
		||||
@@ -80,12 +80,7 @@ void MultiScanProducer::did_change_machine_order() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiAudioProducer
 | 
			
		||||
MultiAudioProducer::MultiAudioProducer(
 | 
			
		||||
	const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
 | 
			
		||||
	std::recursive_mutex &machines_mutex
 | 
			
		||||
) :
 | 
			
		||||
	MultiInterface(machines, machines_mutex)
 | 
			
		||||
{
 | 
			
		||||
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
 | 
			
		||||
	speaker_ = MultiSpeaker::create(machines);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -101,10 +96,10 @@ void MultiAudioProducer::did_change_machine_order() {
 | 
			
		||||
 | 
			
		||||
// MARK: - MultiTimedMachine
 | 
			
		||||
 | 
			
		||||
void MultiTimedMachine::run_for(const Time::Seconds duration) {
 | 
			
		||||
void MultiTimedMachine::run_for(Time::Seconds duration) {
 | 
			
		||||
	perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
 | 
			
		||||
		if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if(delegate_) delegate_->did_run_machines(*this);
 | 
			
		||||
	if(delegate_) delegate_->did_run_machines(this);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,12 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiProducer_hpp
 | 
			
		||||
#define MultiProducer_hpp
 | 
			
		||||
 | 
			
		||||
#include "Concurrency/AsyncTaskQueue.hpp"
 | 
			
		||||
#include "Machines/MachineTypes.hpp"
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
 | 
			
		||||
#include "../../../../Machines/MachineTypes.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include "MultiSpeaker.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -18,94 +19,92 @@
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
template <typename MachineType> class MultiInterface {
 | 
			
		||||
public:
 | 
			
		||||
	MultiInterface(
 | 
			
		||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines,
 | 
			
		||||
		std::recursive_mutex &machines_mutex
 | 
			
		||||
	) :
 | 
			
		||||
		machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
 | 
			
		||||
	public:
 | 
			
		||||
		MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
 | 
			
		||||
			machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	/*!
 | 
			
		||||
		Performs a parallel for operation across all machines, performing the supplied
 | 
			
		||||
		function on each and returning only once all applications have completed.
 | 
			
		||||
	protected:
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs a parallel for operation across all machines, performing the supplied
 | 
			
		||||
			function on each and returning only once all applications have completed.
 | 
			
		||||
 | 
			
		||||
		No guarantees are extended as to which thread operations will occur on.
 | 
			
		||||
	*/
 | 
			
		||||
	void perform_parallel(const std::function<void(MachineType *)> &);
 | 
			
		||||
			No guarantees are extended as to which thread operations will occur on.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_parallel(const std::function<void(MachineType *)> &);
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Performs a serial for operation across all machines, performing the supplied
 | 
			
		||||
		function on each on the calling thread.
 | 
			
		||||
	*/
 | 
			
		||||
	void perform_serial(const std::function<void(MachineType *)> &);
 | 
			
		||||
		/*!
 | 
			
		||||
			Performs a serial for operation across all machines, performing the supplied
 | 
			
		||||
			function on each on the calling thread.
 | 
			
		||||
		*/
 | 
			
		||||
		void perform_serial(const std::function<void(MachineType *)> &);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
			
		||||
	std::recursive_mutex &machines_mutex_;
 | 
			
		||||
	protected:
 | 
			
		||||
		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
 | 
			
		||||
		std::recursive_mutex &machines_mutex_;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
 | 
			
		||||
	private:
 | 
			
		||||
		std::vector<Concurrency::AsyncTaskQueue> queues_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
 | 
			
		||||
public:
 | 
			
		||||
	using MultiInterface::MultiInterface;
 | 
			
		||||
	public:
 | 
			
		||||
		using MultiInterface::MultiInterface;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
			
		||||
		been received.
 | 
			
		||||
	*/
 | 
			
		||||
	struct Delegate {
 | 
			
		||||
		virtual void did_run_machines(MultiTimedMachine &) = 0;
 | 
			
		||||
	};
 | 
			
		||||
	/// Sets @c delegate as the receiver of delegate messages.
 | 
			
		||||
	void set_delegate(Delegate *const delegate) {
 | 
			
		||||
		delegate_ = delegate;
 | 
			
		||||
	}
 | 
			
		||||
		/*!
 | 
			
		||||
			Provides a mechanism by which a delegate can be informed each time a call to run_for has
 | 
			
		||||
			been received.
 | 
			
		||||
		*/
 | 
			
		||||
		struct Delegate {
 | 
			
		||||
			virtual void did_run_machines(MultiTimedMachine *) = 0;
 | 
			
		||||
		};
 | 
			
		||||
		/// Sets @c delegate as the receiver of delegate messages.
 | 
			
		||||
		void set_delegate(Delegate *delegate) {
 | 
			
		||||
			delegate_ = delegate;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	void run_for(Time::Seconds duration) final;
 | 
			
		||||
		void run_for(Time::Seconds duration) final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void run_for(Cycles) final {}
 | 
			
		||||
	Delegate *delegate_ = nullptr;
 | 
			
		||||
	private:
 | 
			
		||||
		void run_for(const Cycles) final {}
 | 
			
		||||
		Delegate *delegate_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
 | 
			
		||||
public:
 | 
			
		||||
	using MultiInterface::MultiInterface;
 | 
			
		||||
	public:
 | 
			
		||||
		using MultiInterface::MultiInterface;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Informs the MultiScanProducer that the order of machines has changed; it
 | 
			
		||||
		uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
			
		||||
		are necessary to bridge the gap between one machine and the next.
 | 
			
		||||
	*/
 | 
			
		||||
	void did_change_machine_order();
 | 
			
		||||
		/*!
 | 
			
		||||
			Informs the MultiScanProducer that the order of machines has changed; it
 | 
			
		||||
			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
 | 
			
		||||
			are necessary to bridge the gap between one machine and the next.
 | 
			
		||||
		*/
 | 
			
		||||
		void did_change_machine_order();
 | 
			
		||||
 | 
			
		||||
	void set_scan_target(Outputs::Display::ScanTarget *) final;
 | 
			
		||||
	Outputs::Display::ScanStatus get_scan_status() const final;
 | 
			
		||||
		void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
 | 
			
		||||
		Outputs::Display::ScanStatus get_scan_status() const final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
			
		||||
	private:
 | 
			
		||||
		Outputs::Display::ScanTarget *scan_target_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
 | 
			
		||||
public:
 | 
			
		||||
	MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &, std::recursive_mutex &);
 | 
			
		||||
	public:
 | 
			
		||||
		MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Informs the MultiAudio that the order of machines has changed; it
 | 
			
		||||
		uses this as an opportunity to switch speaker delegates as appropriate.
 | 
			
		||||
	*/
 | 
			
		||||
	void did_change_machine_order();
 | 
			
		||||
		/*!
 | 
			
		||||
			Informs the MultiAudio that the order of machines has changed; it
 | 
			
		||||
			uses this as an opportunity to switch speaker delegates as appropriate.
 | 
			
		||||
		*/
 | 
			
		||||
		void did_change_machine_order();
 | 
			
		||||
 | 
			
		||||
	Outputs::Speaker::Speaker *get_speaker() final;
 | 
			
		||||
		Outputs::Speaker::Speaker *get_speaker() final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	MultiSpeaker *speaker_ = nullptr;
 | 
			
		||||
	private:
 | 
			
		||||
		MultiSpeaker *speaker_ = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
@@ -117,3 +116,7 @@ private:
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* MultiProducer_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speak
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const float maximum) {
 | 
			
		||||
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
 | 
			
		||||
	float ideal = 0.0f;
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
 | 
			
		||||
@@ -37,7 +37,7 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(const float minimum, const flo
 | 
			
		||||
	return ideal / float(speakers_.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_computed_output_rate(const float cycles_per_second, const int buffer_size, const bool stereo) {
 | 
			
		||||
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
 | 
			
		||||
	stereo_output_ = stereo;
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
 | 
			
		||||
@@ -54,39 +54,39 @@ bool MultiSpeaker::get_is_stereo() {
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_output_volume(const float volume) {
 | 
			
		||||
void MultiSpeaker::set_output_volume(float volume) {
 | 
			
		||||
	for(const auto &speaker: speakers_) {
 | 
			
		||||
		speaker->set_output_volume(volume);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::speaker_did_complete_samples(Speaker &speaker, const std::vector<int16_t> &buffer) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
			
		||||
	if(!delegate) return;
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		if(&speaker != front_speaker_) return;
 | 
			
		||||
		if(speaker != front_speaker_) return;
 | 
			
		||||
	}
 | 
			
		||||
	did_complete_samples(this, buffer, stereo_output_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker &speaker) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
			
		||||
	if(!delegate) return;
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		if(&speaker != front_speaker_) return;
 | 
			
		||||
		if(speaker != front_speaker_) return;
 | 
			
		||||
	}
 | 
			
		||||
	delegate->speaker_did_change_input_clock(*this);
 | 
			
		||||
	delegate->speaker_did_change_input_clock(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
 | 
			
		||||
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
 | 
			
		||||
	{
 | 
			
		||||
		std::lock_guard lock_guard(front_speaker_mutex_);
 | 
			
		||||
		front_speaker_ = machine->audio_producer()->get_speaker();
 | 
			
		||||
	}
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order_relaxed);
 | 
			
		||||
	auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
 | 
			
		||||
	if(delegate) {
 | 
			
		||||
		delegate->speaker_did_change_input_clock(*this);
 | 
			
		||||
		delegate->speaker_did_change_input_clock(this);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,16 +6,18 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiSpeaker_hpp
 | 
			
		||||
#define MultiSpeaker_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "Outputs/Speaker/Speaker.hpp"
 | 
			
		||||
#include "../../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../../Outputs/Speaker/Speaker.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
 | 
			
		||||
@@ -25,32 +27,35 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	abreast of the current frontmost machine.
 | 
			
		||||
*/
 | 
			
		||||
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Provides a construction mechanism that may return nullptr, in the case that all included
 | 
			
		||||
		machines return nullptr as their speaker.
 | 
			
		||||
	*/
 | 
			
		||||
	static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &);
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Provides a construction mechanism that may return nullptr, in the case that all included
 | 
			
		||||
			machines return nullptr as their speaker.
 | 
			
		||||
		*/
 | 
			
		||||
		static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
 | 
			
		||||
 | 
			
		||||
	/// This class requires the caller to nominate changes in the frontmost machine.
 | 
			
		||||
	void set_new_front_machine(::Machine::DynamicMachine *);
 | 
			
		||||
		/// This class requires the caller to nominate changes in the frontmost machine.
 | 
			
		||||
		void set_new_front_machine(::Machine::DynamicMachine *machine);
 | 
			
		||||
 | 
			
		||||
	// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
			
		||||
	float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
			
		||||
	void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
 | 
			
		||||
	bool get_is_stereo() override;
 | 
			
		||||
	void set_output_volume(float) override;
 | 
			
		||||
		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
 | 
			
		||||
		float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
 | 
			
		||||
		void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
 | 
			
		||||
		bool get_is_stereo() override;
 | 
			
		||||
		void set_output_volume(float) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void speaker_did_complete_samples(Speaker &, const std::vector<int16_t> &buffer) final;
 | 
			
		||||
	void speaker_did_change_input_clock(Speaker &) final;
 | 
			
		||||
	MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
			
		||||
	private:
 | 
			
		||||
		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
 | 
			
		||||
		void speaker_did_change_input_clock(Speaker *speaker) final;
 | 
			
		||||
		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
 | 
			
		||||
 | 
			
		||||
	std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
			
		||||
	Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
			
		||||
	std::mutex front_speaker_mutex_;
 | 
			
		||||
		std::vector<Outputs::Speaker::Speaker *> speakers_;
 | 
			
		||||
		Outputs::Speaker::Speaker *front_speaker_ = nullptr;
 | 
			
		||||
		std::mutex front_speaker_mutex_;
 | 
			
		||||
 | 
			
		||||
	bool stereo_output_ = false;
 | 
			
		||||
		bool stereo_output_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiSpeaker_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,14 +7,10 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MultiMachine.hpp"
 | 
			
		||||
#include "Outputs/Log.hpp"
 | 
			
		||||
#include "../../../Outputs/Log.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
using Logger = Log::Logger<Log::Source::MultiMachine>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Dynamic;
 | 
			
		||||
 | 
			
		||||
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
 | 
			
		||||
@@ -25,9 +21,7 @@ MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machin
 | 
			
		||||
	audio_producer_(machines_, machines_mutex_),
 | 
			
		||||
	joystick_machine_(machines_),
 | 
			
		||||
	keyboard_machine_(machines_),
 | 
			
		||||
	media_target_(machines_),
 | 
			
		||||
	media_change_observer_(machines_)
 | 
			
		||||
{
 | 
			
		||||
	media_target_(machines_) {
 | 
			
		||||
	timed_machine_.set_delegate(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -35,13 +29,13 @@ Activity::Source *MultiMachine::activity_source() {
 | 
			
		||||
	return nullptr; // TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define Provider(type, name, member)			\
 | 
			
		||||
	type *MultiMachine::name() {				\
 | 
			
		||||
		if(has_picked_) {						\
 | 
			
		||||
#define Provider(type, name, member)	\
 | 
			
		||||
	type *MultiMachine::name() {	\
 | 
			
		||||
		if(has_picked_) {	\
 | 
			
		||||
			return machines_.front()->name();	\
 | 
			
		||||
		} else {								\
 | 
			
		||||
			return &member;						\
 | 
			
		||||
		}										\
 | 
			
		||||
		} else {	\
 | 
			
		||||
			return &member;	\
 | 
			
		||||
		}	\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
Provider(Configurable::Device, configurable_device, configurable_)
 | 
			
		||||
@@ -51,7 +45,6 @@ Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
 | 
			
		||||
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
 | 
			
		||||
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
 | 
			
		||||
Provider(MachineTypes::MediaTarget, media_target, media_target_)
 | 
			
		||||
Provider(MachineTypes::MediaChangeObserver, media_change_observer, media_change_observer_)
 | 
			
		||||
 | 
			
		||||
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
 | 
			
		||||
	// TODO.
 | 
			
		||||
@@ -66,16 +59,15 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
 | 
			
		||||
		(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MultiMachine::did_run_machines(MultiTimedMachine &) {
 | 
			
		||||
void MultiMachine::did_run_machines(MultiTimedMachine *) {
 | 
			
		||||
	std::lock_guard machines_lock(machines_mutex_);
 | 
			
		||||
 | 
			
		||||
	if constexpr (Logger::InfoEnabled) {
 | 
			
		||||
		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,22 +6,24 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef MultiMachine_hpp
 | 
			
		||||
#define MultiMachine_hpp
 | 
			
		||||
 | 
			
		||||
#include "Machines/DynamicMachine.hpp"
 | 
			
		||||
#include "../../../Machines/DynamicMachine.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp"
 | 
			
		||||
#include "Analyser/Dynamic/MultiMachine/Implementation/MultiMediaTarget.hpp"
 | 
			
		||||
#include "Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Implementation/MultiConfigurable.hpp"
 | 
			
		||||
#include "Implementation/MultiProducer.hpp"
 | 
			
		||||
#include "Implementation/MultiJoystickMachine.hpp"
 | 
			
		||||
#include "Implementation/MultiKeyboardMachine.hpp"
 | 
			
		||||
#include "Implementation/MultiMediaTarget.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Dynamic {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Dynamic {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides the same interface as to a single machine, while multiplexing all
 | 
			
		||||
@@ -38,46 +40,47 @@ namespace Analyser::Dynamic {
 | 
			
		||||
	the others in the set, that machine stops running.
 | 
			
		||||
*/
 | 
			
		||||
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
 | 
			
		||||
public:
 | 
			
		||||
	/*!
 | 
			
		||||
		Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
			
		||||
		requesting this class as a proxy.
 | 
			
		||||
	public:
 | 
			
		||||
		/*!
 | 
			
		||||
			Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
 | 
			
		||||
			requesting this class as a proxy.
 | 
			
		||||
 | 
			
		||||
		@returns @c true if the multimachine would discard all but the first machine in this list;
 | 
			
		||||
			@c false otherwise.
 | 
			
		||||
	*/
 | 
			
		||||
	static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &);
 | 
			
		||||
	MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&);
 | 
			
		||||
			@returns @c true if the multimachine would discard all but the first machine in this list;
 | 
			
		||||
				@c false otherwise.
 | 
			
		||||
		*/
 | 
			
		||||
		static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
 | 
			
		||||
		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
 | 
			
		||||
 | 
			
		||||
	Activity::Source *activity_source() final;
 | 
			
		||||
	Configurable::Device *configurable_device() final;
 | 
			
		||||
	MachineTypes::TimedMachine *timed_machine() final;
 | 
			
		||||
	MachineTypes::ScanProducer *scan_producer() final;
 | 
			
		||||
	MachineTypes::AudioProducer *audio_producer() final;
 | 
			
		||||
	MachineTypes::JoystickMachine *joystick_machine() final;
 | 
			
		||||
	MachineTypes::KeyboardMachine *keyboard_machine() final;
 | 
			
		||||
	MachineTypes::MouseMachine *mouse_machine() final;
 | 
			
		||||
	MachineTypes::MediaTarget *media_target() final;
 | 
			
		||||
	MachineTypes::MediaChangeObserver *media_change_observer() final;
 | 
			
		||||
	void *raw_pointer() final;
 | 
			
		||||
		Activity::Source *activity_source() final;
 | 
			
		||||
		Configurable::Device *configurable_device() final;
 | 
			
		||||
		MachineTypes::TimedMachine *timed_machine() final;
 | 
			
		||||
		MachineTypes::ScanProducer *scan_producer() final;
 | 
			
		||||
		MachineTypes::AudioProducer *audio_producer() final;
 | 
			
		||||
		MachineTypes::JoystickMachine *joystick_machine() final;
 | 
			
		||||
		MachineTypes::KeyboardMachine *keyboard_machine() final;
 | 
			
		||||
		MachineTypes::MouseMachine *mouse_machine() final;
 | 
			
		||||
		MachineTypes::MediaTarget *media_target() final;
 | 
			
		||||
		void *raw_pointer() final;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void did_run_machines(MultiTimedMachine &) final;
 | 
			
		||||
	private:
 | 
			
		||||
		void did_run_machines(MultiTimedMachine *) final;
 | 
			
		||||
 | 
			
		||||
	std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
			
		||||
	std::recursive_mutex machines_mutex_;
 | 
			
		||||
		std::vector<std::unique_ptr<DynamicMachine>> machines_;
 | 
			
		||||
		std::recursive_mutex machines_mutex_;
 | 
			
		||||
 | 
			
		||||
	MultiConfigurable configurable_;
 | 
			
		||||
	MultiTimedMachine timed_machine_;
 | 
			
		||||
	MultiScanProducer scan_producer_;
 | 
			
		||||
	MultiAudioProducer audio_producer_;
 | 
			
		||||
	MultiJoystickMachine joystick_machine_;
 | 
			
		||||
	MultiKeyboardMachine keyboard_machine_;
 | 
			
		||||
	MultiMediaTarget media_target_;
 | 
			
		||||
	MultiMediaChangeObserver media_change_observer_;
 | 
			
		||||
		MultiConfigurable configurable_;
 | 
			
		||||
		MultiTimedMachine timed_machine_;
 | 
			
		||||
		MultiScanProducer scan_producer_;
 | 
			
		||||
		MultiAudioProducer audio_producer_;
 | 
			
		||||
		MultiJoystickMachine joystick_machine_;
 | 
			
		||||
		MultiKeyboardMachine keyboard_machine_;
 | 
			
		||||
		MultiMediaTarget media_target_;
 | 
			
		||||
 | 
			
		||||
	void pick_first();
 | 
			
		||||
	bool has_picked_ = false;
 | 
			
		||||
		void pick_first();
 | 
			
		||||
		bool has_picked_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* MultiMachine_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,8 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Machines_h
 | 
			
		||||
#define Machines_h
 | 
			
		||||
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
 | 
			
		||||
@@ -17,8 +18,6 @@ enum class Machine {
 | 
			
		||||
	Atari2600,
 | 
			
		||||
	AtariST,
 | 
			
		||||
	Amiga,
 | 
			
		||||
	Archimedes,
 | 
			
		||||
	BBCMicro,
 | 
			
		||||
	ColecoVision,
 | 
			
		||||
	Electron,
 | 
			
		||||
	Enterprise,
 | 
			
		||||
@@ -26,11 +25,11 @@ enum class Machine {
 | 
			
		||||
	MasterSystem,
 | 
			
		||||
	MSX,
 | 
			
		||||
	Oric,
 | 
			
		||||
	Plus4,
 | 
			
		||||
	PCCompatible,
 | 
			
		||||
	Vic20,
 | 
			
		||||
	ZX8081,
 | 
			
		||||
	ZXSpectrum,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Machines_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,22 +8,21 @@
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Numeric/CRC.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Numeric/CRC.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 
			
		||||
	auto catalogue = std::make_unique<Catalogue>();
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Single, disk);
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(false, disk);
 | 
			
		||||
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *const names = parser.sector(0, 0, 0);
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *const details = parser.sector(0, 0, 1);
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
 | 
			
		||||
 | 
			
		||||
	if(!names || !details) return nullptr;
 | 
			
		||||
	if(names->samples.empty() || details->samples.empty()) return nullptr;
 | 
			
		||||
@@ -49,39 +48,27 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
			
		||||
		char name[10];
 | 
			
		||||
		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
 | 
			
		||||
		new_file.name = name;
 | 
			
		||||
		new_file.load_address = uint32_t(
 | 
			
		||||
			details->samples[0][file_offset] |
 | 
			
		||||
			(details->samples[0][file_offset+1] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0x0c) << 14)
 | 
			
		||||
		);
 | 
			
		||||
		new_file.execution_address = uint32_t(
 | 
			
		||||
			details->samples[0][file_offset+2] |
 | 
			
		||||
			(details->samples[0][file_offset+3] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0xc0) << 10)
 | 
			
		||||
		);
 | 
			
		||||
		new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
 | 
			
		||||
		new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
 | 
			
		||||
		if(names->samples[0][file_offset + 7] & 0x80) {
 | 
			
		||||
			// File is locked; it may not be altered or deleted.
 | 
			
		||||
			new_file.flags |= File::Flags::Locked;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto data_length = long(
 | 
			
		||||
			details->samples[0][file_offset+4] |
 | 
			
		||||
			(details->samples[0][file_offset+5] << 8) |
 | 
			
		||||
			((details->samples[0][file_offset+6]&0x30) << 12)
 | 
			
		||||
		);
 | 
			
		||||
		long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
 | 
			
		||||
		int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
 | 
			
		||||
		new_file.data.reserve(size_t(data_length));
 | 
			
		||||
 | 
			
		||||
		if(start_sector < 2) continue;
 | 
			
		||||
		while(data_length > 0) {
 | 
			
		||||
			const uint8_t sector = uint8_t(start_sector % 10);
 | 
			
		||||
			const uint8_t track = uint8_t(start_sector / 10);
 | 
			
		||||
			++start_sector;
 | 
			
		||||
			uint8_t sector = uint8_t(start_sector % 10);
 | 
			
		||||
			uint8_t track = uint8_t(start_sector / 10);
 | 
			
		||||
			start_sector++;
 | 
			
		||||
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *next_sector = parser.sector(0, track, sector);
 | 
			
		||||
			Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
 | 
			
		||||
			if(!next_sector) break;
 | 
			
		||||
 | 
			
		||||
			const long length_from_sector = std::min(data_length, 256l);
 | 
			
		||||
			long length_from_sector = std::min(data_length, 256l);
 | 
			
		||||
			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
 | 
			
		||||
			data_length -= length_from_sector;
 | 
			
		||||
		}
 | 
			
		||||
@@ -97,59 +84,36 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
 | 
			
		||||
*/
 | 
			
		||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	auto catalogue = std::make_unique<Catalogue>();
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
 | 
			
		||||
	// Grab the second half of the free-space map because it has the boot option in it.
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
 | 
			
		||||
	if(!free_space_map_second_half) return nullptr;
 | 
			
		||||
	catalogue->has_large_sectors = free_space_map_second_half->samples[0].size() == 1024;
 | 
			
		||||
 | 
			
		||||
	// Possibility: this is a large-sector disk with an old-style free space map. In which
 | 
			
		||||
	// case the above just read the start of the root directory.
 | 
			
		||||
	uint8_t first_directory_sector = 2;
 | 
			
		||||
	if(catalogue->has_large_sectors && !memcmp(&free_space_map_second_half->samples[0][1], "Hugo", 4)) {
 | 
			
		||||
		free_space_map_second_half = parser.sector(0, 0, 0);
 | 
			
		||||
		if(!free_space_map_second_half) return nullptr;
 | 
			
		||||
		first_directory_sector = 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<uint8_t> root_directory;
 | 
			
		||||
	root_directory.reserve(catalogue->has_large_sectors ? 2*1024 : 5*256);
 | 
			
		||||
 | 
			
		||||
	for(uint8_t c = first_directory_sector; c < first_directory_sector + (catalogue->has_large_sectors ? 2 : 5); c++) {
 | 
			
		||||
		const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
 | 
			
		||||
	root_directory.reserve(5 * 256);
 | 
			
		||||
	for(uint8_t c = 2; c < 7; c++) {
 | 
			
		||||
		const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
 | 
			
		||||
		if(!sector) return nullptr;
 | 
			
		||||
		root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check for end of directory marker.
 | 
			
		||||
	if(root_directory[catalogue->has_large_sectors ? 0x7d7 : 0x4cb]) return nullptr;
 | 
			
		||||
	// Quick sanity checks.
 | 
			
		||||
	if(root_directory[0x4cb]) return nullptr;
 | 
			
		||||
	if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
 | 
			
		||||
	if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
 | 
			
		||||
 | 
			
		||||
	// Check for both directory identifiers.
 | 
			
		||||
	const uint8_t *const start_id = &root_directory[1];
 | 
			
		||||
	const uint8_t *const end_id = &root_directory[root_directory.size() - 5];
 | 
			
		||||
	catalogue->is_hugo = !memcmp(start_id, "Hugo", 4) && !memcmp(end_id, "Hugo", 4);
 | 
			
		||||
	const bool is_nick = !memcmp(start_id, "Nick", 4) && !memcmp(end_id, "Nick", 4);
 | 
			
		||||
	if(!catalogue->is_hugo && !is_nick) {
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!catalogue->has_large_sectors) {
 | 
			
		||||
		// TODO: I don't know where the boot option rests with large sectors.
 | 
			
		||||
		switch(free_space_map_second_half->samples[0][0xfd]) {
 | 
			
		||||
			default: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
			
		||||
			case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
			
		||||
			case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
			
		||||
			case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
			
		||||
		}
 | 
			
		||||
	switch(free_space_map_second_half->samples[0][0xfd]) {
 | 
			
		||||
		default: catalogue->bootOption = Catalogue::BootOption::None;		break;
 | 
			
		||||
		case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT;	break;
 | 
			
		||||
		case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT;		break;
 | 
			
		||||
		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parse the root directory, at least.
 | 
			
		||||
	const std::size_t directory_extent = catalogue->has_large_sectors ? 0x7d7 : 0x4cb;
 | 
			
		||||
	for(std::size_t file_offset = 0x005; file_offset < directory_extent; file_offset += 0x1a) {
 | 
			
		||||
	for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
 | 
			
		||||
		// Obtain the name, which will be at most ten characters long, and will
 | 
			
		||||
		// be terminated by either a NULL character or a \r.
 | 
			
		||||
		char name[11]{};
 | 
			
		||||
		char name[11];
 | 
			
		||||
		std::size_t c = 0;
 | 
			
		||||
		for(; c < 10; c++) {
 | 
			
		||||
			const char next = root_directory[file_offset + c] & 0x7f;
 | 
			
		||||
@@ -158,9 +122,8 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
			
		||||
		}
 | 
			
		||||
		name[c] = '\0';
 | 
			
		||||
 | 
			
		||||
		// An empty name implies the directory has ended; files are always listed in case-insensitive
 | 
			
		||||
		// sorted order, with that list being terminated by a '\0'.
 | 
			
		||||
		if(name[0] == '\0') break;
 | 
			
		||||
		// Skip if the name is empty.
 | 
			
		||||
		if(name[0] == '\0') continue;
 | 
			
		||||
 | 
			
		||||
		// Populate a file then.
 | 
			
		||||
		File new_file;
 | 
			
		||||
@@ -203,36 +166,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
 | 
			
		||||
 | 
			
		||||
		new_file.data.reserve(size);
 | 
			
		||||
		while(new_file.data.size() < size) {
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *const sector =
 | 
			
		||||
				parser.sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
 | 
			
		||||
			const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
 | 
			
		||||
			if(!sector) break;
 | 
			
		||||
 | 
			
		||||
			const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
 | 
			
		||||
			new_file.data.insert(
 | 
			
		||||
				new_file.data.end(),
 | 
			
		||||
				sector->samples[0].begin(),
 | 
			
		||||
				sector->samples[0].begin() + ssize_t(length_from_sector)
 | 
			
		||||
			);
 | 
			
		||||
			new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
 | 
			
		||||
			++start_sector;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		catalogue->files.push_back(std::move(new_file));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Include the directory title.
 | 
			
		||||
	const char *title, *name;
 | 
			
		||||
	if(catalogue->has_large_sectors) {
 | 
			
		||||
		title = reinterpret_cast<const char *>(&root_directory[0x7dd]);
 | 
			
		||||
		name = reinterpret_cast<const char *>(&root_directory[0x7f0]);
 | 
			
		||||
	} else {
 | 
			
		||||
		title = reinterpret_cast<const char *>(&root_directory[0x4d9]);
 | 
			
		||||
		name = reinterpret_cast<const char *>(&root_directory[0x4cc]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	catalogue->name = std::string(title, strnlen(title, 19));
 | 
			
		||||
	if(catalogue->name.empty() || catalogue->name == "$") {
 | 
			
		||||
		catalogue->name = std::string(name, strnlen(name, 10));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return catalogue;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,18 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Acorn_Disk_hpp
 | 
			
		||||
#define StaticAnalyser_Acorn_Disk_hpp
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
 | 
			
		||||
struct Catalogue {
 | 
			
		||||
	bool is_hugo = false;
 | 
			
		||||
	bool has_large_sectors = false;
 | 
			
		||||
	std::string name;
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	enum class BootOption {
 | 
			
		||||
@@ -27,7 +28,11 @@ struct Catalogue {
 | 
			
		||||
	} bootOption;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Disk_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,16 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Acorn_File_hpp
 | 
			
		||||
#define StaticAnalyser_Acorn_File_hpp
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
	std::string name;
 | 
			
		||||
@@ -59,3 +61,7 @@ struct File {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* File_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -12,45 +12,25 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Numeric/StringSimilarity.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
bool is_basic(const File &file) {
 | 
			
		||||
	std::size_t pointer = 0;
 | 
			
		||||
	const uint8_t *const data = file.data.data();
 | 
			
		||||
	const std::size_t data_size = file.data.size();
 | 
			
		||||
	while(true) {
 | 
			
		||||
		if(pointer >= data_size-1 || data[pointer] != 0x0d) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		if((data[pointer+1]&0x7f) == 0x7f) break;
 | 
			
		||||
		pointer += data[pointer+3];
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
		AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// Only one mapped item is allowed.
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// Cartridges must be 8 or 16 kb in size.
 | 
			
		||||
		// which must be 8 or 16 kb in size
 | 
			
		||||
		const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
 | 
			
		||||
 | 
			
		||||
		// Check copyright string.
 | 
			
		||||
		// is a copyright string present?
 | 
			
		||||
		const uint8_t copyright_offset = segment.data[7];
 | 
			
		||||
		if(
 | 
			
		||||
			segment.data[copyright_offset] != 0x00 ||
 | 
			
		||||
@@ -59,16 +39,16 @@ AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
 | 
			
		||||
			segment.data[copyright_offset+3] != 0x29
 | 
			
		||||
		) continue;
 | 
			
		||||
 | 
			
		||||
		// Check language entry point.
 | 
			
		||||
		// is the language entry point valid?
 | 
			
		||||
		if(!(
 | 
			
		||||
			(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
 | 
			
		||||
			(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
 | 
			
		||||
			)) continue;
 | 
			
		||||
 | 
			
		||||
		// Check service entry point.
 | 
			
		||||
		// is the service entry point valid?
 | 
			
		||||
		if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
 | 
			
		||||
 | 
			
		||||
		// Probability of a random binary blob that isn't an Acorn ROM proceeding to here:
 | 
			
		||||
		// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
 | 
			
		||||
		//		1/(2^32) *
 | 
			
		||||
		//		( ((2^24)-1)/(2^24)*(1/4)		+		1/(2^24)	) *
 | 
			
		||||
		//		1/4
 | 
			
		||||
@@ -79,96 +59,73 @@ AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
 | 
			
		||||
	return acorn_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	auto targetElectron = std::make_unique<ElectronTarget>();
 | 
			
		||||
	auto targetBBC = std::make_unique<BBCMicroTarget>();
 | 
			
		||||
	auto targetArchimedes = std::make_unique<ArchimedesTarget>();
 | 
			
		||||
	int bbc_hits = 0;
 | 
			
		||||
	int electron_hits = 0;
 | 
			
		||||
	bool format_prefers_bbc = false;
 | 
			
		||||
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 targets.
 | 
			
		||||
	targetElectron->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
	targetBBC->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
	// strip out inappropriate cartridges
 | 
			
		||||
	target->media.cartridges = AcornCartridgesFrom(media.cartridges);
 | 
			
		||||
 | 
			
		||||
	// If there are tapes, attempt to get data from the first.
 | 
			
		||||
	// if there are any tapes, attempt to get data from the first
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> files = GetFiles(*serialiser);
 | 
			
		||||
		std::vector<File> files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
 | 
			
		||||
		// Continue only if there are any files.
 | 
			
		||||
		// continue if there are any files
 | 
			
		||||
		if(!files.empty()) {
 | 
			
		||||
			bool is_basic = true;
 | 
			
		||||
 | 
			
		||||
			// If a file is execute-only, that means *RUN.
 | 
			
		||||
			if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
 | 
			
		||||
 | 
			
		||||
			// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
 | 
			
		||||
			// so that's also justification to *RUN
 | 
			
		||||
			std::size_t pointer = 0;
 | 
			
		||||
			uint8_t *const data = &files.front().data[0];
 | 
			
		||||
			const std::size_t data_size = files.front().data.size();
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(pointer >= data_size-1 || data[pointer] != 13) {
 | 
			
		||||
					is_basic = false;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				if((data[pointer+1]&0x7f) == 0x7f) break;
 | 
			
		||||
				pointer += data[pointer+3];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Inspect first file. If it's protected or doesn't look like BASIC
 | 
			
		||||
			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 
			
		||||
			targetElectron->loading_command =
 | 
			
		||||
				(files.front().flags & File::Flags::ExecuteOnly) || !is_basic(files.front()) ? "*RUN\n" : "CHAIN\"\"\n";
 | 
			
		||||
			targetElectron->media.tapes = media.tapes;
 | 
			
		||||
			target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
 | 
			
		||||
 | 
			
		||||
			// TODO: my BBC Micro doesn't yet support tapes; evaluate here in the future.
 | 
			
		||||
			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.
 | 
			
		||||
 | 
			
		||||
			// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
 | 
			
		||||
			targetElectron->media.disks = media.disks;
 | 
			
		||||
			targetElectron->has_dfs = bool(dfs_catalogue);
 | 
			
		||||
			targetElectron->has_pres_adfs = bool(adfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// BBC: only the 1770 DFS is currently supported, so use that.
 | 
			
		||||
			targetBBC->media.disks = media.disks;
 | 
			
		||||
			targetBBC->has_1770dfs = bool(dfs_catalogue);
 | 
			
		||||
			targetBBC->has_adfs = bool(adfs_catalogue);
 | 
			
		||||
			// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
 | 
			
		||||
			target->media.disks = media.disks;
 | 
			
		||||
			target->has_dfs = bool(dfs_catalogue);
 | 
			
		||||
			target->has_pres_adfs = bool(adfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// Check whether a simple shift+break will do for loading this disk.
 | 
			
		||||
			const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
			
		||||
			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
 | 
			
		||||
			if(bootOption != Catalogue::BootOption::None) {
 | 
			
		||||
				targetBBC->should_shift_restart = targetElectron->should_shift_restart = true;
 | 
			
		||||
				target->should_shift_restart = true;
 | 
			
		||||
			} else {
 | 
			
		||||
				// Otherwise: if there's only one BASIC program then chain it.
 | 
			
		||||
				// Failing that, do a *CAT to be communicative.
 | 
			
		||||
 | 
			
		||||
				const File *sole_basic_file = nullptr;
 | 
			
		||||
				for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
 | 
			
		||||
					if(is_basic(file)) {
 | 
			
		||||
						if(!sole_basic_file) {
 | 
			
		||||
							sole_basic_file = &file;
 | 
			
		||||
						} else {
 | 
			
		||||
							sole_basic_file = nullptr;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				targetBBC->loading_command = targetElectron->loading_command =
 | 
			
		||||
					sole_basic_file ? "CHAIN \"" + sole_basic_file->name + "\"\n" : "*CAT\n";
 | 
			
		||||
				target->loading_command = "*CAT\n";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Add a slight preference for the BBC over the Electron, all else being equal, if this is a DFS floppy.
 | 
			
		||||
			format_prefers_bbc = bool(dfs_catalogue);
 | 
			
		||||
 | 
			
		||||
			// Check whether adding the AP6 ROM is justified.
 | 
			
		||||
			// For now this is an incredibly dense text search;
 | 
			
		||||
			// if any of the commands that aren't usually present
 | 
			
		||||
			// on a stock Electron are here, add the AP6 ROM and
 | 
			
		||||
			// some sideways RAM such that the SR commands are useful.
 | 
			
		||||
			for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
 | 
			
		||||
				// Electron: check whether adding the AP6 ROM is justified.
 | 
			
		||||
				// For now this is an incredibly dense text search;
 | 
			
		||||
				// if any of the commands that aren't usually present
 | 
			
		||||
				// on a stock Electron are here, add the AP6 ROM and
 | 
			
		||||
				// some sideways RAM such that the SR commands are useful.
 | 
			
		||||
				for(const auto &command: {
 | 
			
		||||
					"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
 | 
			
		||||
					"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
 | 
			
		||||
@@ -176,97 +133,10 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
 | 
			
		||||
					"VERIFY", "ZERO"
 | 
			
		||||
				}) {
 | 
			
		||||
					if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
 | 
			
		||||
						targetElectron->has_ap6_rom = true;
 | 
			
		||||
						targetElectron->has_sideways_ram = true;
 | 
			
		||||
						target->has_ap6_rom = true;
 | 
			
		||||
						target->has_sideways_ram = true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
 | 
			
		||||
				// Also currently a dense search.
 | 
			
		||||
 | 
			
		||||
				const auto hits = [&](const std::initializer_list<uint16_t> collection) {
 | 
			
		||||
					int hits = 0;
 | 
			
		||||
					for(const auto address: collection) {
 | 
			
		||||
						const uint8_t sta_address[3] = {
 | 
			
		||||
							0x8d, uint8_t(address & 0xff), uint8_t(address >> 8)
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						if(std::search(
 | 
			
		||||
							file.data.begin(), file.data.end(),
 | 
			
		||||
							std::begin(sta_address), std::end(sta_address)
 | 
			
		||||
						) != file.data.end()) {
 | 
			
		||||
							++hits;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// I think I'll want std::ranges::contains_subrange if/when building for C++23.
 | 
			
		||||
					}
 | 
			
		||||
					return hits;
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				bbc_hits += hits({
 | 
			
		||||
					// The video control registers.
 | 
			
		||||
					0xfe20, 0xfe21,
 | 
			
		||||
 | 
			
		||||
					// The system VIA.
 | 
			
		||||
					0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, 0xfe46, 0xfe47,
 | 
			
		||||
					0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f,
 | 
			
		||||
 | 
			
		||||
					// The user VIA.
 | 
			
		||||
					0xfe60, 0xfe61, 0xfe62, 0xfe63, 0xfe64, 0xfe65, 0xfe66, 0xfe67,
 | 
			
		||||
					0xfe68, 0xfe69, 0xfe6a, 0xfe6b, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
 | 
			
		||||
				});
 | 
			
		||||
				// BASIC for "MODE7".
 | 
			
		||||
				static constexpr uint8_t mode7[] = {0xeb, 0x37};
 | 
			
		||||
				bbc_hits += std::search(
 | 
			
		||||
							file.data.begin(), file.data.end(),
 | 
			
		||||
							std::begin(mode7), std::end(mode7)
 | 
			
		||||
						) != file.data.end();
 | 
			
		||||
 | 
			
		||||
				electron_hits += hits({
 | 
			
		||||
					// ULA addresses that aren't also the BBC's CRTC.
 | 
			
		||||
					0xfe03, 0xfe04, 0xfe05,
 | 
			
		||||
					0xfe06, 0xfe07, 0xfe08,
 | 
			
		||||
					0xfe09, 0xfe0a, 0xfe0b,
 | 
			
		||||
					0xfe0c, 0xfe0d, 0xfe0e,
 | 
			
		||||
					0xfe0f,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		} 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).
 | 
			
		||||
				static constexpr char read[] = "read";
 | 
			
		||||
				static 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;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -274,41 +144,28 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
 | 
			
		||||
	// 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()) {
 | 
			
		||||
		targetElectron->has_pres_adfs = false;	// To override a floppy selection, if one was made.
 | 
			
		||||
		targetElectron->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.
 | 
			
		||||
		targetElectron->has_ap6_rom = true;
 | 
			
		||||
		targetElectron->has_sideways_ram = true;
 | 
			
		||||
		target->has_ap6_rom = true;
 | 
			
		||||
		target->has_sideways_ram = true;
 | 
			
		||||
 | 
			
		||||
		targetElectron->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 = targetElectron->media.mass_storage_devices.front()->get_block(1);
 | 
			
		||||
		const auto sector = target->media.mass_storage_devices.front()->get_block(1);
 | 
			
		||||
		if(sector[0xfd]) {
 | 
			
		||||
			targetElectron->should_shift_restart = true;
 | 
			
		||||
			target->should_shift_restart = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			targetElectron->loading_command = "*CAT\n";
 | 
			
		||||
			target->loading_command = "*CAT\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
	if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
 | 
			
		||||
		if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
 | 
			
		||||
			targets.push_back(std::move(targetBBC));
 | 
			
		||||
		} else {
 | 
			
		||||
			targets.push_back(std::move(targetElectron));
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if(!targetElectron->media.empty()) {
 | 
			
		||||
			targets.push_back(std::move(targetElectron));
 | 
			
		||||
		} else if(!targetBBC->media.empty()) {
 | 
			
		||||
			targets.push_back(std::move(targetBBC));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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 "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* AcornAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -10,68 +10,70 @@
 | 
			
		||||
 | 
			
		||||
#include <deque>
 | 
			
		||||
 | 
			
		||||
#include "Numeric/CRC.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Acorn.hpp"
 | 
			
		||||
#include "../../../Numeric/CRC.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Acorn;
 | 
			
		||||
 | 
			
		||||
static std::unique_ptr<File::Chunk> GetNextChunk(
 | 
			
		||||
	Storage::Tape::TapeSerialiser &serialiser,
 | 
			
		||||
	Storage::Tape::Acorn::Parser &parser
 | 
			
		||||
) {
 | 
			
		||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
 | 
			
		||||
	auto new_chunk = std::make_unique<File::Chunk>();
 | 
			
		||||
	int shift_register = 0;
 | 
			
		||||
 | 
			
		||||
	// TODO: move this into the parser
 | 
			
		||||
	const auto find = [&](int target) {
 | 
			
		||||
		while(!serialiser.is_at_end() && (shift_register != target)) {
 | 
			
		||||
			shift_register = (shift_register >> 1) | (parser.get_next_bit(serialiser) << 9);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
// TODO: move this into the parser
 | 
			
		||||
#define shift()	shift_register = (shift_register >> 1) |  (parser.get_next_bit(tape) << 9)
 | 
			
		||||
 | 
			
		||||
	// find next area of high tone
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x3ff)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// find next 0x2a (swallowing stop bit)
 | 
			
		||||
	while(!tape->is_at_end() && (shift_register != 0x254)) {
 | 
			
		||||
		shift();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#undef shift
 | 
			
		||||
 | 
			
		||||
	// Find first sync byte that follows high tone.
 | 
			
		||||
	find(0x3ff);
 | 
			
		||||
	find(0x254);	// i.e. 0x2a wrapped in a 1 start bit and a 0 stop bit.
 | 
			
		||||
	parser.reset_crc();
 | 
			
		||||
	parser.reset_error_flag();
 | 
			
		||||
 | 
			
		||||
	// Read name.
 | 
			
		||||
	char name[11]{};
 | 
			
		||||
	// read out name
 | 
			
		||||
	char name[11];
 | 
			
		||||
	std::size_t name_ptr = 0;
 | 
			
		||||
	while(!serialiser.is_at_end() && name_ptr < sizeof(name)) {
 | 
			
		||||
		name[name_ptr] = char(parser.get_next_byte(serialiser));
 | 
			
		||||
	while(!tape->is_at_end() && name_ptr < sizeof(name)) {
 | 
			
		||||
		name[name_ptr] = char(parser.get_next_byte(tape));
 | 
			
		||||
		if(!name[name_ptr]) break;
 | 
			
		||||
		++name_ptr;
 | 
			
		||||
	}
 | 
			
		||||
	name[sizeof(name)-1] = '\0';
 | 
			
		||||
	new_chunk->name = name;
 | 
			
		||||
 | 
			
		||||
	// Read rest of header fields.
 | 
			
		||||
	new_chunk->load_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
	new_chunk->execution_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
	new_chunk->block_number = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
	new_chunk->block_length = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
	new_chunk->block_flag = uint8_t(parser.get_next_byte(serialiser));
 | 
			
		||||
	new_chunk->next_address = uint32_t(parser.get_next_word(serialiser));
 | 
			
		||||
	// addresses
 | 
			
		||||
	new_chunk->load_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
	new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
	new_chunk->block_number = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_length = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
 | 
			
		||||
	new_chunk->next_address = uint32_t(parser.get_next_word(tape));
 | 
			
		||||
 | 
			
		||||
	const auto matched_crc = [&]() {
 | 
			
		||||
		const uint16_t calculated_crc = parser.get_crc();
 | 
			
		||||
		uint16_t stored_crc = uint16_t(parser.get_next_short(serialiser));
 | 
			
		||||
		stored_crc = uint16_t((stored_crc >> 8) | (stored_crc << 8));
 | 
			
		||||
		return stored_crc == calculated_crc;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	new_chunk->header_crc_matched = matched_crc();
 | 
			
		||||
	uint16_t calculated_header_crc = parser.get_crc();
 | 
			
		||||
	uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
	stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
 | 
			
		||||
	new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
 | 
			
		||||
 | 
			
		||||
	if(!new_chunk->header_crc_matched) return nullptr;
 | 
			
		||||
 | 
			
		||||
	// Bit 6 of the block flag means 'empty block'; allow it to override declared block length.
 | 
			
		||||
	parser.reset_crc();
 | 
			
		||||
	new_chunk->data.reserve(new_chunk->block_length);
 | 
			
		||||
	for(int c = 0; c < new_chunk->block_length; c++) {
 | 
			
		||||
		new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
 | 
			
		||||
		parser.reset_crc();
 | 
			
		||||
		new_chunk->data.reserve(new_chunk->block_length);
 | 
			
		||||
		for(int c = 0; c < new_chunk->block_length; c++) {
 | 
			
		||||
			new_chunk->data.push_back(uint8_t(parser.get_next_byte(serialiser)));
 | 
			
		||||
		}
 | 
			
		||||
		new_chunk->data_crc_matched = matched_crc();
 | 
			
		||||
		uint16_t calculated_data_crc = parser.get_crc();
 | 
			
		||||
		uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
 | 
			
		||||
		stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
 | 
			
		||||
		new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
 | 
			
		||||
	} else {
 | 
			
		||||
		new_chunk->data_crc_matched = true;
 | 
			
		||||
	}
 | 
			
		||||
@@ -80,62 +82,67 @@ static std::unique_ptr<File::Chunk> GetNextChunk(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
 | 
			
		||||
	// Find next chunk with a block number of 0.
 | 
			
		||||
	while(!chunks.empty() && chunks.front().block_number) {
 | 
			
		||||
	// find next chunk with a block number of 0
 | 
			
		||||
	while(chunks.size() && chunks.front().block_number) {
 | 
			
		||||
		chunks.pop_front();
 | 
			
		||||
	}
 | 
			
		||||
	if(chunks.empty()) return nullptr;
 | 
			
		||||
 | 
			
		||||
	// Accumulate sequential blocks until end-of-file bit is set.
 | 
			
		||||
	if(!chunks.size()) return nullptr;
 | 
			
		||||
 | 
			
		||||
	// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
 | 
			
		||||
	auto file = std::make_unique<File>();
 | 
			
		||||
 | 
			
		||||
	uint16_t block_number = 0;
 | 
			
		||||
	while(!chunks.empty()) {
 | 
			
		||||
 | 
			
		||||
	while(chunks.size()) {
 | 
			
		||||
		if(chunks.front().block_number != block_number) return nullptr;
 | 
			
		||||
 | 
			
		||||
		const bool was_last = chunks.front().block_flag & 0x80;
 | 
			
		||||
		bool was_last = chunks.front().block_flag & 0x80;
 | 
			
		||||
		file->chunks.push_back(chunks.front());
 | 
			
		||||
		chunks.pop_front();
 | 
			
		||||
		++block_number;
 | 
			
		||||
		block_number++;
 | 
			
		||||
 | 
			
		||||
		if(was_last) break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Grab metadata flags.
 | 
			
		||||
	// accumulate total data, copy flags appropriately
 | 
			
		||||
	file->name = file->chunks.front().name;
 | 
			
		||||
	file->load_address = file->chunks.front().load_address;
 | 
			
		||||
	file->execution_address = file->chunks.front().execution_address;
 | 
			
		||||
	// I think the final chunk's flags are the ones that count; TODO: check.
 | 
			
		||||
	if(file->chunks.back().block_flag & 0x01) {
 | 
			
		||||
		// File is locked i.e. for execution only.
 | 
			
		||||
		// File is locked, which in more generalised terms means it is
 | 
			
		||||
		// for execution only.
 | 
			
		||||
		file->flags |= File::Flags::ExecuteOnly;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy data into a single big block.
 | 
			
		||||
	file->data.reserve(file->chunks.size() * 256);
 | 
			
		||||
	for(auto &chunk : file->chunks) {
 | 
			
		||||
	// copy all data into a single big block
 | 
			
		||||
	for(File::Chunk chunk : file->chunks) {
 | 
			
		||||
		file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Acorn::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Storage::Tape::Acorn::Parser parser;
 | 
			
		||||
 | 
			
		||||
	// Read all chunks.
 | 
			
		||||
	// populate chunk list
 | 
			
		||||
	std::deque<File::Chunk> chunk_list;
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		const std::unique_ptr<File::Chunk> chunk = GetNextChunk(serialiser, parser);
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
 | 
			
		||||
		if(chunk) {
 | 
			
		||||
			chunk_list.push_back(std::move(*chunk));
 | 
			
		||||
			chunk_list.push_back(*chunk);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert to files.
 | 
			
		||||
	// decompose into file list
 | 
			
		||||
	std::vector<File> file_list;
 | 
			
		||||
	while(!chunk_list.empty()) {
 | 
			
		||||
		const std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
			
		||||
 | 
			
		||||
	while(chunk_list.size()) {
 | 
			
		||||
		std::unique_ptr<File> next_file = GetNextFile(chunk_list);
 | 
			
		||||
		if(next_file) {
 | 
			
		||||
			file_list.push_back(std::move(*next_file));
 | 
			
		||||
			file_list.push_back(*next_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,22 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Acorn_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_Acorn_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Tape_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,16 +6,18 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Acorn_Target_h
 | 
			
		||||
#define Analyser_Static_Acorn_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Acorn {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Acorn {
 | 
			
		||||
 | 
			
		||||
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
 | 
			
		||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	bool has_acorn_adfs = false;
 | 
			
		||||
	bool has_pres_adfs = false;
 | 
			
		||||
	bool has_dfs = false;
 | 
			
		||||
@@ -24,51 +26,19 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str
 | 
			
		||||
	bool should_shift_restart = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	ElectronTarget() : Analyser::Static::Target(Machine::Electron) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<ElectronTarget>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_pres_adfs);
 | 
			
		||||
		DeclareField(has_acorn_adfs);
 | 
			
		||||
		DeclareField(has_dfs);
 | 
			
		||||
		DeclareField(has_ap6_rom);
 | 
			
		||||
		DeclareField(has_sideways_ram);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Electron) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(has_pres_adfs);
 | 
			
		||||
			DeclareField(has_acorn_adfs);
 | 
			
		||||
			DeclareField(has_dfs);
 | 
			
		||||
			DeclareField(has_ap6_rom);
 | 
			
		||||
			DeclareField(has_sideways_ram);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool should_shift_restart = false;
 | 
			
		||||
 | 
			
		||||
	bool has_1770dfs = false;
 | 
			
		||||
	bool has_adfs = false;
 | 
			
		||||
	bool has_sideways_ram = true;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(TubeProcessor, None, MOS6502);
 | 
			
		||||
	TubeProcessor tube_processor = TubeProcessor::None;
 | 
			
		||||
 | 
			
		||||
	BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<BBCMicroTarget>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_1770dfs);
 | 
			
		||||
		DeclareField(has_adfs);
 | 
			
		||||
		DeclareField(has_sideways_ram);
 | 
			
		||||
		AnnounceEnum(TubeProcessor);
 | 
			
		||||
		DeclareField(tube_processor);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
 | 
			
		||||
	std::string main_program;
 | 
			
		||||
 | 
			
		||||
	ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<ArchimedesTarget>;
 | 
			
		||||
	void declare_fields() {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Acorn_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,9 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty() && !is_confident) return {};
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
	// As there is at least one usable media image, wave it through.
 | 
			
		||||
	Analyser::Static::TargetList targets;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,22 @@
 | 
			
		||||
//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_Amiga_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Amiga {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Amiga {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,15 @@
 | 
			
		||||
//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Amiga_Target_h
 | 
			
		||||
#define Analyser_Static_Amiga_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Amiga {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Amiga {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(ChipRAM,
 | 
			
		||||
@@ -28,16 +31,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
 | 
			
		||||
	FastRAM fast_ram = FastRAM::EightMegabytes;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Amiga) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(fast_ram);
 | 
			
		||||
		DeclareField(chip_ram);
 | 
			
		||||
		AnnounceEnum(FastRAM);
 | 
			
		||||
		AnnounceEnum(ChipRAM);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Amiga) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(fast_ram);
 | 
			
		||||
			DeclareField(chip_ram);
 | 
			
		||||
			AnnounceEnum(FastRAM);
 | 
			
		||||
			AnnounceEnum(ChipRAM);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Amiga_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,26 +8,17 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::string rtrimmed(const std::string &input) {
 | 
			
		||||
	auto trimmed = input;
 | 
			
		||||
	trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](const char ch) {
 | 
			
		||||
		return !std::isspace(ch);
 | 
			
		||||
	}).base(), trimmed.end());
 | 
			
		||||
	return trimmed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool strcmp_insensitive(const char *a, const char *b) {
 | 
			
		||||
	if(std::strlen(a) != std::strlen(b)) return false;
 | 
			
		||||
	while(*a) {
 | 
			
		||||
@@ -72,8 +63,8 @@ std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
 | 
			
		||||
 | 
			
		||||
void InspectCatalogue(
 | 
			
		||||
	const Storage::Disk::CPM::Catalogue &catalogue,
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
 | 
			
		||||
) {
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
			
		||||
 | 
			
		||||
	std::vector<const Storage::Disk::CPM::File *> candidate_files;
 | 
			
		||||
	candidate_files.reserve(catalogue.files.size());
 | 
			
		||||
	for(const auto &file : catalogue.files) {
 | 
			
		||||
@@ -113,93 +104,63 @@ void InspectCatalogue(
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto run_name = [&]() -> std::optional<std::string> {
 | 
			
		||||
		// Collect:
 | 
			
		||||
		//
 | 
			
		||||
		//	1. a set of all files that can be run without specifying an extension plus their appearance counts;
 | 
			
		||||
		//	2. a set of all BASIC file names.
 | 
			
		||||
		std::unordered_map<std::string, int> candidates;
 | 
			
		||||
		std::unordered_set<std::string> basic_names;
 | 
			
		||||
		for(std::size_t c = 0; c < candidate_files.size(); c++) {
 | 
			
		||||
			// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 
			
		||||
			if(
 | 
			
		||||
				(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ") ||
 | 
			
		||||
				!is_implied_extension(candidate_files[c]->type)
 | 
			
		||||
			) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
	// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
 | 
			
		||||
	// that AMSDOS allows to be omitted, pick that one.
 | 
			
		||||
	int basic_files = 0;
 | 
			
		||||
	int implicit_suffixed_files = 0;
 | 
			
		||||
 | 
			
		||||
			++candidates[candidate_files[c]->name];
 | 
			
		||||
			if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
 | 
			
		||||
				basic_names.insert(candidate_files[c]->name);
 | 
			
		||||
			}
 | 
			
		||||
	std::size_t last_basic_file = 0;
 | 
			
		||||
	std::size_t last_implicit_suffixed_file = 0;
 | 
			
		||||
 | 
			
		||||
	for(std::size_t c = 0; c < candidate_files.size(); c++) {
 | 
			
		||||
		// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
 | 
			
		||||
		if(candidate_files[c]->type == "   " && candidate_files[c]->name == "        ")
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		// Check for whether this is [potentially] BASIC.
 | 
			
		||||
		if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
 | 
			
		||||
			basic_files++;
 | 
			
		||||
			last_basic_file = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Only one candidate total.
 | 
			
		||||
		if(candidates.size() == 1) {
 | 
			
		||||
			return candidates.begin()->first;
 | 
			
		||||
		// Check suffix for emptiness.
 | 
			
		||||
		if(is_implied_extension(candidate_files[c]->type)) {
 | 
			
		||||
			implicit_suffixed_files++;
 | 
			
		||||
			last_implicit_suffixed_file = c;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Only one BASIC candidate.
 | 
			
		||||
		if(basic_names.size() == 1) {
 | 
			
		||||
			return *basic_names.begin();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Exactly two candidate names, but only one is a unique name.
 | 
			
		||||
		if(candidates.size() == 2) {
 | 
			
		||||
			const auto item1 = candidates.begin();
 | 
			
		||||
			const auto item2 = std::next(item1);
 | 
			
		||||
 | 
			
		||||
			if(item1->second == 1 && item2->second != 1) {
 | 
			
		||||
				return item1->first;
 | 
			
		||||
			}
 | 
			
		||||
			if(item2->second == 1 && item1->second != 1) {
 | 
			
		||||
				return item2->first;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Remove from candidates anything that is just a suffixed version of
 | 
			
		||||
		// another name, as long as the other name is three or more characters.
 | 
			
		||||
		std::vector<std::string> to_remove;
 | 
			
		||||
		for(const auto &lhs: candidates) {
 | 
			
		||||
			const auto trimmed = rtrimmed(lhs.first);
 | 
			
		||||
			if(trimmed.size() < 3) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for(const auto &rhs: candidates) {
 | 
			
		||||
				if(lhs.first == rhs.first) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(rhs.first.find(trimmed) == 0) {
 | 
			
		||||
					to_remove.push_back(rhs.first);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for(const auto &candidate: to_remove) {
 | 
			
		||||
			candidates.erase(candidate);
 | 
			
		||||
		}
 | 
			
		||||
		if(candidates.size() == 1) {
 | 
			
		||||
			return candidates.begin()->first;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {};
 | 
			
		||||
	} ();
 | 
			
		||||
 | 
			
		||||
	if(run_name.has_value()) {
 | 
			
		||||
		target->loading_command = "run\"" + rtrimmed(*run_name) + "\n";
 | 
			
		||||
	} else {
 | 
			
		||||
		target->loading_command = "cat\n";
 | 
			
		||||
	}
 | 
			
		||||
	if(basic_files == 1 || implicit_suffixed_files == 1) {
 | 
			
		||||
		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
 | 
			
		||||
		target->loading_command = RunCommandFor(*candidate_files[selected_file]);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// One more guess: if only one remaining candidate file has a different name than the others,
 | 
			
		||||
	// assume it is intended to stand out.
 | 
			
		||||
	std::map<std::string, int> name_counts;
 | 
			
		||||
	std::map<std::string, std::size_t> indices_by_name;
 | 
			
		||||
	std::size_t index = 0;
 | 
			
		||||
	for(const auto &file : candidate_files) {
 | 
			
		||||
		name_counts[file->name]++;
 | 
			
		||||
		indices_by_name[file->name] = index;
 | 
			
		||||
		index++;
 | 
			
		||||
	}
 | 
			
		||||
	if(name_counts.size() == 2) {
 | 
			
		||||
		for(const auto &pair : name_counts) {
 | 
			
		||||
			if(pair.second == 1) {
 | 
			
		||||
				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Desperation.
 | 
			
		||||
	target->loading_command = "cat\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CheckBootSector(
 | 
			
		||||
	const std::shared_ptr<Storage::Disk::Disk> &disk,
 | 
			
		||||
	const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target
 | 
			
		||||
) {
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, 0x41);
 | 
			
		||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
 | 
			
		||||
	if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
 | 
			
		||||
		// Check that the first 64 bytes of the sector aren't identical; if they are then probably
 | 
			
		||||
		// this disk was formatted and the filler byte never replaced.
 | 
			
		||||
@@ -221,7 +182,7 @@ bool CheckBootSector(
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	// Limited sophistication here; look for a CPC-style file header, that is
 | 
			
		||||
	// any Spectrum-esque block with a synchronisation character of 0x2c.
 | 
			
		||||
	//
 | 
			
		||||
@@ -230,7 +191,7 @@ bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
	Parser parser(Parser::MachineType::AmstradCPC);
 | 
			
		||||
 | 
			
		||||
	while(true) {
 | 
			
		||||
		const auto block = parser.find_block(serialiser);
 | 
			
		||||
		const auto block = parser.find_block(tape);
 | 
			
		||||
		if(!block) break;
 | 
			
		||||
 | 
			
		||||
		if(block->type == 0x2c) {
 | 
			
		||||
@@ -243,12 +204,7 @@ bool IsAmstradTape(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
@@ -258,8 +214,7 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		bool has_cpc_tape = false;
 | 
			
		||||
		for(auto &tape: media.tapes) {
 | 
			
		||||
			const auto serialiser = tape->serialiser();
 | 
			
		||||
			has_cpc_tape |= IsAmstradTape(*serialiser);
 | 
			
		||||
			has_cpc_tape |= IsAmstradTape(tape);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(has_cpc_tape) {
 | 
			
		||||
@@ -273,14 +228,26 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!media.disks.empty()) {
 | 
			
		||||
		const auto data_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();
 | 
			
		||||
		const auto system_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();
 | 
			
		||||
		Storage::Disk::CPM::ParameterBlock data_format;
 | 
			
		||||
		data_format.sectors_per_track = 9;
 | 
			
		||||
		data_format.tracks = 40;
 | 
			
		||||
		data_format.block_size = 1024;
 | 
			
		||||
		data_format.first_sector = 0xc1;
 | 
			
		||||
		data_format.catalogue_allocation_bitmap = 0xc000;
 | 
			
		||||
		data_format.reserved_tracks = 0;
 | 
			
		||||
 | 
			
		||||
		Storage::Disk::CPM::ParameterBlock system_format;
 | 
			
		||||
		system_format.sectors_per_track = 9;
 | 
			
		||||
		system_format.tracks = 40;
 | 
			
		||||
		system_format.block_size = 1024;
 | 
			
		||||
		system_format.first_sector = 0x41;
 | 
			
		||||
		system_format.catalogue_allocation_bitmap = 0xc000;
 | 
			
		||||
		system_format.reserved_tracks = 2;
 | 
			
		||||
 | 
			
		||||
		for(auto &disk: media.disks) {
 | 
			
		||||
			// Check for an ordinary catalogue, making sure this isn't actually a ZX Spectrum disk.
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue =
 | 
			
		||||
				Storage::Disk::CPM::GetCatalogue(disk, data_format, false);
 | 
			
		||||
			if(data_catalogue && !data_catalogue->is_zx_spectrum_booter()) {
 | 
			
		||||
			// Check for an ordinary catalogue.
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
 | 
			
		||||
			if(data_catalogue) {
 | 
			
		||||
				InspectCatalogue(*data_catalogue, target);
 | 
			
		||||
				target->media.disks.push_back(disk);
 | 
			
		||||
				continue;
 | 
			
		||||
@@ -293,9 +260,8 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Failing that check for a system catalogue.
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue =
 | 
			
		||||
				Storage::Disk::CPM::GetCatalogue(disk, system_format, false);
 | 
			
		||||
			if(system_catalogue && !system_catalogue->is_zx_spectrum_booter()) {
 | 
			
		||||
			std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
 | 
			
		||||
			if(system_catalogue) {
 | 
			
		||||
				InspectCatalogue(*system_catalogue, target);
 | 
			
		||||
				target->media.disks.push_back(disk);
 | 
			
		||||
				continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AmstradCPC_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_AmstradCPC_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AmstradCPC {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AmstradCPC {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,36 +6,34 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AmstradCPC_Target_h
 | 
			
		||||
#define Analyser_Static_AmstradCPC_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AmstradCPC {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AmstradCPC {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(Model, CPC464, CPC664, CPC6128);
 | 
			
		||||
	Model model = Model::CPC464;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3);
 | 
			
		||||
	CRTCType crtc_type = CRTCType::Type2;
 | 
			
		||||
 | 
			
		||||
	// This is used internally for testing; it therefore isn't exposed reflectively.
 | 
			
		||||
	bool catch_ssm_codes = false;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AmstradCPC) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(crtc_type);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(CRTCType);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AmstradCPC) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AmstradCPC_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,26 +9,12 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->media = media;
 | 
			
		||||
 | 
			
		||||
	// If any disks are present, attach a Disk II.
 | 
			
		||||
	if(!target->media.disks.empty()) {
 | 
			
		||||
	if(!target->media.disks.empty())
 | 
			
		||||
		target->disk_controller = Target::DiskController::SixteenSector;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The emulated SCSI card requires a IIe, so upgrade to that if
 | 
			
		||||
	// any mass storage is present.
 | 
			
		||||
	if(!target->media.mass_storage_devices.empty()) {
 | 
			
		||||
		target->model = Target::Model::EnhancedIIe;
 | 
			
		||||
		target->scsi_controller = Target::SCSIController::AppleSCSI;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
	targets.push_back(std::move(target));
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AppleII_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_AppleII_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleII {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AppleII {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,16 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AppleII_Target_h
 | 
			
		||||
#define Analyser_Static_AppleII_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleII {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AppleII {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(Model,
 | 
			
		||||
@@ -26,34 +29,22 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
		SixteenSector,
 | 
			
		||||
		ThirteenSector
 | 
			
		||||
	);
 | 
			
		||||
	ReflectableEnum(SCSIController,
 | 
			
		||||
		None,
 | 
			
		||||
		AppleSCSI
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	Model model = Model::IIe;
 | 
			
		||||
	DiskController disk_controller = DiskController::None;
 | 
			
		||||
	SCSIController scsi_controller = SCSIController::None;
 | 
			
		||||
	bool has_mockingboard = true;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleII) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(disk_controller);
 | 
			
		||||
		DeclareField(scsi_controller);
 | 
			
		||||
		DeclareField(has_mockingboard);
 | 
			
		||||
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(DiskController);
 | 
			
		||||
		AnnounceEnum(SCSIController);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleII) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(disk_controller);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(DiskController);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr bool is_iie(const Target::Model model) {
 | 
			
		||||
	return model == Target::Model::IIe || model == Target::Model::EnhancedIIe;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
#endif /* Analyser_Static_AppleII_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->media = media;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleIIgs {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AppleIIgs {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,16 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AppleIIgs_Target_h
 | 
			
		||||
#define Analyser_Static_AppleIIgs_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AppleIIgs {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AppleIIgs {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(Model,
 | 
			
		||||
@@ -29,16 +32,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Model model = Model::ROM01;
 | 
			
		||||
	MemoryModel memory_model = MemoryModel::EightMB;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleIIgs) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(memory_model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(MemoryModel);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AppleIIgs) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(memory_model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(MemoryModel);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AppleIIgs_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Atari2600;
 | 
			
		||||
using Target = Analyser::Static::Atari2600::Target;
 | 
			
		||||
@@ -33,13 +33,11 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
 | 
			
		||||
	// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 
			
		||||
	// large amounts of memory.
 | 
			
		||||
	bool has_wide_area_store = false;
 | 
			
		||||
	for(const auto &entry : high_location_disassembly.instructions_by_address) {
 | 
			
		||||
		using Instruction = Analyser::Static::MOS6502::Instruction;
 | 
			
		||||
	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
 | 
			
		||||
		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
 | 
			
		||||
			has_wide_area_store |=
 | 
			
		||||
				entry.second.addressing_mode == Instruction::Indirect ||
 | 
			
		||||
				entry.second.addressing_mode == Instruction::IndexedIndirectX ||
 | 
			
		||||
				entry.second.addressing_mode == Instruction::IndirectIndexedY;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
 | 
			
		||||
			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
 | 
			
		||||
 | 
			
		||||
			if(has_wide_area_store) break;
 | 
			
		||||
		}
 | 
			
		||||
@@ -52,21 +50,13 @@ static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartrid
 | 
			
		||||
	if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor8kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &segment,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 
			
		||||
	// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
 | 
			
		||||
	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
 | 
			
		||||
	if(
 | 
			
		||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 &&
 | 
			
		||||
		segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
			
		||||
		(
 | 
			
		||||
			segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 ||
 | 
			
		||||
			segment.data[8190] != 0x00 || segment.data[8188] != 0x00
 | 
			
		||||
		) &&
 | 
			
		||||
		segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
 | 
			
		||||
		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
 | 
			
		||||
		segment.data[0] == 0x78
 | 
			
		||||
	) {
 | 
			
		||||
		target.paging_model = Target::PagingModel::ActivisionStack;
 | 
			
		||||
@@ -98,11 +88,7 @@ static void DeterminePagingFor8kCartridge(
 | 
			
		||||
	else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor16kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// Make an assumption that this is the Atari paging model.
 | 
			
		||||
	target.paging_model = Target::PagingModel::Atari16k;
 | 
			
		||||
 | 
			
		||||
@@ -122,11 +108,7 @@ static void DeterminePagingFor16kCartridge(
 | 
			
		||||
	if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void DeterminePagingFor64kCartridge(
 | 
			
		||||
	Target &target,
 | 
			
		||||
	const Storage::Cartridge::Cartridge::Segment &,
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly
 | 
			
		||||
) {
 | 
			
		||||
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	// Make an assumption that this is a Tigervision if there is a write to 3F.
 | 
			
		||||
	target.paging_model =
 | 
			
		||||
		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
 | 
			
		||||
@@ -139,12 +121,8 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto word = [](const uint8_t low, const uint8_t high) {
 | 
			
		||||
		return uint16_t(low | (high << 8));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const auto entry_address = word(segment.data[segment.data.size() - 4], segment.data[segment.data.size() - 3]);
 | 
			
		||||
	const auto break_address = word(segment.data[segment.data.size() - 2], segment.data[segment.data.size() - 1]);
 | 
			
		||||
	const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
 | 
			
		||||
	const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
 | 
			
		||||
 | 
			
		||||
	std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
 | 
			
		||||
		if(!(address & 0x1000)) return size_t(-1);
 | 
			
		||||
@@ -152,16 +130,27 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
 | 
			
		||||
	const auto disassembly =
 | 
			
		||||
		Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
			
		||||
	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
 | 
			
		||||
 | 
			
		||||
	switch(segment.data.size()) {
 | 
			
		||||
		case 8192:		DeterminePagingFor8kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		case 10495:		target.paging_model = Target::PagingModel::Pitfall2;			break;
 | 
			
		||||
		case 12288:		target.paging_model = Target::PagingModel::CBSRamPlus;			break;
 | 
			
		||||
		case 16384:		DeterminePagingFor16kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		case 32768:		target.paging_model = Target::PagingModel::Atari32k;			break;
 | 
			
		||||
		case 65536:		DeterminePagingFor64kCartridge(target, segment, disassembly);	break;
 | 
			
		||||
		case 8192:
 | 
			
		||||
			DeterminePagingFor8kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 10495:
 | 
			
		||||
			target.paging_model = Target::PagingModel::Pitfall2;
 | 
			
		||||
		break;
 | 
			
		||||
		case 12288:
 | 
			
		||||
			target.paging_model = Target::PagingModel::CBSRamPlus;
 | 
			
		||||
		break;
 | 
			
		||||
		case 16384:
 | 
			
		||||
			DeterminePagingFor16kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		case 32768:
 | 
			
		||||
			target.paging_model = Target::PagingModel::Atari32k;
 | 
			
		||||
		break;
 | 
			
		||||
		case 65536:
 | 
			
		||||
			DeterminePagingFor64kCartridge(target, segment, disassembly);
 | 
			
		||||
		break;
 | 
			
		||||
		default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
@@ -188,12 +177,7 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// TODO: sanity checking; is this image really for an Atari 2600?
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_Atari_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Atari2600 {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Atari2600 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,14 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Atari2600_Target_h
 | 
			
		||||
#define Analyser_Static_Atari2600_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Atari2600 {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Atari2600 {
 | 
			
		||||
 | 
			
		||||
struct Target: public ::Analyser::Static::Target {
 | 
			
		||||
	enum class PagingModel {
 | 
			
		||||
@@ -36,3 +39,7 @@ struct Target: public ::Analyser::Static::Target {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Atari_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,22 @@
 | 
			
		||||
//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_AtariST_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AtariST {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AtariST {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,28 +6,22 @@
 | 
			
		||||
//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_AtariST_Target_h
 | 
			
		||||
#define Analyser_Static_AtariST_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::AtariST {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace AtariST {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(MemorySize,
 | 
			
		||||
		FiveHundredAndTwelveKilobytes,
 | 
			
		||||
		OneMegabyte,
 | 
			
		||||
		FourMegabytes);
 | 
			
		||||
	MemorySize memory_size = MemorySize::OneMegabyte;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::AtariST) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(memory_size);
 | 
			
		||||
		AnnounceEnum(MemorySize);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_AtariST_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
		ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
@@ -52,20 +52,11 @@ ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartr
 | 
			
		||||
	return coleco_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	const bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
	auto target = std::make_unique<Target>(Machine::ColecoVision);
 | 
			
		||||
	target->confidence = 1.0f - 1.0f / 32768.0f;
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
	} else {
 | 
			
		||||
		target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
			
		||||
	}
 | 
			
		||||
	target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
 | 
			
		||||
	if(!target->media.empty())
 | 
			
		||||
		targets.push_back(std::move(target));
 | 
			
		||||
	return targets;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Coleco {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Coleco {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,168 +7,166 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Disk.hpp"
 | 
			
		||||
#include "Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "Storage/Data/Commodore.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Controller/DiskController.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
 | 
			
		||||
#include "../../../Storage/Data/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <array>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
class CommodoreGCRParser: public Storage::Disk::Controller {
 | 
			
		||||
public:
 | 
			
		||||
	CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
			
		||||
		emplace_drive(4000000, 300, 2);
 | 
			
		||||
		set_drive(1);
 | 
			
		||||
		get_drive().set_motor_on(true);
 | 
			
		||||
	}
 | 
			
		||||
	public:
 | 
			
		||||
		CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
 | 
			
		||||
			emplace_drive(4000000, 300, 2);
 | 
			
		||||
			set_drive(1);
 | 
			
		||||
			get_drive().set_motor_on(true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	struct Sector {
 | 
			
		||||
		uint8_t sector, track;
 | 
			
		||||
		std::array<uint8_t, 256> data;
 | 
			
		||||
		bool header_checksum_matched;
 | 
			
		||||
		bool data_checksum_matched;
 | 
			
		||||
	};
 | 
			
		||||
		struct Sector {
 | 
			
		||||
			uint8_t sector, track;
 | 
			
		||||
			std::array<uint8_t, 256> data;
 | 
			
		||||
			bool header_checksum_matched;
 | 
			
		||||
			bool data_checksum_matched;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
		Attempts to read the sector located at @c track and @c sector.
 | 
			
		||||
		/*!
 | 
			
		||||
			Attempts to read the sector located at @c track and @c sector.
 | 
			
		||||
 | 
			
		||||
		@returns a sector if one was found; @c nullptr otherwise.
 | 
			
		||||
	*/
 | 
			
		||||
	const Sector *sector(const uint8_t track, const uint8_t sector) {
 | 
			
		||||
		int difference = int(track) - int(track_);
 | 
			
		||||
		track_ = track;
 | 
			
		||||
			@returns a sector if one was found; @c nullptr otherwise.
 | 
			
		||||
		*/
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
 | 
			
		||||
			int difference = int(track) - int(track_);
 | 
			
		||||
			track_ = track;
 | 
			
		||||
 | 
			
		||||
		if(difference) {
 | 
			
		||||
			const int direction = difference < 0 ? -1 : 1;
 | 
			
		||||
			difference *= direction;
 | 
			
		||||
			if(difference) {
 | 
			
		||||
				int direction = difference < 0 ? -1 : 1;
 | 
			
		||||
				difference *= direction;
 | 
			
		||||
 | 
			
		||||
			for(int c = 0; c < difference; c++) {
 | 
			
		||||
				get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
			
		||||
				for(int c = 0; c < difference; c++) {
 | 
			
		||||
					get_drive().step(Storage::Disk::HeadPosition(direction));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				unsigned int zone = 3;
 | 
			
		||||
				if(track >= 18) zone = 2;
 | 
			
		||||
				else if(track >= 25) zone = 1;
 | 
			
		||||
				else if(track >= 31) zone = 0;
 | 
			
		||||
				set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			unsigned int zone = 3;
 | 
			
		||||
			if(track >= 18) zone = 2;
 | 
			
		||||
			else if(track >= 25) zone = 1;
 | 
			
		||||
			else if(track >= 31) zone = 0;
 | 
			
		||||
			set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
 | 
			
		||||
			return get_sector(sector);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return get_sector(sector);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
		get_drive().set_disk(disk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	unsigned int shift_register_;
 | 
			
		||||
	int index_count_;
 | 
			
		||||
	int bit_count_;
 | 
			
		||||
	uint8_t track_;
 | 
			
		||||
	std::unordered_map<uint16_t, std::unique_ptr<Sector>> sector_cache_;
 | 
			
		||||
 | 
			
		||||
	void process_input_bit(const int value) override {
 | 
			
		||||
		shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
 | 
			
		||||
		bit_count_++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsigned int proceed_to_next_block(const int max_index_count) {
 | 
			
		||||
		// find GCR lead-in
 | 
			
		||||
		proceed_to_shift_value(0x3ff);
 | 
			
		||||
		if(shift_register_ != 0x3ff) return 0xff;
 | 
			
		||||
 | 
			
		||||
		// find end of lead-in
 | 
			
		||||
		while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
			get_drive().set_disk(disk);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// continue for a further nine bits
 | 
			
		||||
		bit_count_ = 0;
 | 
			
		||||
		while(bit_count_ < 9 && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
	private:
 | 
			
		||||
		unsigned int shift_register_;
 | 
			
		||||
		int index_count_;
 | 
			
		||||
		int bit_count_;
 | 
			
		||||
		uint8_t track_;
 | 
			
		||||
		std::shared_ptr<Sector> sector_cache_[65536];
 | 
			
		||||
 | 
			
		||||
		void process_input_bit(int value) {
 | 
			
		||||
			shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
 | 
			
		||||
			bit_count_++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	}
 | 
			
		||||
		unsigned int proceed_to_next_block(int max_index_count) {
 | 
			
		||||
			// find GCR lead-in
 | 
			
		||||
			proceed_to_shift_value(0x3ff);
 | 
			
		||||
			if(shift_register_ != 0x3ff) return 0xff;
 | 
			
		||||
 | 
			
		||||
	unsigned int get_next_byte() {
 | 
			
		||||
		bit_count_ = 0;
 | 
			
		||||
		while(bit_count_ < 10) run_for(Cycles(1));
 | 
			
		||||
		return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
	}
 | 
			
		||||
			// find end of lead-in
 | 
			
		||||
			while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	void proceed_to_shift_value(const unsigned int shift_value) {
 | 
			
		||||
		const int max_index_count = index_count_ + 2;
 | 
			
		||||
		while(shift_register_ != shift_value && index_count_ < max_index_count) {
 | 
			
		||||
			run_for(Cycles(1));
 | 
			
		||||
			// continue for a further nine bits
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 9 && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void process_index_hole() override {
 | 
			
		||||
		index_count_++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Sector *get_sector(const uint8_t sector) {
 | 
			
		||||
		const uint16_t sector_address = uint16_t((track_ << 8) | sector);
 | 
			
		||||
		auto existing = sector_cache_.find(sector_address);
 | 
			
		||||
		if(existing != sector_cache_.end()) return existing->second.get();
 | 
			
		||||
 | 
			
		||||
		const auto first_sector = get_next_sector();
 | 
			
		||||
		if(!first_sector) return first_sector;
 | 
			
		||||
		if(first_sector->sector == sector) return first_sector;
 | 
			
		||||
 | 
			
		||||
		while(true) {
 | 
			
		||||
			const auto next_sector = get_next_sector();
 | 
			
		||||
			if(next_sector->sector == first_sector->sector) return nullptr;
 | 
			
		||||
			if(next_sector->sector == sector) return next_sector;
 | 
			
		||||
		unsigned int get_next_byte() {
 | 
			
		||||
			bit_count_ = 0;
 | 
			
		||||
			while(bit_count_ < 10) run_for(Cycles(1));
 | 
			
		||||
			return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Sector *get_next_sector() {
 | 
			
		||||
		auto sector = std::make_unique<Sector>();
 | 
			
		||||
		const int max_index_count = index_count_ + 2;
 | 
			
		||||
		void proceed_to_shift_value(unsigned int shift_value) {
 | 
			
		||||
			const int max_index_count = index_count_ + 2;
 | 
			
		||||
			while(shift_register_ != shift_value && index_count_ < max_index_count) {
 | 
			
		||||
				run_for(Cycles(1));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void process_index_hole() {
 | 
			
		||||
			index_count_++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		std::shared_ptr<Sector> get_sector(uint8_t sector) {
 | 
			
		||||
			const uint16_t sector_address = uint16_t((track_ << 8) | sector);
 | 
			
		||||
			if(sector_cache_[sector_address]) return sector_cache_[sector_address];
 | 
			
		||||
 | 
			
		||||
			const std::shared_ptr<Sector> first_sector = get_next_sector();
 | 
			
		||||
			if(!first_sector) return first_sector;
 | 
			
		||||
			if(first_sector->sector == sector) return first_sector;
 | 
			
		||||
 | 
			
		||||
		while(index_count_ < max_index_count) {
 | 
			
		||||
			// look for a sector header
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(proceed_to_next_block(max_index_count) == 0x08) break;
 | 
			
		||||
				if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// get sector details, skip if this looks malformed
 | 
			
		||||
			uint8_t checksum = uint8_t(get_next_byte());
 | 
			
		||||
			sector->sector = uint8_t(get_next_byte());
 | 
			
		||||
			sector->track = uint8_t(get_next_byte());
 | 
			
		||||
			uint8_t disk_id[2];
 | 
			
		||||
			disk_id[0] = uint8_t(get_next_byte());
 | 
			
		||||
			disk_id[1] = uint8_t(get_next_byte());
 | 
			
		||||
			if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
			
		||||
 | 
			
		||||
			// look for the following data
 | 
			
		||||
			while(1) {
 | 
			
		||||
				if(proceed_to_next_block(max_index_count) == 0x07) break;
 | 
			
		||||
				if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			checksum = 0;
 | 
			
		||||
			for(std::size_t c = 0; c < 256; c++) {
 | 
			
		||||
				sector->data[c] = uint8_t(get_next_byte());
 | 
			
		||||
				checksum ^= sector->data[c];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(checksum == get_next_byte()) {
 | 
			
		||||
				uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
 | 
			
		||||
				auto pair = sector_cache_.emplace(sector_address, std::move(sector));
 | 
			
		||||
				return pair.first->second.get();
 | 
			
		||||
				const std::shared_ptr<Sector> next_sector = get_next_sector();
 | 
			
		||||
				if(next_sector->sector == first_sector->sector) return nullptr;
 | 
			
		||||
				if(next_sector->sector == sector) return next_sector;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
		std::shared_ptr<Sector> get_next_sector() {
 | 
			
		||||
			auto sector = std::make_shared<Sector>();
 | 
			
		||||
			const int max_index_count = index_count_ + 2;
 | 
			
		||||
 | 
			
		||||
			while(index_count_ < max_index_count) {
 | 
			
		||||
				// look for a sector header
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block(max_index_count) == 0x08) break;
 | 
			
		||||
					if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// get sector details, skip if this looks malformed
 | 
			
		||||
				uint8_t checksum = uint8_t(get_next_byte());
 | 
			
		||||
				sector->sector = uint8_t(get_next_byte());
 | 
			
		||||
				sector->track = uint8_t(get_next_byte());
 | 
			
		||||
				uint8_t disk_id[2];
 | 
			
		||||
				disk_id[0] = uint8_t(get_next_byte());
 | 
			
		||||
				disk_id[1] = uint8_t(get_next_byte());
 | 
			
		||||
				if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
 | 
			
		||||
 | 
			
		||||
				// look for the following data
 | 
			
		||||
				while(1) {
 | 
			
		||||
					if(proceed_to_next_block(max_index_count) == 0x07) break;
 | 
			
		||||
					if(index_count_ >= max_index_count) return nullptr;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				checksum = 0;
 | 
			
		||||
				for(std::size_t c = 0; c < 256; c++) {
 | 
			
		||||
					sector->data[c] = uint8_t(get_next_byte());
 | 
			
		||||
					checksum ^= sector->data[c];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(checksum == get_next_byte()) {
 | 
			
		||||
					uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
 | 
			
		||||
					sector_cache_[sector_address] = sector;
 | 
			
		||||
					return sector;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
@@ -176,26 +174,20 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
			
		||||
	CommodoreGCRParser parser;
 | 
			
		||||
	parser.set_disk(disk);
 | 
			
		||||
 | 
			
		||||
	// Assemble directory.
 | 
			
		||||
	// find any sector whatsoever to establish the current track
 | 
			
		||||
	std::shared_ptr<CommodoreGCRParser::Sector> sector;
 | 
			
		||||
 | 
			
		||||
	// assemble directory
 | 
			
		||||
	std::vector<uint8_t> directory;
 | 
			
		||||
	uint8_t next_track = 18;
 | 
			
		||||
	uint8_t next_sector = 1;
 | 
			
		||||
	directory.reserve(20 * 1024);	// Probably more than plenty.
 | 
			
		||||
	std::set<std::pair<uint8_t, uint8_t>> visited;
 | 
			
		||||
	while(true) {
 | 
			
		||||
		// Don't be fooled by disks that are encoded with a looping directory.
 | 
			
		||||
		const auto key = std::make_pair(next_track, next_sector);
 | 
			
		||||
		if(visited.find(key) != visited.end()) break;
 | 
			
		||||
		visited.insert(key);
 | 
			
		||||
 | 
			
		||||
		// Append sector to directory and follow next link.
 | 
			
		||||
		const auto sector = parser.sector(next_track, next_sector);
 | 
			
		||||
	while(1) {
 | 
			
		||||
		sector = parser.get_sector(next_track, next_sector);
 | 
			
		||||
		if(!sector) break;
 | 
			
		||||
		directory.insert(directory.end(), sector->data.begin(), sector->data.end());
 | 
			
		||||
		next_track = sector->data[0];
 | 
			
		||||
		next_sector = sector->data[1];
 | 
			
		||||
 | 
			
		||||
		// Check for end-of-directory.
 | 
			
		||||
		if(!next_track) break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -224,36 +216,24 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
 | 
			
		||||
		}
 | 
			
		||||
		new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
 | 
			
		||||
 | 
			
		||||
		const std::size_t number_of_sectors =
 | 
			
		||||
			size_t(directory[header_pointer + 0x1e]) +
 | 
			
		||||
			(size_t(directory[header_pointer + 0x1f]) << 8);
 | 
			
		||||
		if(number_of_sectors) {
 | 
			
		||||
			new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
			
		||||
		std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
 | 
			
		||||
		new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
 | 
			
		||||
 | 
			
		||||
			bool is_first_sector = true;
 | 
			
		||||
			while(next_track) {
 | 
			
		||||
				const auto sector = parser.sector(next_track, next_sector);
 | 
			
		||||
				if(!sector) break;
 | 
			
		||||
		bool is_first_sector = true;
 | 
			
		||||
		while(next_track) {
 | 
			
		||||
			sector = parser.get_sector(next_track, next_sector);
 | 
			
		||||
			if(!sector) break;
 | 
			
		||||
 | 
			
		||||
				next_track = sector->data[0];
 | 
			
		||||
				next_sector = sector->data[1];
 | 
			
		||||
			next_track = sector->data[0];
 | 
			
		||||
			next_sector = sector->data[1];
 | 
			
		||||
 | 
			
		||||
				if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
 | 
			
		||||
				if(next_track)
 | 
			
		||||
					new_file.data.insert(
 | 
			
		||||
						new_file.data.end(),
 | 
			
		||||
						sector->data.begin() + (is_first_sector ? 4 : 2),
 | 
			
		||||
						sector->data.end()
 | 
			
		||||
					);
 | 
			
		||||
				else
 | 
			
		||||
					new_file.data.insert(
 | 
			
		||||
						new_file.data.end(),
 | 
			
		||||
						sector->data.begin() + 2,
 | 
			
		||||
						sector->data.begin() + next_sector
 | 
			
		||||
					);
 | 
			
		||||
			if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
 | 
			
		||||
			if(next_track)
 | 
			
		||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
 | 
			
		||||
			else
 | 
			
		||||
				new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
 | 
			
		||||
 | 
			
		||||
				is_first_sector = false;
 | 
			
		||||
			}
 | 
			
		||||
			is_first_sector = false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!next_track) files.push_back(new_file);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,22 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Commodore_Disk_hpp
 | 
			
		||||
#define StaticAnalyser_Commodore_Disk_hpp
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Disk_hpp */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								Analyser/Static/Commodore/File.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Analyser/Static/Commodore/File.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
//
 | 
			
		||||
//  File.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 10/09/2016.
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
bool Analyser::Static::Commodore::File::is_basic() {
 | 
			
		||||
	// BASIC files are always relocatable (?)
 | 
			
		||||
	if(type != File::RelocatableProgram) return false;
 | 
			
		||||
 | 
			
		||||
	uint16_t line_address = starting_address;
 | 
			
		||||
	int line_number = -1;
 | 
			
		||||
 | 
			
		||||
	// decide whether this is a BASIC file based on the proposition that:
 | 
			
		||||
	//	(1) they're always relocatable; and
 | 
			
		||||
	//	(2) they have a per-line structure of:
 | 
			
		||||
	//		[4 bytes: address of start of next line]
 | 
			
		||||
	//		[4 bytes: this line number]
 | 
			
		||||
	//		... null-terminated code ...
 | 
			
		||||
	//	(with a next line address of 0000 indicating end of program)
 | 
			
		||||
	while(1) {
 | 
			
		||||
		if(size_t(line_address - starting_address) >= data.size() + 2) break;
 | 
			
		||||
 | 
			
		||||
		uint16_t next_line_address = data[line_address - starting_address];
 | 
			
		||||
		next_line_address |= data[line_address - starting_address + 1] << 8;
 | 
			
		||||
 | 
			
		||||
		if(!next_line_address) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		if(next_line_address < line_address + 5) break;
 | 
			
		||||
 | 
			
		||||
		if(size_t(line_address - starting_address) >= data.size() + 5) break;
 | 
			
		||||
		uint16_t next_line_number = data[line_address - starting_address + 2];
 | 
			
		||||
		next_line_number |= data[line_address - starting_address + 3] << 8;
 | 
			
		||||
 | 
			
		||||
		if(next_line_number <= line_number) break;
 | 
			
		||||
 | 
			
		||||
		line_number = uint16_t(next_line_number);
 | 
			
		||||
		line_address = next_line_address;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
@@ -6,13 +6,15 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef File_hpp
 | 
			
		||||
#define File_hpp
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
	std::wstring name;
 | 
			
		||||
@@ -29,6 +31,12 @@ struct File {
 | 
			
		||||
		Relative
 | 
			
		||||
	} type;
 | 
			
		||||
	std::vector<uint8_t> data;
 | 
			
		||||
 | 
			
		||||
	bool is_basic();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* File_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -12,33 +12,26 @@
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
			
		||||
#include "Outputs/Log.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
 | 
			
		||||
#include "../../../Outputs/Log.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
 | 
			
		||||
		Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
 | 
			
		||||
	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
 | 
			
		||||
 | 
			
		||||
	for(const auto &cartridge : cartridges) {
 | 
			
		||||
		const auto &segments = cartridge->get_segments();
 | 
			
		||||
 | 
			
		||||
		// Only one mapped item is allowed ...
 | 
			
		||||
		// only one mapped item is allowed
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// ... which must be 16 kb in size.
 | 
			
		||||
		// which must be 16 kb in size
 | 
			
		||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
			
		||||
		if(segment.start_address != 0xa000) continue;
 | 
			
		||||
		if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
 | 
			
		||||
@@ -46,312 +39,126 @@ Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartri
 | 
			
		||||
		vic20_cartridges.push_back(cartridge);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: other machines?
 | 
			
		||||
 | 
			
		||||
	return vic20_cartridges;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct BASICAnalysis {
 | 
			
		||||
	enum class Version {
 | 
			
		||||
		NotBASIC,
 | 
			
		||||
		BASIC2,
 | 
			
		||||
		BASIC4,
 | 
			
		||||
		BASIC3_5,
 | 
			
		||||
	} minimum_version = Version::NotBASIC;
 | 
			
		||||
	std::vector<uint16_t> machine_code_addresses;
 | 
			
		||||
};
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
 | 
			
		||||
std::optional<BASICAnalysis> analyse(const File &file) {
 | 
			
		||||
	BASICAnalysis analysis;
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->machine = Machine::Vic20;	// TODO: machine estimation
 | 
			
		||||
	target->confidence = 0.5; // TODO: a proper estimation
 | 
			
		||||
 | 
			
		||||
	switch(file.type) {
 | 
			
		||||
		// For 'program' types, proceed with analysis below.
 | 
			
		||||
		case File::RelocatableProgram:
 | 
			
		||||
		case File::NonRelocatableProgram:
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		// For sequential and relative data stop right now.
 | 
			
		||||
		case File::DataSequence:
 | 
			
		||||
		case File::Relative:
 | 
			
		||||
		return std::nullopt;
 | 
			
		||||
 | 
			
		||||
		// For user data, try decoding from the starting point.
 | 
			
		||||
		case File::User:
 | 
			
		||||
			analysis.machine_code_addresses.push_back(file.starting_address);
 | 
			
		||||
		return analysis;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Don't form an opinion if file is empty.
 | 
			
		||||
	if(file.data.empty()) {
 | 
			
		||||
		return std::nullopt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint16_t line_address = file.starting_address;
 | 
			
		||||
//	int previous_line_number = -1;
 | 
			
		||||
 | 
			
		||||
	const auto byte = [&](uint16_t address) {
 | 
			
		||||
		return file.data[address - file.starting_address];
 | 
			
		||||
	};
 | 
			
		||||
	const auto word = [&](uint16_t address) {
 | 
			
		||||
		return uint16_t(byte(address) | byte(address + 1) << 8);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// BASIC programs have a per-line structure of:
 | 
			
		||||
	//		[2 bytes: address of start of next line]
 | 
			
		||||
	//		[2 bytes: this line number]
 | 
			
		||||
	//		... null-terminated code ...
 | 
			
		||||
	//	(with a next line address of 0000 indicating end of program)
 | 
			
		||||
	//
 | 
			
		||||
	// If a SYS is encountered that jumps into the BASIC program then treat that as
 | 
			
		||||
	// a machine code entry point.
 | 
			
		||||
 | 
			
		||||
	std::unordered_set<uint16_t> visited_lines;
 | 
			
		||||
	while(true) {
 | 
			
		||||
		// Analysis has failed if there isn't at least one complete BASIC line from here.
 | 
			
		||||
		// Fall back on guessing the start address as a machine code entrypoint.
 | 
			
		||||
		if(size_t(line_address - file.starting_address) + 5 >= file.data.size() || line_address < file.starting_address) {
 | 
			
		||||
			analysis.machine_code_addresses.push_back(file.starting_address);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto next_line_address = word(line_address);
 | 
			
		||||
//		const auto line_number = word(line_address + 2);
 | 
			
		||||
 | 
			
		||||
		uint16_t code = line_address + 4;
 | 
			
		||||
		const auto next = [&]() -> uint8_t {
 | 
			
		||||
			if(code >= file.starting_address + file.data.size()) {
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
			return byte(code++);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// TODO: sanity check on apparent line contents.
 | 
			
		||||
		// TODO: observe token set (and possibly parameters?) to guess BASIC version.
 | 
			
		||||
		while(true) {
 | 
			
		||||
			const auto token = next();
 | 
			
		||||
			if(!token || token == 0x8f) break;
 | 
			
		||||
 | 
			
		||||
			switch(token) {
 | 
			
		||||
				case 0x9e: {	// SYS; parse following ASCII argument.
 | 
			
		||||
					uint16_t address = 0;
 | 
			
		||||
					while(true) {
 | 
			
		||||
						const auto c = next();
 | 
			
		||||
						if(c < '0' || c > '9') {
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
						address = (address * 10) + (c - '0');
 | 
			
		||||
					};
 | 
			
		||||
					analysis.machine_code_addresses.push_back(address);
 | 
			
		||||
				} break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Exit if a formal end of the program has been declared or if, as some copy protections do,
 | 
			
		||||
		// the linked list of line contents has been made circular.
 | 
			
		||||
		visited_lines.insert(line_address);
 | 
			
		||||
		if(!next_line_address || visited_lines.find(next_line_address) != visited_lines.end()) {
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
//		previous_line_number = line_number;
 | 
			
		||||
		line_address = next_line_address;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return analysis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename TargetT>
 | 
			
		||||
void set_loading_command(TargetT &target) {
 | 
			
		||||
	if(target.media.disks.empty()) {
 | 
			
		||||
		target.loading_command = "LOAD\"\",1,1\nRUN\n";
 | 
			
		||||
	} else {
 | 
			
		||||
		target.loading_command = "LOAD\"*\",8,1\nRUN\n";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool obviously_uses_ted(const File &file) {
 | 
			
		||||
	const auto analysis = analyse(file);
 | 
			
		||||
	if(!analysis) return false;
 | 
			
		||||
 | 
			
		||||
	// Disassemble.
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		file.data,
 | 
			
		||||
		Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
 | 
			
		||||
		analysis->machine_code_addresses
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// Check for interrupt status and paging touches.
 | 
			
		||||
	for(const auto address: {0xff3e, 0xff3f, 0xff09}) {
 | 
			
		||||
		for(const auto &collection: {
 | 
			
		||||
			disassembly.external_loads,
 | 
			
		||||
			disassembly.external_stores,
 | 
			
		||||
			disassembly.external_modifies
 | 
			
		||||
		}) {
 | 
			
		||||
			if(collection.find(uint16_t(address)) != collection.end()) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct FileAnalysis {
 | 
			
		||||
	int device = 0;
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	bool is_disk = false;
 | 
			
		||||
	Analyser::Static::Media media;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <TargetPlatform::Type platform>
 | 
			
		||||
FileAnalysis analyse_files(const Analyser::Static::Media &media) {
 | 
			
		||||
	FileAnalysis analysis;
 | 
			
		||||
	// strip out inappropriate cartridges
 | 
			
		||||
	target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
			
		||||
 | 
			
		||||
	// Find all valid Commodore files on disks.
 | 
			
		||||
	// check disks
 | 
			
		||||
	for(auto &disk : media.disks) {
 | 
			
		||||
		std::vector<File> disk_files = GetFiles(disk);
 | 
			
		||||
		if(!disk_files.empty()) {
 | 
			
		||||
			analysis.is_disk = true;
 | 
			
		||||
			analysis.files.insert(
 | 
			
		||||
				analysis.files.end(),
 | 
			
		||||
				std::make_move_iterator(disk_files.begin()),
 | 
			
		||||
				std::make_move_iterator(disk_files.end())
 | 
			
		||||
			);
 | 
			
		||||
			analysis.media.disks.push_back(disk);
 | 
			
		||||
			if(!analysis.device) analysis.device = 8;
 | 
			
		||||
			is_disk = true;
 | 
			
		||||
			files.insert(files.end(), disk_files.begin(), disk_files.end());
 | 
			
		||||
			target->media.disks.push_back(disk);
 | 
			
		||||
			if(!device) device = 8;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find all valid Commodore files on tapes.
 | 
			
		||||
	// check tapes
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(*serialiser, platform);
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		if(!tape_files.empty()) {
 | 
			
		||||
			analysis.files.insert(
 | 
			
		||||
				analysis.files.end(),
 | 
			
		||||
				std::make_move_iterator(tape_files.begin()),
 | 
			
		||||
				std::make_move_iterator(tape_files.end())
 | 
			
		||||
			);
 | 
			
		||||
			analysis.media.tapes.push_back(tape);
 | 
			
		||||
			if(!analysis.device) analysis.device = 1;
 | 
			
		||||
			files.insert(files.end(), tape_files.begin(), tape_files.end());
 | 
			
		||||
			target->media.tapes.push_back(tape);
 | 
			
		||||
			if(!device) device = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return analysis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string loading_command(const FileAnalysis &file_analysis) {
 | 
			
		||||
	std::ostringstream string_stream;
 | 
			
		||||
	string_stream << "LOAD\"" << (file_analysis.is_disk ? "*" : "") << "\"," << file_analysis.device;
 | 
			
		||||
 | 
			
		||||
	const auto analysis = analyse(file_analysis.files[0]);
 | 
			
		||||
	if(analysis && !analysis->machine_code_addresses.empty()) {
 | 
			
		||||
		string_stream << ",1";
 | 
			
		||||
	}
 | 
			
		||||
	string_stream << "\nRUN\n";
 | 
			
		||||
	return string_stream.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::pair<TargetPlatform::IntType, std::optional<Vic20Target::MemoryModel>>
 | 
			
		||||
analyse_starting_address(uint16_t starting_address) {
 | 
			
		||||
	switch(starting_address) {
 | 
			
		||||
		case 0x1c01:
 | 
			
		||||
			// TODO: assume C128.
 | 
			
		||||
		default:
 | 
			
		||||
			Log::Logger<Log::Source::CommodoreStaticAnalyser>::error().append(
 | 
			
		||||
				"Unrecognised loading address for Commodore program: %04x", starting_address);
 | 
			
		||||
			[[fallthrough]];
 | 
			
		||||
		case 0x1001:
 | 
			
		||||
		return std::make_pair(TargetPlatform::Vic20 | TargetPlatform::Plus4, Vic20Target::MemoryModel::Unexpanded);
 | 
			
		||||
 | 
			
		||||
		case 0x1201:	return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::ThirtyTwoKB);
 | 
			
		||||
		case 0x0401:	return std::make_pair(TargetPlatform::Vic20, Vic20Target::MemoryModel::EightKB);
 | 
			
		||||
		case 0x0801:	return std::make_pair(TargetPlatform::C64, std::nullopt);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <TargetPlatform::IntType platform>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Plus4>(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Plus4Target>();
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
		set_loading_command(*target);
 | 
			
		||||
	} else {
 | 
			
		||||
		const auto files = analyse_files<TargetPlatform::Plus4>(media);
 | 
			
		||||
		if(!files.files.empty()) {
 | 
			
		||||
			target->loading_command = loading_command(files);
 | 
			
		||||
	if(!files.empty()) {
 | 
			
		||||
		auto memory_model = Target::MemoryModel::Unexpanded;
 | 
			
		||||
		std::ostringstream string_stream;
 | 
			
		||||
		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
 | 
			
		||||
		if(files.front().is_basic()) {
 | 
			
		||||
			string_stream << "0";
 | 
			
		||||
		} else {
 | 
			
		||||
			string_stream << "1";
 | 
			
		||||
		}
 | 
			
		||||
		target->media.disks = media.disks;
 | 
			
		||||
		target->media.tapes = media.tapes;
 | 
			
		||||
	}
 | 
			
		||||
		string_stream << "\nRUN\n";
 | 
			
		||||
		target->loading_command = string_stream.str();
 | 
			
		||||
 | 
			
		||||
	// Attach a 1541 if there are any disks here.
 | 
			
		||||
	target->has_c1541 = !target->media.disks.empty();
 | 
			
		||||
	return target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
 | 
			
		||||
	const Analyser::Static::Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	auto target = std::make_unique<Vic20Target>();
 | 
			
		||||
	const auto files = analyse_files<TargetPlatform::Vic20>(media);
 | 
			
		||||
	if(!files.files.empty()) {
 | 
			
		||||
		target->loading_command = loading_command(files);
 | 
			
		||||
 | 
			
		||||
		const auto model = analyse_starting_address(files.files[0].starting_address);
 | 
			
		||||
		if(model.second.has_value()) {
 | 
			
		||||
			target->set_memory_model(*model.second);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(is_confident) {
 | 
			
		||||
		target->media = media;
 | 
			
		||||
		set_loading_command(*target);
 | 
			
		||||
	} else {
 | 
			
		||||
		// Strip out inappropriate cartridges but retain all tapes and disks.
 | 
			
		||||
		target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
 | 
			
		||||
		target->media.disks = media.disks;
 | 
			
		||||
		target->media.tapes = media.tapes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for(const auto &file : files.files) {
 | 
			
		||||
		// The Vic-20 never has RAM after 0x8000.
 | 
			
		||||
		if(file.ending_address >= 0x8000) {
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		// make a first guess based on loading address
 | 
			
		||||
		switch(files.front().starting_address) {
 | 
			
		||||
			default:
 | 
			
		||||
				LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) <<  files.front().starting_address);
 | 
			
		||||
				[[fallthrough]];
 | 
			
		||||
			case 0x1001:
 | 
			
		||||
				memory_model = Target::MemoryModel::Unexpanded;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0x1201:
 | 
			
		||||
				memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
			break;
 | 
			
		||||
			case 0x0401:
 | 
			
		||||
				memory_model = Target::MemoryModel::EightKB;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(obviously_uses_ted(file)) {
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
		target->set_memory_model(memory_model);
 | 
			
		||||
 | 
			
		||||
		// General approach: increase memory size conservatively such that the largest file found will fit.
 | 
			
		||||
//		for(File &file : files) {
 | 
			
		||||
//			std::size_t file_size = file.data.size();
 | 
			
		||||
//			bool is_basic = file.is_basic();
 | 
			
		||||
 | 
			
		||||
			/*if(is_basic)
 | 
			
		||||
			{
 | 
			
		||||
				// BASIC files may be relocated, so the only limit is size.
 | 
			
		||||
				//
 | 
			
		||||
				// An unexpanded machine has 3583 bytes free for BASIC;
 | 
			
		||||
				// a 3kb expanded machine has 6655 bytes free.
 | 
			
		||||
				if(file_size > 6655)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
 | 
			
		||||
				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
 | 
			
		||||
					target->vic20.memory_model = Vic20MemoryModel::EightKB;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{*/
 | 
			
		||||
//			if(!file.type == File::NonRelocatableProgram)
 | 
			
		||||
//			{
 | 
			
		||||
				// Non-BASIC files may be relocatable but, if so, by what logic?
 | 
			
		||||
				// Given that this is unknown, take starting address as literal
 | 
			
		||||
				// and check against memory windows.
 | 
			
		||||
				//
 | 
			
		||||
				// (ignoring colour memory...)
 | 
			
		||||
				// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
 | 
			
		||||
				// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
 | 
			
		||||
				// A 32kb expanded Vic has memory in the entire low 32kb.
 | 
			
		||||
//				uint16_t starting_address = file.starting_address;
 | 
			
		||||
 | 
			
		||||
				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 
			
		||||
				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | 
			
		||||
//				if(starting_address + file_size > 0x2000)
 | 
			
		||||
//					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
//				else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
 | 
			
		||||
//					target->memory_model = Target::MemoryModel::ThirtyTwoKB;
 | 
			
		||||
//			}
 | 
			
		||||
//		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Inspect filename for configuration hints.
 | 
			
		||||
	if(!target->media.empty()) {
 | 
			
		||||
		using Region = Analyser::Static::Commodore::Vic20Target::Region;
 | 
			
		||||
 | 
			
		||||
		// Inspect filename for configuration hints.
 | 
			
		||||
		std::string lowercase_name = file_name;
 | 
			
		||||
		std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
 | 
			
		||||
 | 
			
		||||
		// Hint 1: 'ntsc' anywhere in the name implies America.
 | 
			
		||||
		if(lowercase_name.find("ntsc") != std::string::npos) {
 | 
			
		||||
			target->region = Region::American;
 | 
			
		||||
			target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Potential additional hints: check for TheC64 tags; these are Vic-20 exclusive.
 | 
			
		||||
		// Potential additional hints: check for TheC64 tags.
 | 
			
		||||
		auto final_underscore = lowercase_name.find_last_of('_');
 | 
			
		||||
		if(final_underscore != std::string::npos) {
 | 
			
		||||
			auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
 | 
			
		||||
@@ -373,50 +180,25 @@ std::unique_ptr<Analyser::Static::Target> get_target<TargetPlatform::Vic20>(
 | 
			
		||||
				target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
 | 
			
		||||
				target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
 | 
			
		||||
				if(!strcmp(next_tag, "tn")) {	// i.e. NTSC.
 | 
			
		||||
					target->region = Region::American;
 | 
			
		||||
					target->region = Analyser::Static::Commodore::Target::Region::American;
 | 
			
		||||
				}
 | 
			
		||||
				if(!strcmp(next_tag, "tp")) {	// i.e. PAL.
 | 
			
		||||
					target->region = Region::European;
 | 
			
		||||
					target->region = Analyser::Static::Commodore::Target::Region::European;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Unhandled:
 | 
			
		||||
				//
 | 
			
		||||
				//	M6:		this is a C64 file.
 | 
			
		||||
				//	MV:		this is a Vic-20 file.
 | 
			
		||||
				//	M6: 	this is a C64 file.
 | 
			
		||||
				//	MV: 	this is a Vic-20 file.
 | 
			
		||||
				//	J1/J2:	this C64 file should have the primary joystick in slot 1/2.
 | 
			
		||||
				//	RO:		this disk image should be treated as read-only.
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Attach a 1540 if there are any disks here.
 | 
			
		||||
	target->has_c1540 = !target->media.disks.empty();
 | 
			
		||||
	return target;
 | 
			
		||||
}
 | 
			
		||||
		// Attach a 1540 if there are any disks here.
 | 
			
		||||
		target->has_c1540 = !target->media.disks.empty();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType platforms,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
 | 
			
		||||
	if(platforms & TargetPlatform::Vic20) {
 | 
			
		||||
		auto vic20 = get_target<TargetPlatform::Vic20>(media, file_name, is_confident);
 | 
			
		||||
		if(vic20) {
 | 
			
		||||
			destination.push_back(std::move(vic20));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(platforms & TargetPlatform::Plus4) {
 | 
			
		||||
		auto plus4 = get_target<TargetPlatform::Plus4>(media, file_name, is_confident);
 | 
			
		||||
		if(plus4) {
 | 
			
		||||
			destination.push_back(std::move(plus4));
 | 
			
		||||
		}
 | 
			
		||||
		destination.push_back(std::move(target));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return destination;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* CommodoreAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,27 +7,26 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Commodore;
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
 | 
			
		||||
	Storage::Tape::Commodore::Parser parser(type);
 | 
			
		||||
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	Storage::Tape::Commodore::Parser parser;
 | 
			
		||||
	std::vector<File> file_list;
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(serialiser);
 | 
			
		||||
	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
 | 
			
		||||
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		if(!header) {
 | 
			
		||||
			header = parser.get_next_header(serialiser);
 | 
			
		||||
			header = parser.get_next_header(tape);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch(header->type) {
 | 
			
		||||
			case Storage::Tape::Commodore::Header::DataSequenceHeader: {
 | 
			
		||||
				File &new_file = file_list.emplace_back();
 | 
			
		||||
				File new_file;
 | 
			
		||||
				new_file.name = header->name;
 | 
			
		||||
				new_file.raw_name = header->raw_name;
 | 
			
		||||
				new_file.starting_address = header->starting_address;
 | 
			
		||||
@@ -35,36 +34,38 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSeria
 | 
			
		||||
				new_file.type = File::DataSequence;
 | 
			
		||||
 | 
			
		||||
				new_file.data.swap(header->data);
 | 
			
		||||
				while(!serialiser.is_at_end()) {
 | 
			
		||||
					header = parser.get_next_header(serialiser);
 | 
			
		||||
				while(!tape->is_at_end()) {
 | 
			
		||||
					header = parser.get_next_header(tape);
 | 
			
		||||
					if(!header) continue;
 | 
			
		||||
					if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
 | 
			
		||||
					std::ranges::copy(header->data, std::back_inserter(new_file.data));
 | 
			
		||||
					std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				file_list.push_back(new_file);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			case Storage::Tape::Commodore::Header::RelocatableProgram:
 | 
			
		||||
			case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
 | 
			
		||||
				std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(serialiser);
 | 
			
		||||
				std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
 | 
			
		||||
				if(data) {
 | 
			
		||||
					File &new_file = file_list.emplace_back();
 | 
			
		||||
					File new_file;
 | 
			
		||||
					new_file.name = header->name;
 | 
			
		||||
					new_file.raw_name = header->raw_name;
 | 
			
		||||
					new_file.starting_address = header->starting_address;
 | 
			
		||||
					new_file.ending_address = header->ending_address;
 | 
			
		||||
					new_file.data.swap(data->data);
 | 
			
		||||
					new_file.type =
 | 
			
		||||
						header->type == Storage::Tape::Commodore::Header::RelocatableProgram
 | 
			
		||||
							? File::RelocatableProgram : File::NonRelocatableProgram;
 | 
			
		||||
					new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
 | 
			
		||||
 | 
			
		||||
					file_list.push_back(new_file);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				header = parser.get_next_header(serialiser);
 | 
			
		||||
				header = parser.get_next_header(tape);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				header = parser.get_next_header(serialiser);
 | 
			
		||||
				header = parser.get_next_header(tape);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,20 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Commodore_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_Commodore_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "File.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &, TargetPlatform::Type);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Tape_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,30 +6,19 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Commodore_Target_h
 | 
			
		||||
#define Analyser_Static_Commodore_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Commodore {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Commodore {
 | 
			
		||||
 | 
			
		||||
struct Plus4Target: public Analyser::Static::Target, public Reflection::StructImpl<Plus4Target> {
 | 
			
		||||
	// TODO: region, etc.
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool has_c1541 = false;
 | 
			
		||||
 | 
			
		||||
	Plus4Target() : Analyser::Static::Target(Machine::Plus4) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Plus4Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_c1541);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Vic20Target: public Analyser::Static::Target, public Reflection::StructImpl<Vic20Target> {
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	enum class MemoryModel {
 | 
			
		||||
		Unexpanded,
 | 
			
		||||
		EightKB,
 | 
			
		||||
@@ -68,20 +57,22 @@ struct Vic20Target: public Analyser::Static::Target, public Reflection::StructIm
 | 
			
		||||
	bool has_c1540 = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Vic20Target() : Analyser::Static::Target(Machine::Vic20) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Vic20Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(enabled_ram.bank0);
 | 
			
		||||
		DeclareField(enabled_ram.bank1);
 | 
			
		||||
		DeclareField(enabled_ram.bank2);
 | 
			
		||||
		DeclareField(enabled_ram.bank3);
 | 
			
		||||
		DeclareField(enabled_ram.bank5);
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		DeclareField(has_c1540);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Vic20) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(enabled_ram.bank0);
 | 
			
		||||
			DeclareField(enabled_ram.bank1);
 | 
			
		||||
			DeclareField(enabled_ram.bank2);
 | 
			
		||||
			DeclareField(enabled_ram.bank3);
 | 
			
		||||
			DeclareField(enabled_ram.bank5);
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			DeclareField(has_c1540);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Commodore_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -11,18 +11,13 @@
 | 
			
		||||
#include "Kernel.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::MOS6502;
 | 
			
		||||
namespace {
 | 
			
		||||
namespace  {
 | 
			
		||||
 | 
			
		||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
 | 
			
		||||
 | 
			
		||||
struct MOS6502Disassembler {
 | 
			
		||||
 | 
			
		||||
static void AddToDisassembly(
 | 
			
		||||
	PartialDisassembly &disassembly,
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	uint16_t entry_point
 | 
			
		||||
) {
 | 
			
		||||
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
	disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
	uint16_t address = entry_point;
 | 
			
		||||
	while(true) {
 | 
			
		||||
@@ -80,25 +75,23 @@ static void AddToDisassembly(
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Decode operation.
 | 
			
		||||
#define RM_INSTRUCTION(base, op)									\
 | 
			
		||||
	case base+0x09: case base+0x05: case base+0x15: case base+0x01:	\
 | 
			
		||||
	case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
			
		||||
		instruction.operation = op;									\
 | 
			
		||||
#define RM_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19:	\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define URM_INSTRUCTION(base, op)																					\
 | 
			
		||||
#define URM_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base+0x07: case base+0x17: case base+0x03: case base+0x13: case base+0x0f: case base+0x1f: case base+0x1b:	\
 | 
			
		||||
		instruction.operation = op;																					\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define M_INSTRUCTION(base, op)														\
 | 
			
		||||
#define M_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base+0x0a: case base+0x06: case base+0x16: case base+0x0e: case base+0x1e:	\
 | 
			
		||||
		instruction.operation = op;													\
 | 
			
		||||
		instruction.operation = op;	\
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
#define IM_INSTRUCTION(base, op)					\
 | 
			
		||||
#define IM_INSTRUCTION(base, op)	\
 | 
			
		||||
	case base:	instruction.operation = op; break;
 | 
			
		||||
 | 
			
		||||
		switch(operation) {
 | 
			
		||||
			default:
 | 
			
		||||
				instruction.operation = Instruction::KIL;
 | 
			
		||||
@@ -243,7 +236,7 @@ static void AddToDisassembly(
 | 
			
		||||
			case Instruction::Relative: {
 | 
			
		||||
				std::size_t operand_address = address_mapper(address);
 | 
			
		||||
				if(operand_address >= memory.size()) return;
 | 
			
		||||
				++address;
 | 
			
		||||
				address++;
 | 
			
		||||
 | 
			
		||||
				instruction.operand = memory[operand_address];
 | 
			
		||||
			}
 | 
			
		||||
@@ -266,10 +259,7 @@ static void AddToDisassembly(
 | 
			
		||||
		disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
 | 
			
		||||
 | 
			
		||||
		// TODO: something wider-ranging than this
 | 
			
		||||
		if(
 | 
			
		||||
			instruction.addressing_mode == Instruction::Absolute ||
 | 
			
		||||
			instruction.addressing_mode == Instruction::ZeroPage
 | 
			
		||||
		) {
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
 | 
			
		||||
			const size_t mapped_address = address_mapper(instruction.operand);
 | 
			
		||||
			const bool is_external = mapped_address >= memory.size();
 | 
			
		||||
 | 
			
		||||
@@ -282,56 +272,39 @@ static void AddToDisassembly(
 | 
			
		||||
				case Instruction::ADC: case Instruction::SBC:
 | 
			
		||||
				case Instruction::LAS:
 | 
			
		||||
				case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::STY: case Instruction::STX: case Instruction::STA:
 | 
			
		||||
				case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
 | 
			
		||||
				case Instruction::TAS:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
				case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
 | 
			
		||||
				case Instruction::DCP: case Instruction::ISC:
 | 
			
		||||
				case Instruction::INC: case Instruction::DEC:
 | 
			
		||||
				case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies)
 | 
			
		||||
						.insert(instruction.operand);
 | 
			
		||||
					(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Decide on overall flow control.
 | 
			
		||||
 | 
			
		||||
		// All relative instructions are flow control.
 | 
			
		||||
		if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
 | 
			
		||||
		if(instruction.operation == Instruction::BRK) return;	// TODO: check whether IRQ vector is within memory range
 | 
			
		||||
		if(instruction.operation == Instruction::JSR) {
 | 
			
		||||
			disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.operation == Instruction::JMP) {
 | 
			
		||||
			if(instruction.addressing_mode == Instruction::Absolute)
 | 
			
		||||
				disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if(instruction.addressing_mode == Instruction::Relative) {
 | 
			
		||||
			uint16_t destination = uint16_t(address + int8_t(instruction.operand));
 | 
			
		||||
			disassembly.remaining_entry_points.push_back(destination);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch(instruction.operation) {
 | 
			
		||||
			default: break;
 | 
			
		||||
 | 
			
		||||
			case Instruction::KIL:
 | 
			
		||||
			case Instruction::RTS:
 | 
			
		||||
			case Instruction::RTI:
 | 
			
		||||
			case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
 | 
			
		||||
				disassembly.implicit_entry_points.push_back(address);
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
			case Instruction::JMP:
 | 
			
		||||
				// Adding a new entry point for relative jumps was handled above.
 | 
			
		||||
				if(instruction.addressing_mode == Instruction::Absolute) {
 | 
			
		||||
					disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
				}
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
			case Instruction::JSR:
 | 
			
		||||
				disassembly.remaining_entry_points.push_back(instruction.operand);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -343,10 +316,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points) {
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(
 | 
			
		||||
		memory,
 | 
			
		||||
		address_mapper,
 | 
			
		||||
		entry_points,
 | 
			
		||||
		false
 | 
			
		||||
	);
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,20 +6,27 @@
 | 
			
		||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef AddressMapper_hpp
 | 
			
		||||
#define AddressMapper_hpp
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Disassembler {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Disassembler {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Provides an address mapper that relocates a chunk of memory so that it starts at
 | 
			
		||||
	address @c start_address.
 | 
			
		||||
*/
 | 
			
		||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(const T start_address) {
 | 
			
		||||
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
 | 
			
		||||
	return [start_address](T argument) {
 | 
			
		||||
		return size_t(argument - start_address);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* AddressMapper_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -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,60 +11,56 @@
 | 
			
		||||
#include "Kernel.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Z80;
 | 
			
		||||
namespace {
 | 
			
		||||
namespace  {
 | 
			
		||||
 | 
			
		||||
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
 | 
			
		||||
 | 
			
		||||
class Accessor {
 | 
			
		||||
public:
 | 
			
		||||
	Accessor(
 | 
			
		||||
		const std::vector<uint8_t> &memory,
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
		uint16_t address
 | 
			
		||||
	) :
 | 
			
		||||
		memory_(memory), address_mapper_(address_mapper), address_(address) {}
 | 
			
		||||
	public:
 | 
			
		||||
		Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
 | 
			
		||||
			memory_(memory), address_mapper_(address_mapper), address_(address) {}
 | 
			
		||||
 | 
			
		||||
	uint8_t byte() {
 | 
			
		||||
		std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
		++address_;
 | 
			
		||||
		if(mapped_address >= memory_.size()) {
 | 
			
		||||
			overrun_ = true;
 | 
			
		||||
			return 0xff;
 | 
			
		||||
		uint8_t byte() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			address_++;
 | 
			
		||||
			if(mapped_address >= memory_.size()) {
 | 
			
		||||
				overrun_ = true;
 | 
			
		||||
				return 0xff;
 | 
			
		||||
			}
 | 
			
		||||
			return memory_[mapped_address];
 | 
			
		||||
		}
 | 
			
		||||
		return memory_[mapped_address];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint16_t word() {
 | 
			
		||||
		uint8_t low = byte();
 | 
			
		||||
		uint8_t high = byte();
 | 
			
		||||
		return uint16_t(low | (high << 8));
 | 
			
		||||
	}
 | 
			
		||||
		uint16_t word() {
 | 
			
		||||
			uint8_t low = byte();
 | 
			
		||||
			uint8_t high = byte();
 | 
			
		||||
			return uint16_t(low | (high << 8));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	bool overrun() const {
 | 
			
		||||
		return overrun_;
 | 
			
		||||
	}
 | 
			
		||||
		bool overrun() {
 | 
			
		||||
			return overrun_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	bool at_end() const {
 | 
			
		||||
		std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
		return mapped_address >= memory_.size();
 | 
			
		||||
	}
 | 
			
		||||
		bool at_end() {
 | 
			
		||||
			std::size_t mapped_address = address_mapper_(address_);
 | 
			
		||||
			return mapped_address >= memory_.size();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	uint16_t address() const {
 | 
			
		||||
		return address_;
 | 
			
		||||
	}
 | 
			
		||||
		uint16_t address() {
 | 
			
		||||
			return address_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::vector<uint8_t> &memory_;
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper_;
 | 
			
		||||
	uint16_t address_;
 | 
			
		||||
	bool overrun_ = false;
 | 
			
		||||
	private:
 | 
			
		||||
		const std::vector<uint8_t> &memory_;
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper_;
 | 
			
		||||
		uint16_t address_;
 | 
			
		||||
		bool overrun_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr uint8_t x(const uint8_t v) { return v >> 6; }
 | 
			
		||||
constexpr uint8_t y(const uint8_t v) { return (v >> 3) & 7; }
 | 
			
		||||
constexpr uint8_t q(const uint8_t v) { return (v >> 3) & 1; }
 | 
			
		||||
constexpr uint8_t p(const uint8_t v) { return (v >> 4) & 3; }
 | 
			
		||||
constexpr uint8_t z(const uint8_t v) { return v & 7; }
 | 
			
		||||
#define x(v) (v >> 6)
 | 
			
		||||
#define y(v) ((v >> 3) & 7)
 | 
			
		||||
#define q(v) ((v >> 3) & 1)
 | 
			
		||||
#define p(v) ((v >> 4) & 3)
 | 
			
		||||
#define z(v) (v & 7)
 | 
			
		||||
 | 
			
		||||
Instruction::Condition condition_table[] = {
 | 
			
		||||
	Instruction::Condition::NZ,		Instruction::Condition::Z,
 | 
			
		||||
@@ -87,12 +83,8 @@ Instruction::Location register_pair_table2[] = {
 | 
			
		||||
	Instruction::Location::AF
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Instruction::Location RegisterTableEntry(
 | 
			
		||||
	const int offset, Accessor &accessor,
 | 
			
		||||
	Instruction &instruction,
 | 
			
		||||
	const bool needs_indirect_offset
 | 
			
		||||
) {
 | 
			
		||||
	static constexpr Instruction::Location register_table[] = {
 | 
			
		||||
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	Instruction::Location register_table[] = {
 | 
			
		||||
		Instruction::Location::B,	Instruction::Location::C,
 | 
			
		||||
		Instruction::Location::D,	Instruction::Location::E,
 | 
			
		||||
		Instruction::Location::H,	Instruction::Location::L,
 | 
			
		||||
@@ -100,7 +92,7 @@ Instruction::Location RegisterTableEntry(
 | 
			
		||||
		Instruction::Location::A
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Instruction::Location location = register_table[offset];
 | 
			
		||||
	Instruction::Location location = register_table[offset];
 | 
			
		||||
	if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
 | 
			
		||||
		instruction.offset = accessor.byte() - 128;
 | 
			
		||||
	}
 | 
			
		||||
@@ -108,7 +100,7 @@ Instruction::Location RegisterTableEntry(
 | 
			
		||||
	return location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr Instruction::Operation alu_table[] = {
 | 
			
		||||
Instruction::Operation alu_table[] = {
 | 
			
		||||
	Instruction::Operation::ADD,
 | 
			
		||||
	Instruction::Operation::ADC,
 | 
			
		||||
	Instruction::Operation::SUB,
 | 
			
		||||
@@ -119,7 +111,7 @@ constexpr Instruction::Operation alu_table[] = {
 | 
			
		||||
	Instruction::Operation::CP
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr Instruction::Operation rotation_table[] = {
 | 
			
		||||
Instruction::Operation rotation_table[] = {
 | 
			
		||||
	Instruction::Operation::RLC,
 | 
			
		||||
	Instruction::Operation::RRC,
 | 
			
		||||
	Instruction::Operation::RL,
 | 
			
		||||
@@ -130,32 +122,19 @@ constexpr Instruction::Operation rotation_table[] = {
 | 
			
		||||
	Instruction::Operation::SRL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr Instruction::Operation block_table[][4] = {
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDI, Instruction::Operation::CPI,
 | 
			
		||||
		Instruction::Operation::INI, Instruction::Operation::OUTI
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDD, Instruction::Operation::CPD,
 | 
			
		||||
		Instruction::Operation::IND, Instruction::Operation::OUTD
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDIR, Instruction::Operation::CPIR,
 | 
			
		||||
		Instruction::Operation::INIR, Instruction::Operation::OTIR
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Instruction::Operation::LDDR, Instruction::Operation::CPDR,
 | 
			
		||||
		Instruction::Operation::INDR, Instruction::Operation::OTDR
 | 
			
		||||
	},
 | 
			
		||||
Instruction::Operation block_table[][4] = {
 | 
			
		||||
	{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
 | 
			
		||||
	{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
 | 
			
		||||
	{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
 | 
			
		||||
	{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
 | 
			
		||||
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	if(!x(operation)) {
 | 
			
		||||
		instruction.operation = rotation_table[y(operation)];
 | 
			
		||||
		instruction.source = instruction.destination =
 | 
			
		||||
			RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
		instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
	} else {
 | 
			
		||||
		instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
		instruction.source = Instruction::Location::Operand;
 | 
			
		||||
@@ -169,7 +148,7 @@ void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
 | 
			
		||||
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
 | 
			
		||||
	const uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
	switch(x(operation)) {
 | 
			
		||||
@@ -191,8 +170,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.destination = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 1:
 | 
			
		||||
@@ -201,8 +179,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
 | 
			
		||||
					if(y(operation) == 6) {
 | 
			
		||||
						instruction.source = Instruction::Location::None;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.source =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
				break;
 | 
			
		||||
				case 2:
 | 
			
		||||
@@ -213,13 +190,11 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
 | 
			
		||||
				case 3:
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					if(q(operation)) {
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand_Indirect;
 | 
			
		||||
					} else {
 | 
			
		||||
						instruction.destination = Instruction::Location::Operand_Indirect;
 | 
			
		||||
						instruction.source =
 | 
			
		||||
							RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					}
 | 
			
		||||
					instruction.operand = accessor.word();
 | 
			
		||||
				break;
 | 
			
		||||
@@ -227,8 +202,7 @@ void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool
 | 
			
		||||
					instruction.operation = Instruction::Operation::NEG;
 | 
			
		||||
				break;
 | 
			
		||||
				case 5:
 | 
			
		||||
					instruction.operation =
 | 
			
		||||
						y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
 | 
			
		||||
					instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
 | 
			
		||||
				break;
 | 
			
		||||
				case 6:
 | 
			
		||||
					instruction.operation = Instruction::Operation::IM;
 | 
			
		||||
@@ -279,7 +253,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
	} hl_substitution = None;
 | 
			
		||||
 | 
			
		||||
	while(true) {
 | 
			
		||||
		const uint8_t operation = accessor.byte();
 | 
			
		||||
		uint8_t operation = accessor.byte();
 | 
			
		||||
 | 
			
		||||
		switch(x(operation)) {
 | 
			
		||||
			case 0:
 | 
			
		||||
@@ -369,18 +343,15 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
					break;
 | 
			
		||||
					case 4:
 | 
			
		||||
						instruction.operation = Instruction::Operation::INC;
 | 
			
		||||
						instruction.source = instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 5:
 | 
			
		||||
						instruction.operation = Instruction::Operation::DEC;
 | 
			
		||||
						instruction.source = instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					break;
 | 
			
		||||
					case 6:
 | 
			
		||||
						instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
						instruction.destination =
 | 
			
		||||
							RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
						instruction.source = Instruction::Location::Operand;
 | 
			
		||||
						instruction.operand = accessor.byte();
 | 
			
		||||
					break;
 | 
			
		||||
@@ -403,10 +374,8 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
					instruction.operation = Instruction::Operation::HALT;
 | 
			
		||||
				} else {
 | 
			
		||||
					instruction.operation = Instruction::Operation::LD;
 | 
			
		||||
					instruction.source =
 | 
			
		||||
						RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.destination =
 | 
			
		||||
						RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
					instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
 | 
			
		||||
				}
 | 
			
		||||
			break;
 | 
			
		||||
			case 2:
 | 
			
		||||
@@ -548,14 +517,10 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
			instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
 | 
			
		||||
			if(instruction.source == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.source =
 | 
			
		||||
					hl_substitution == IX ?
 | 
			
		||||
						Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
				instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			if(instruction.destination == Instruction::Location::HL_Indirect) {
 | 
			
		||||
				instruction.destination =
 | 
			
		||||
					hl_substitution == IX ?
 | 
			
		||||
						Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
				instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
 | 
			
		||||
			}
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
@@ -577,12 +542,7 @@ void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Z80Disassembler {
 | 
			
		||||
	static void AddToDisassembly(
 | 
			
		||||
		PartialDisassembly &disassembly,
 | 
			
		||||
		const std::vector<uint8_t> &memory,
 | 
			
		||||
		const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
		const uint16_t entry_point
 | 
			
		||||
	) {
 | 
			
		||||
	static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
 | 
			
		||||
		disassembly.disassembly.internal_calls.insert(entry_point);
 | 
			
		||||
		Accessor accessor(memory, address_mapper, entry_point);
 | 
			
		||||
 | 
			
		||||
@@ -629,7 +589,7 @@ struct Z80Disassembler {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Add any (potentially) newly-discovered entry point.
 | 
			
		||||
			// Add any (potentially) newly discovered entry point.
 | 
			
		||||
			if(	instruction.operation == Instruction::Operation::JP ||
 | 
			
		||||
				instruction.operation == Instruction::Operation::JR ||
 | 
			
		||||
				instruction.operation == Instruction::Operation::CALL ||
 | 
			
		||||
@@ -638,37 +598,22 @@ struct Z80Disassembler {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
 | 
			
		||||
			switch(instruction.operation) {
 | 
			
		||||
				default: break;
 | 
			
		||||
			if(instruction.condition != Instruction::Condition::None)	continue;
 | 
			
		||||
 | 
			
		||||
				case Instruction::Operation::RET:
 | 
			
		||||
				case Instruction::Operation::RETI:
 | 
			
		||||
				case Instruction::Operation::RETN:
 | 
			
		||||
				case Instruction::Operation::JP:
 | 
			
		||||
				case Instruction::Operation::JR:
 | 
			
		||||
					if(instruction.condition == Instruction::Condition::None) {
 | 
			
		||||
						disassembly.implicit_entry_points.push_back(accessor.address());
 | 
			
		||||
						return;
 | 
			
		||||
					}
 | 
			
		||||
			}
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RET)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RETI)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::RETN)	return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::JP)		return;
 | 
			
		||||
			if(instruction.operation == Instruction::Operation::JR)		return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}	// end of anonymous namespace
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Disassembly Analyser::Static::Z80::Disassemble(
 | 
			
		||||
	const std::vector<uint8_t> &memory,
 | 
			
		||||
	const std::function<std::size_t(uint16_t)> &address_mapper,
 | 
			
		||||
	std::vector<uint16_t> entry_points,
 | 
			
		||||
	Approach approach)
 | 
			
		||||
{
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
 | 
			
		||||
		memory,
 | 
			
		||||
		address_mapper,
 | 
			
		||||
		entry_points,
 | 
			
		||||
		approach == Approach::Exhaustive
 | 
			
		||||
	);
 | 
			
		||||
	std::vector<uint16_t> entry_points) {
 | 
			
		||||
	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,14 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/AppleII/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//AppleIIgs/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//Oric/Target.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "../AppleII/Target.hpp"
 | 
			
		||||
#include "../AppleIIgs/Target.hpp"
 | 
			
		||||
#include "../Oric/Target.hpp"
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
@@ -47,12 +47,7 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
@@ -60,15 +55,14 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
 | 
			
		||||
	// If the disk image is too large for a 5.25" disk, map this to the IIgs.
 | 
			
		||||
	if(disk->maximum_head_position() > Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
	if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
		targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
 | 
			
		||||
		targets.back()->media = media;
 | 
			
		||||
		return targets;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Grab track 0, sector 0: the boot sector.
 | 
			
		||||
	const auto track_zero =
 | 
			
		||||
		disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
 | 
			
		||||
		Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
 | 
			
		||||
 | 
			
		||||
@@ -95,8 +89,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(
 | 
			
		||||
	// If the boot sector looks like it's intended for the Oric, create an Oric.
 | 
			
		||||
	// Otherwise go with the Apple II.
 | 
			
		||||
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
 | 
			
		||||
 | 
			
		||||
	bool did_read_shift_register = false;
 | 
			
		||||
	bool is_oric = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,22 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_DiskII_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_DiskII_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::DiskII {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace DiskII {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Parsers/FAT.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
@@ -26,12 +26,7 @@ bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
@@ -45,8 +40,6 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
 | 
			
		||||
	target->basic_version = Target::BASICVersion::Any;
 | 
			
		||||
 | 
			
		||||
	// Inspect any supplied disks.
 | 
			
		||||
	//
 | 
			
		||||
	// TODO: how best can these be discerned from MS-DOS and MSX disks?
 | 
			
		||||
	if(!media.disks.empty()) {
 | 
			
		||||
		// DOS will be needed.
 | 
			
		||||
		target->dos = Target::DOS::EXDOS;
 | 
			
		||||
@@ -77,8 +70,7 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
 | 
			
		||||
 | 
			
		||||
			if(!has_exdos_ini) {
 | 
			
		||||
				if(did_pick_file) {
 | 
			
		||||
					target->loading_command =
 | 
			
		||||
						std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
 | 
			
		||||
					target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
 | 
			
		||||
				} else {
 | 
			
		||||
					target->loading_command = ":dir\n";
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,22 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Enterprise {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Enterprise {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,18 @@
 | 
			
		||||
//  Copyright © 2021 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Enterprise_Target_h
 | 
			
		||||
#define Analyser_Static_Enterprise_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Enterprise {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Enterprise {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
 | 
			
		||||
@@ -30,23 +33,25 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Speed speed = Speed::FourMHz;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Enterprise) {}
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Enterprise) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
			AnnounceEnum(EXOSVersion);
 | 
			
		||||
			AnnounceEnum(BASICVersion);
 | 
			
		||||
			AnnounceEnum(DOS);
 | 
			
		||||
			AnnounceEnum(Speed);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		AnnounceEnum(EXOSVersion);
 | 
			
		||||
		AnnounceEnum(BASICVersion);
 | 
			
		||||
		AnnounceEnum(DOS);
 | 
			
		||||
		AnnounceEnum(Speed);
 | 
			
		||||
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		DeclareField(exos_version);
 | 
			
		||||
		DeclareField(basic_version);
 | 
			
		||||
		DeclareField(dos);
 | 
			
		||||
		DeclareField(speed);
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			DeclareField(exos_version);
 | 
			
		||||
			DeclareField(basic_version);
 | 
			
		||||
			DeclareField(dos);
 | 
			
		||||
			DeclareField(speed);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Enterprise_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,106 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 05/12/2023.
 | 
			
		||||
//  Copyright 2023 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Track/TrackSerialiser.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/SegmentParser.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::FAT12::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType platforms,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
	auto &disk = media.disks.front();
 | 
			
		||||
	TargetList targets;
 | 
			
		||||
 | 
			
		||||
	// Total list of potential platforms is:
 | 
			
		||||
	//
 | 
			
		||||
	//	* the Enterprise (and, by extension, CP/M-targetted software);
 | 
			
		||||
	//	* the Atari ST;
 | 
			
		||||
	//	* the MSX (ditto on CP/M); and
 | 
			
		||||
	//	* the PC.
 | 
			
		||||
	//
 | 
			
		||||
	// (though the MSX and Atari ST don't currently call in here for now)
 | 
			
		||||
 | 
			
		||||
	// If the disk image is very small or large, map it to the PC. That's the only option old enough
 | 
			
		||||
	// to have used 5.25" media.
 | 
			
		||||
	if(disk->maximum_head_position() <= Storage::Disk::HeadPosition(40)) {
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Attempt to grab MFM track 0, sector 1: the boot sector.
 | 
			
		||||
	const auto track_zero =
 | 
			
		||||
		disk->track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
 | 
			
		||||
	const auto sector_map = Storage::Encodings::MFM::sectors_from_segment(
 | 
			
		||||
			Storage::Disk::track_serialisation(
 | 
			
		||||
				*track_zero,
 | 
			
		||||
				Storage::Encodings::MFM::MFMBitLength
 | 
			
		||||
			), Storage::Encodings::MFM::Density::Double);
 | 
			
		||||
 | 
			
		||||
	// If no sectors were found, assume this disk was either single density or high density, which both imply the PC.
 | 
			
		||||
	if(sector_map.empty() || sector_map.size() > 10) {
 | 
			
		||||
		return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *boot_sector = nullptr;
 | 
			
		||||
	for(const auto &pair: sector_map) {
 | 
			
		||||
		if(pair.second.address.sector == 1) {
 | 
			
		||||
			boot_sector = &pair.second;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// This shouldn't technically be possible since the disk has been identified as FAT12, but be safe.
 | 
			
		||||
	if(!boot_sector) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check for key phrases that imply a PC disk.
 | 
			
		||||
	const auto &sample = boot_sector->samples[0];
 | 
			
		||||
	const std::vector<std::string> pc_strings = {
 | 
			
		||||
		// MS-DOS strings.
 | 
			
		||||
		"MSDOS",
 | 
			
		||||
		"Non-System disk or disk error",
 | 
			
		||||
		// DOS Plus strings.
 | 
			
		||||
		"Insert a SYSTEM disk",
 | 
			
		||||
	};
 | 
			
		||||
	for(const auto &string: pc_strings) {
 | 
			
		||||
		if(
 | 
			
		||||
			std::search(sample.begin(), sample.end(), string.begin(), string.end()) != sample.end()
 | 
			
		||||
		) {
 | 
			
		||||
			return Analyser::Static::PCCompatible::GetTargets(media, file_name, platforms, true);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: look for a COM, EXE or BAT, inspect. AUTOEXEC.BAT and/or CONFIG.SYS could be either PC or MSX.
 | 
			
		||||
	// Disassembling the boot sector doesn't necessarily work, as several Enterprise titles out there in the wild seem
 | 
			
		||||
	// to have been created by WINIMAGE which adds an x86 PC-style boot sector.
 | 
			
		||||
 | 
			
		||||
	// Enterprise notes: EXOS files all start with a 16-byte header which should begin with a 0 and then have a type
 | 
			
		||||
	// byte that will be 0xa or lower; cf http://epbas.lgb.hu/readme.html
 | 
			
		||||
	//
 | 
			
		||||
	// Some disks commonly passed around as Enterprise software are actually CP/M software, expecting IS-DOS (the CP/M
 | 
			
		||||
	// clone) to be present. It's certainly possible the same could be true of MSX disks and MSX-DOS. So analysing COM
 | 
			
		||||
	// files probably means searching for CALL 5s and/or INT 21hs, if not a more rigorous disassembly.
 | 
			
		||||
	//
 | 
			
		||||
	// I have not been able to locate a copy of IS-DOS so there's probably not much that can be done here; perhaps I
 | 
			
		||||
	// could redirect to an MSX2 with MSX-DOS2? Though it'd be nicer if I had a machine that was pure CP/M.
 | 
			
		||||
 | 
			
		||||
	// Being unable to prove that this is a PC disk, throw it to the Enterprise.
 | 
			
		||||
	return Analyser::Static::Enterprise::GetTargets(media, file_name, platforms, false);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 05/12/2023.
 | 
			
		||||
//  Copyright 2023 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::FAT12 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -6,11 +6,14 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Cartridge_hpp
 | 
			
		||||
#define Cartridge_hpp
 | 
			
		||||
 | 
			
		||||
#include "Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace MSX {
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Extends the base cartridge class by adding a (guess at) the banking scheme.
 | 
			
		||||
@@ -26,8 +29,12 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
 | 
			
		||||
	};
 | 
			
		||||
	const Type type;
 | 
			
		||||
 | 
			
		||||
	Cartridge(const std::vector<Segment> &segments, const Type type) :
 | 
			
		||||
	Cartridge(const std::vector<Segment> &segments, Type type) :
 | 
			
		||||
		Storage::Cartridge::Cartridge(segments), type(type) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Cartridge_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Disassembler/Z80.hpp"
 | 
			
		||||
#include "Analyser/Static//Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "../Disassembler/Z80.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
@@ -27,8 +27,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
			
		||||
	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
 | 
			
		||||
	if(segment.data.size() & 0x1fff) {
 | 
			
		||||
		std::vector<uint8_t> truncated_data;
 | 
			
		||||
		const auto truncated_size =
 | 
			
		||||
			std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
 | 
			
		||||
		std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
 | 
			
		||||
		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
 | 
			
		||||
		output_segments.emplace_back(start_address, truncated_data);
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -38,11 +37,6 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
 | 
			
		||||
	auto target = std::make_unique<Analyser::Static::MSX::Target>();
 | 
			
		||||
	target->confidence = confidence;
 | 
			
		||||
 | 
			
		||||
	// Observation: all ROMs of 48kb or less are from the MSX 1 era.
 | 
			
		||||
	if(segment.data.size() < 48*1024) {
 | 
			
		||||
		target->model = Analyser::Static::MSX::Target::Model::MSX1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(type == Analyser::Static::MSX::Cartridge::Type::None) {
 | 
			
		||||
		target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -83,7 +77,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		if(segments.size() != 1) continue;
 | 
			
		||||
 | 
			
		||||
		// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
 | 
			
		||||
		const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
 | 
			
		||||
		Storage::Cartridge::Cartridge::Segment segment = segments.front();
 | 
			
		||||
		const size_t data_size = segment.data.size();
 | 
			
		||||
		if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
 | 
			
		||||
 | 
			
		||||
@@ -102,11 +96,10 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		// Reject cartridge if the ROM header wasn't found.
 | 
			
		||||
		if(!found_start) continue;
 | 
			
		||||
 | 
			
		||||
		const uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
 | 
			
		||||
		uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
 | 
			
		||||
		// TODO: check for a rational init address?
 | 
			
		||||
 | 
			
		||||
		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
 | 
			
		||||
		// Bonus observation: all such ROMs are from the MSX 1 era.
 | 
			
		||||
		if(data_size <= 0xc000) {
 | 
			
		||||
			targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
 | 
			
		||||
			continue;
 | 
			
		||||
@@ -116,16 +109,97 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
		// be at play; disassemble to try to figure it out.
 | 
			
		||||
		std::vector<uint8_t> first_8k;
 | 
			
		||||
		first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
 | 
			
		||||
		const Analyser::Static::Z80::Disassembly disassembly =
 | 
			
		||||
		Analyser::Static::Z80::Disassembly disassembly =
 | 
			
		||||
			Analyser::Static::Z80::Disassemble(
 | 
			
		||||
				first_8k,
 | 
			
		||||
				Analyser::Static::Disassembler::OffsetMapper(start_address),
 | 
			
		||||
				{ init_address },
 | 
			
		||||
				Analyser::Static::Z80::Approach::Exhaustive
 | 
			
		||||
				{ init_address }
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
//		// Look for a indirect store followed by an unconditional JP or CALL into another
 | 
			
		||||
//		// segment, that's a fairly explicit sign where found.
 | 
			
		||||
		using Instruction = Analyser::Static::Z80::Instruction;
 | 
			
		||||
		const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
 | 
			
		||||
		std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
 | 
			
		||||
		bool is_ascii = false;
 | 
			
		||||
//		auto iterator = instructions.begin();
 | 
			
		||||
//		while(iterator != instructions.end()) {
 | 
			
		||||
//			auto next_iterator = iterator;
 | 
			
		||||
//			next_iterator++;
 | 
			
		||||
//			if(next_iterator == instructions.end()) break;
 | 
			
		||||
//
 | 
			
		||||
//			if(	iterator->second.operation == Instruction::Operation::LD &&
 | 
			
		||||
//				iterator->second.destination == Instruction::Location::Operand_Indirect &&
 | 
			
		||||
//				(
 | 
			
		||||
//					iterator->second.operand == 0x5000 ||
 | 
			
		||||
//					iterator->second.operand == 0x6000 ||
 | 
			
		||||
//					iterator->second.operand == 0x6800 ||
 | 
			
		||||
//					iterator->second.operand == 0x7000 ||
 | 
			
		||||
//					iterator->second.operand == 0x77ff ||
 | 
			
		||||
//					iterator->second.operand == 0x7800 ||
 | 
			
		||||
//					iterator->second.operand == 0x8000 ||
 | 
			
		||||
//					iterator->second.operand == 0x9000 ||
 | 
			
		||||
//					iterator->second.operand == 0xa000
 | 
			
		||||
//				) &&
 | 
			
		||||
//				(
 | 
			
		||||
//					next_iterator->second.operation == Instruction::Operation::CALL ||
 | 
			
		||||
//					next_iterator->second.operation == Instruction::Operation::JP
 | 
			
		||||
//				) &&
 | 
			
		||||
//				((next_iterator->second.operand >> 13) != (0x4000 >> 13))
 | 
			
		||||
//			) {
 | 
			
		||||
//				const uint16_t address = uint16_t(next_iterator->second.operand);
 | 
			
		||||
//				switch(iterator->second.operand) {
 | 
			
		||||
//					case 0x6000:
 | 
			
		||||
//						if(address >= 0x6000 && address < 0x8000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x6800:
 | 
			
		||||
//						if(address >= 0x6000 && address < 0x6800) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x7000:
 | 
			
		||||
//						if(address >= 0x6000 && address < 0x8000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
 | 
			
		||||
//						}
 | 
			
		||||
//						if(address >= 0x7000 && address < 0x7800) {
 | 
			
		||||
//							is_ascii = true;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x77ff:
 | 
			
		||||
//						if(address >= 0x7000 && address < 0x7800) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x7800:
 | 
			
		||||
//						if(address >= 0xa000 && address < 0xc000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x8000:
 | 
			
		||||
//						if(address >= 0x8000 && address < 0xa000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0x9000:
 | 
			
		||||
//						if(address >= 0x8000 && address < 0xa000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0xa000:
 | 
			
		||||
//						if(address >= 0xa000 && address < 0xc000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//					case 0xb000:
 | 
			
		||||
//						if(address >= 0xa000 && address < 0xc000) {
 | 
			
		||||
//							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
 | 
			
		||||
//						}
 | 
			
		||||
//					break;
 | 
			
		||||
//				}
 | 
			
		||||
//			}
 | 
			
		||||
//
 | 
			
		||||
//			iterator = next_iterator;
 | 
			
		||||
 | 
			
		||||
		// Look for LD (nnnn), A instructions, and collate those addresses.
 | 
			
		||||
		std::map<uint16_t, int> address_counts;
 | 
			
		||||
@@ -137,60 +211,56 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
 | 
			
		||||
		const auto ascii_8kb_total =
 | 
			
		||||
			address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
 | 
			
		||||
		const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
 | 
			
		||||
		const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
 | 
			
		||||
		const auto konami_with_scc_total =
 | 
			
		||||
			address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
 | 
			
		||||
		// Weight confidences by number of observed hits.
 | 
			
		||||
		float total_hits =
 | 
			
		||||
			float(
 | 
			
		||||
				address_counts[0x6000] + address_counts[0x6800] +
 | 
			
		||||
				address_counts[0x7000] + address_counts[0x7800] +
 | 
			
		||||
				address_counts[0x77ff] + address_counts[0x8000] +
 | 
			
		||||
				address_counts[0xa000] + address_counts[0x5000] +
 | 
			
		||||
				address_counts[0x9000] + address_counts[0xb000]
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
 | 
			
		||||
 | 
			
		||||
		const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
 | 
			
		||||
		const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
 | 
			
		||||
		const bool is_konami = (konami_total * 5) / (total_hits * 3);
 | 
			
		||||
		const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
 | 
			
		||||
 | 
			
		||||
		if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
 | 
			
		||||
			targets.push_back(CartridgeTarget(
 | 
			
		||||
				segment,
 | 
			
		||||
				start_address,
 | 
			
		||||
				Analyser::Static::MSX::Cartridge::ASCII8kb,
 | 
			
		||||
				float(ascii_8kb_total) / float(total_hits)));
 | 
			
		||||
		}
 | 
			
		||||
		if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
 | 
			
		||||
			targets.push_back(CartridgeTarget(
 | 
			
		||||
				segment,
 | 
			
		||||
				start_address,
 | 
			
		||||
				Analyser::Static::MSX::Cartridge::ASCII16kb,
 | 
			
		||||
				float(ascii_16kb_total) / float(total_hits)));
 | 
			
		||||
		}
 | 
			
		||||
		if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
 | 
			
		||||
		targets.push_back(CartridgeTarget(
 | 
			
		||||
			segment,
 | 
			
		||||
			start_address,
 | 
			
		||||
			Analyser::Static::MSX::Cartridge::ASCII8kb,
 | 
			
		||||
			float(	address_counts[0x6000] +
 | 
			
		||||
					address_counts[0x6800] +
 | 
			
		||||
					address_counts[0x7000] +
 | 
			
		||||
					address_counts[0x7800]) / total_hits));
 | 
			
		||||
		targets.push_back(CartridgeTarget(
 | 
			
		||||
			segment,
 | 
			
		||||
			start_address,
 | 
			
		||||
			Analyser::Static::MSX::Cartridge::ASCII16kb,
 | 
			
		||||
			float(	address_counts[0x6000] +
 | 
			
		||||
					address_counts[0x7000] +
 | 
			
		||||
					address_counts[0x77ff]) / total_hits));
 | 
			
		||||
		if(!is_ascii) {
 | 
			
		||||
			targets.push_back(CartridgeTarget(
 | 
			
		||||
				segment,
 | 
			
		||||
				start_address,
 | 
			
		||||
				Analyser::Static::MSX::Cartridge::Konami,
 | 
			
		||||
				float(konami_total) / float(total_hits)));
 | 
			
		||||
				float(	address_counts[0x6000] +
 | 
			
		||||
						address_counts[0x8000] +
 | 
			
		||||
						address_counts[0xa000]) / total_hits));
 | 
			
		||||
		}
 | 
			
		||||
		if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
 | 
			
		||||
		if(!is_ascii) {
 | 
			
		||||
			targets.push_back(CartridgeTarget(
 | 
			
		||||
				segment,
 | 
			
		||||
				start_address,
 | 
			
		||||
				Analyser::Static::MSX::Cartridge::KonamiWithSCC,
 | 
			
		||||
				float(konami_with_scc_total) / float(total_hits)));
 | 
			
		||||
				float(	address_counts[0x5000] +
 | 
			
		||||
						address_counts[0x7000] +
 | 
			
		||||
						address_counts[0x9000] +
 | 
			
		||||
						address_counts[0xb000]) / total_hits));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return targets;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
 | 
			
		||||
	// Append targets for any cartridges that look correct.
 | 
			
		||||
@@ -202,7 +272,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(
 | 
			
		||||
 | 
			
		||||
	// Check tapes for loadable files.
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		const std::vector<File> files_on_tape = GetFiles(tape);
 | 
			
		||||
		std::vector<File> files_on_tape = GetFiles(tape);
 | 
			
		||||
		if(!files_on_tape.empty()) {
 | 
			
		||||
			switch(files_on_tape.front().type) {
 | 
			
		||||
				case File::Type::ASCII:				target->loading_command = "RUN\"CAS:\r";		break;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_MSX_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace MSX {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Tape/Parsers/MSX.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/MSX.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::MSX;
 | 
			
		||||
 | 
			
		||||
@@ -29,12 +29,12 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
 | 
			
		||||
 | 
			
		||||
	Storage::Tape::BinaryTapePlayer tape_player(1000000);
 | 
			
		||||
	tape_player.set_motor_control(true);
 | 
			
		||||
	tape_player.set_tape(tape, TargetPlatform::MSX);
 | 
			
		||||
	tape_player.set_tape(tape);
 | 
			
		||||
 | 
			
		||||
	using Parser = Storage::Tape::MSX::Parser;
 | 
			
		||||
 | 
			
		||||
	// Get all recognisable files from the tape.
 | 
			
		||||
	while(!tape_player.is_at_end()) {
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		// Try to locate and measure a header.
 | 
			
		||||
		std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
 | 
			
		||||
		if(!file_speed) continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,17 @@
 | 
			
		||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_MSX_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_MSX_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace MSX {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
	std::string name;
 | 
			
		||||
@@ -32,6 +35,10 @@ struct File {
 | 
			
		||||
	File();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_MSX_Tape_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,26 +6,22 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_MSX_Target_h
 | 
			
		||||
#define Analyser_Static_MSX_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::MSX {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace MSX {
 | 
			
		||||
 | 
			
		||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	bool has_disk_drive = false;
 | 
			
		||||
	bool has_msx_music = true;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(Model,
 | 
			
		||||
		MSX1,
 | 
			
		||||
		MSX2
 | 
			
		||||
	);
 | 
			
		||||
	Model model = Model::MSX2;
 | 
			
		||||
 | 
			
		||||
	ReflectableEnum(Region,
 | 
			
		||||
		Japan,
 | 
			
		||||
		USA,
 | 
			
		||||
@@ -33,18 +29,17 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
 | 
			
		||||
	);
 | 
			
		||||
	Region region = Region::USA;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::MSX) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(has_disk_drive);
 | 
			
		||||
		DeclareField(has_msx_music);
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::MSX) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(has_disk_drive);
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_MSX_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,9 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool is_confident
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	// This analyser can comprehend disks and mass-storage devices only.
 | 
			
		||||
	if(media.disks.empty() && media.mass_storage_devices.empty() && !is_confident) return {};
 | 
			
		||||
	if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
 | 
			
		||||
 | 
			
		||||
	// As there is at least one usable media image, wave it through.
 | 
			
		||||
	Analyser::Static::TargetList targets;
 | 
			
		||||
@@ -29,7 +24,7 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(
 | 
			
		||||
	if(media.mass_storage_devices.empty()) {
 | 
			
		||||
		bool has_800kb_disks = false;
 | 
			
		||||
		for(const auto &disk: media.disks) {
 | 
			
		||||
			has_800kb_disks |= disk->head_count() > 1;
 | 
			
		||||
			has_800kb_disks |= disk->get_head_count() > 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!has_800kb_disks) {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,22 @@
 | 
			
		||||
//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Macintosh {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Macintosh {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,26 +6,32 @@
 | 
			
		||||
//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Macintosh_Target_h
 | 
			
		||||
#define Analyser_Static_Macintosh_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Macintosh {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Macintosh {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
 | 
			
		||||
	Model model = Model::MacPlus;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Macintosh) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::Macintosh) {
 | 
			
		||||
		// Boilerplate for declaring fields and potential values.
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(model);
 | 
			
		||||
			AnnounceEnum(Model);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Macintosh_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/Disassembler/6502.hpp"
 | 
			
		||||
#include "Analyser/Static/Disassembler/AddressMapper.hpp"
 | 
			
		||||
#include "../Disassembler/6502.hpp"
 | 
			
		||||
#include "../Disassembler/AddressMapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
@@ -22,22 +22,12 @@ using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
int score(
 | 
			
		||||
	const Analyser::Static::MOS6502::Disassembly &disassembly,
 | 
			
		||||
	const std::set<uint16_t> &rom_functions,
 | 
			
		||||
	const std::set<uint16_t> &variable_locations
 | 
			
		||||
) {
 | 
			
		||||
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
 | 
			
		||||
	int score = 0;
 | 
			
		||||
 | 
			
		||||
	for(const auto address : disassembly.outward_calls) {
 | 
			
		||||
		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
	for(const auto address : disassembly.external_stores) {
 | 
			
		||||
		score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
	for(const auto address : disassembly.external_loads) {
 | 
			
		||||
		score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	}
 | 
			
		||||
	for(const auto address : disassembly.outward_calls)		score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
 | 
			
		||||
	for(const auto address : disassembly.external_stores)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
	for(const auto address : disassembly.external_loads)	score += (variable_locations.find(address) != variable_locations.end()) ? 1 : -1;
 | 
			
		||||
 | 
			
		||||
	return score;
 | 
			
		||||
}
 | 
			
		||||
@@ -45,32 +35,19 @@ int score(
 | 
			
		||||
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	const std::set<uint16_t> rom_functions = {
 | 
			
		||||
		0x0228,	0x022b,
 | 
			
		||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,
 | 
			
		||||
		0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
			
		||||
		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,
 | 
			
		||||
		0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c,
 | 
			
		||||
		0xca1f,	0xca3e,	0xca61,	0xca78,	0xca98,	0xcad2,	0xcb61,	0xcb9f,
 | 
			
		||||
		0xcc59,	0xcbed,	0xcc0a,	0xcc8c,	0xcc8f,	0xccba,	0xccc9,	0xccfd,
 | 
			
		||||
		0xce0c,	0xce77,	0xce8b,	0xcfac,	0xcf74,	0xd03c,	0xd059,	0xcff0,
 | 
			
		||||
		0xd087,	0xd0f2,	0xd0fc,	0xd361,	0xd3eb,	0xd47e,	0xd4a6,	0xd401,
 | 
			
		||||
		0xd593,	0xd5a3,	0xd4fa,	0xd595,	0xd730,	0xd767,	0xd816,	0xd82a,
 | 
			
		||||
		0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd80a,	0xd867,	0xd938,	0xd894,
 | 
			
		||||
		0xd89d,	0xd8ac,	0xd983,	0xd993,	0xd9b5,	0xd93d,	0xd965,	0xda3f,
 | 
			
		||||
		0xd9c6,	0xda16,	0xdaab,	0xdada,	0xda6b,	0xdb92,	0xdbb9,	0xdc79,
 | 
			
		||||
		0xdd4d,	0xdda3,	0xddbf,	0xd0d0,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,
 | 
			
		||||
		0xdf04,	0xdf12,	0xdf31,	0xdf4c,	0xdf8c,	0xdfa5,	0xdfcf,	0xe076,
 | 
			
		||||
		0xe0c1,	0xe22a,	0xe27c,	0xe2a6,	0xe313,	0xe34b,	0xe387,	0xe38e,
 | 
			
		||||
		0xe3d7,	0xe407,	0xe43b,	0xe46f,	0xe4a8,	0xe4f2,	0xe554,	0xe57d,
 | 
			
		||||
		0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe563,
 | 
			
		||||
		0xe5c6,	0xe630,	0xe696,	0xe6ba,	0xe6ca,	0xe725,	0xe7aa,	0xe903,
 | 
			
		||||
		0xe7db,	0xe80d,	0xe987,	0xe9d1,	0xe87d,	0xe905,	0xe965,	0xe974,
 | 
			
		||||
		0xe994,	0xe9a9,	0xe9bb,	0xec45,	0xeccc,	0xedc4,	0xecc7,	0xed01,
 | 
			
		||||
		0xed09,	0xed70,	0xed81,	0xed8f,	0xe0ad,	0xeee8,	0xeef8,	0xebdf,
 | 
			
		||||
		0xebe2,	0xebe5,	0xebeb,	0xebee,	0xebf4,	0xebf7,	0xebfa,	0xebe8,
 | 
			
		||||
		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,
 | 
			
		||||
		0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920,
 | 
			
		||||
		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,
 | 
			
		||||
		0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe
 | 
			
		||||
		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738,
 | 
			
		||||
		0xc773,	0xc824,	0xc832,	0xc841,	0xc8c1,	0xc8fe,	0xc91f,	0xc93f,	0xc941,	0xc91e,	0xc98b,	0xc996,	0xc9b3,	0xc9e0,	0xca0a,	0xca1c,
 | 
			
		||||
		0xca1f,	0xca3e,	0xca61,	0xca78,	0xca98,	0xcad2,	0xcb61,	0xcb9f,	0xcc59,	0xcbed,	0xcc0a,	0xcc8c,	0xcc8f,	0xccba,	0xccc9,	0xccfd,
 | 
			
		||||
		0xce0c,	0xce77,	0xce8b,	0xcfac,	0xcf74,	0xd03c,	0xd059,	0xcff0,	0xd087,	0xd0f2,	0xd0fc,	0xd361,	0xd3eb,	0xd47e,	0xd4a6,	0xd401,
 | 
			
		||||
		0xd593,	0xd5a3,	0xd4fa,	0xd595,	0xd730,	0xd767,	0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd80a,	0xd867,	0xd938,	0xd894,
 | 
			
		||||
		0xd89d,	0xd8ac,	0xd983,	0xd993,	0xd9b5,	0xd93d,	0xd965,	0xda3f,	0xd9c6,	0xda16,	0xdaab,	0xdada,	0xda6b,	0xdb92,	0xdbb9,	0xdc79,
 | 
			
		||||
		0xdd4d,	0xdda3,	0xddbf,	0xd0d0,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,	0xdf04,	0xdf12,	0xdf31,	0xdf4c,	0xdf8c,	0xdfa5,	0xdfcf,	0xe076,
 | 
			
		||||
		0xe0c1,	0xe22a,	0xe27c,	0xe2a6,	0xe313,	0xe34b,	0xe387,	0xe38e,	0xe3d7,	0xe407,	0xe43b,	0xe46f,	0xe4a8,	0xe4f2,	0xe554,	0xe57d,
 | 
			
		||||
		0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe563,	0xe5c6,	0xe630,	0xe696,	0xe6ba,	0xe6ca,	0xe725,	0xe7aa,	0xe903,
 | 
			
		||||
		0xe7db,	0xe80d,	0xe987,	0xe9d1,	0xe87d,	0xe905,	0xe965,	0xe974,	0xe994,	0xe9a9,	0xe9bb,	0xec45,	0xeccc,	0xedc4,	0xecc7,	0xed01,
 | 
			
		||||
		0xed09,	0xed70,	0xed81,	0xed8f,	0xe0ad,	0xeee8,	0xeef8,	0xebdf,	0xebe2,	0xebe5,	0xebeb,	0xebee,	0xebf4,	0xebf7,	0xebfa,	0xebe8,
 | 
			
		||||
		0xf43c,	0xf4ef,	0xf523,	0xf561,	0xf535,	0xf57b,	0xf5d3,	0xf71a,	0xf73f,	0xf7e4,	0xf7e0,	0xf82f,	0xf88f,	0xf8af,	0xf8b5,	0xf920,
 | 
			
		||||
		0xf967,	0xf960,	0xf9c9,	0xfa14,	0xfa85,	0xfa9b,	0xfab1,	0xfac7,	0xfafa,	0xfb10,	0xfb26,	0xfbb6,	0xfbfe
 | 
			
		||||
	};
 | 
			
		||||
	const std::set<uint16_t> variable_locations = {
 | 
			
		||||
		0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
 | 
			
		||||
@@ -82,32 +59,19 @@ int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
 | 
			
		||||
	const std::set<uint16_t> rom_functions = {
 | 
			
		||||
		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247,
 | 
			
		||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,
 | 
			
		||||
		0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
			
		||||
		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,
 | 
			
		||||
		0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12,
 | 
			
		||||
		0xca3c,	0xca4e,	0xca51,	0xca70,	0xca99,	0xcac2,	0xcae2,	0xcb1c,
 | 
			
		||||
		0xcbab,	0xcbf0,	0xcc59,	0xccb0,	0xccce,	0xcd16,	0xcd19,	0xcd46,
 | 
			
		||||
		0xcd55,	0xcd89,	0xce98,	0xcf03,	0xcf17,	0xcfac,	0xd000,	0xd03c,
 | 
			
		||||
		0xd059,	0xd07c,	0xd113,	0xd17e,	0xd188,	0xd361,	0xd3eb,	0xd47e,
 | 
			
		||||
		0xd4a6,	0xd4ba,	0xd593,	0xd5a3,	0xd5b5,	0xd650,	0xd730,	0xd767,
 | 
			
		||||
		0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd8c5,	0xd922,
 | 
			
		||||
		0xd938,	0xd94f,	0xd958,	0xd967,	0xd983,	0xd993,	0xd9b5,	0xd9de,
 | 
			
		||||
		0xda0c,	0xda3f,	0xda51,	0xdaa1,	0xdaab,	0xdada,	0xdaf6,	0xdb92,
 | 
			
		||||
		0xdbb9,	0xdcaf,	0xdd51,	0xdda7,	0xddc3,	0xddd4,	0xde77,	0xdef4,
 | 
			
		||||
		0xdf0b,	0xdf0f,	0xdf13,	0xdf21,	0xdf49,	0xdf4c,	0xdf8c,	0xdfbd,
 | 
			
		||||
		0xdfe7,	0xe076,	0xe0c5,	0xe22e,	0xe27c,	0xe2aa,	0xe313,	0xe34f,
 | 
			
		||||
		0xe38b,	0xe392,	0xe3db,	0xe407,	0xe43f,	0xe46f,	0xe4ac,	0xe4e0,
 | 
			
		||||
		0xe4f2,	0xe56c,	0xe57d,	0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,
 | 
			
		||||
		0xe5b6,	0xe5ea,	0xe5f5,	0xe607,	0xe65e,	0xe6c9,	0xe735,	0xe75a,
 | 
			
		||||
		0xe76a,	0xe7b2,	0xe85b,	0xe903,	0xe909,	0xe946,	0xe987,	0xe9d1,
 | 
			
		||||
		0xeaf0,	0xeb78,	0xebce,	0xebe7,	0xec0c,	0xec21,	0xec33,	0xec45,
 | 
			
		||||
		0xeccc,	0xedc4,	0xede0,	0xee1a,	0xee22,	0xee8c,	0xee9d,	0xeeab,
 | 
			
		||||
		0xeec9,	0xeee8,	0xeef8,	0xf0c8,	0xf0fd,	0xf110,	0xf11d,	0xf12d,
 | 
			
		||||
		0xf204,	0xf210,	0xf268,	0xf37f,	0xf495,	0xf4ef,	0xf523,	0xf561,
 | 
			
		||||
		0xf590,	0xf5c1,	0xf602,	0xf71a,	0xf77c,	0xf7e4,	0xf816,	0xf865,
 | 
			
		||||
		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,
 | 
			
		||||
		0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0,
 | 
			
		||||
		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d,
 | 
			
		||||
		0xc748,	0xc7fd,	0xc809,	0xc816,	0xc82f,	0xc855,	0xc8c1,	0xc915,	0xc952,	0xc971,	0xc973,	0xc9a0,	0xc9bd,	0xc9c8,	0xc9e5,	0xca12,
 | 
			
		||||
		0xca3c,	0xca4e,	0xca51,	0xca70,	0xca99,	0xcac2,	0xcae2,	0xcb1c,	0xcbab,	0xcbf0,	0xcc59,	0xccb0,	0xccce,	0xcd16,	0xcd19,	0xcd46,
 | 
			
		||||
		0xcd55,	0xcd89,	0xce98,	0xcf03,	0xcf17,	0xcfac,	0xd000,	0xd03c,	0xd059,	0xd07c,	0xd113,	0xd17e,	0xd188,	0xd361,	0xd3eb,	0xd47e,
 | 
			
		||||
		0xd4a6,	0xd4ba,	0xd593,	0xd5a3,	0xd5b5,	0xd650,	0xd730,	0xd767,	0xd816,	0xd82a,	0xd856,	0xd861,	0xd8a6,	0xd8b5,	0xd8c5,	0xd922,
 | 
			
		||||
		0xd938,	0xd94f,	0xd958,	0xd967,	0xd983,	0xd993,	0xd9b5,	0xd9de,	0xda0c,	0xda3f,	0xda51,	0xdaa1,	0xdaab,	0xdada,	0xdaf6,	0xdb92,
 | 
			
		||||
		0xdbb9,	0xdcaf,	0xdd51,	0xdda7,	0xddc3,	0xddd4,	0xde77,	0xdef4,	0xdf0b,	0xdf0f,	0xdf13,	0xdf21,	0xdf49,	0xdf4c,	0xdf8c,	0xdfbd,
 | 
			
		||||
		0xdfe7,	0xe076,	0xe0c5,	0xe22e,	0xe27c,	0xe2aa,	0xe313,	0xe34f,	0xe38b,	0xe392,	0xe3db,	0xe407,	0xe43f,	0xe46f,	0xe4ac,	0xe4e0,
 | 
			
		||||
		0xe4f2,	0xe56c,	0xe57d,	0xe585,	0xe58c,	0xe594,	0xe5a4,	0xe5ab,	0xe5b6,	0xe5ea,	0xe5f5,	0xe607,	0xe65e,	0xe6c9,	0xe735,	0xe75a,
 | 
			
		||||
		0xe76a,	0xe7b2,	0xe85b,	0xe903,	0xe909,	0xe946,	0xe987,	0xe9d1,	0xeaf0,	0xeb78,	0xebce,	0xebe7,	0xec0c,	0xec21,	0xec33,	0xec45,
 | 
			
		||||
		0xeccc,	0xedc4,	0xede0,	0xee1a,	0xee22,	0xee8c,	0xee9d,	0xeeab,	0xeec9,	0xeee8,	0xeef8,	0xf0c8,	0xf0fd,	0xf110,	0xf11d,	0xf12d,
 | 
			
		||||
		0xf204,	0xf210,	0xf268,	0xf37f,	0xf495,	0xf4ef,	0xf523,	0xf561,	0xf590,	0xf5c1,	0xf602,	0xf71a,	0xf77c,	0xf7e4,	0xf816,	0xf865,
 | 
			
		||||
		0xf88f,	0xf8af,	0xf8b5,	0xf920,	0xf967,	0xf9aa,	0xf9c9,	0xfa14,	0xfa9f,	0xfab5,	0xfacb,	0xfae1,	0xfb14,	0xfb2a,	0xfb40,	0xfbd0,
 | 
			
		||||
		0xfc18
 | 
			
		||||
	};
 | 
			
		||||
	const std::set<uint16_t> variable_locations = {
 | 
			
		||||
@@ -121,7 +85,7 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
 | 
			
		||||
	/*
 | 
			
		||||
		The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
 | 
			
		||||
	*/
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 2);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
 | 
			
		||||
	if(!sector) return false;
 | 
			
		||||
	if(sector->samples.empty()) return false;
 | 
			
		||||
 | 
			
		||||
@@ -138,13 +102,13 @@ bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
 | 
			
		||||
	return !std::memcmp(signature, first_sample.data(), sizeof(signature));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range_start, const uint16_t range_end) {
 | 
			
		||||
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
 | 
			
		||||
	/*
 | 
			
		||||
		Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
 | 
			
		||||
		use disassembly to test for likely matches.
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *sector = parser.sector(0, 0, 1);
 | 
			
		||||
	Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
 | 
			
		||||
	if(!sector) return false;
 | 
			
		||||
	if(sector->samples.empty()) return false;
 | 
			
		||||
 | 
			
		||||
@@ -156,8 +120,8 @@ bool is_400_loader(Storage::Encodings::MFM::Parser &parser, const uint16_t range
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Grab a disassembly.
 | 
			
		||||
	const auto disassembly = Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
		first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
 | 
			
		||||
	const auto disassembly =
 | 
			
		||||
		Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
 | 
			
		||||
 | 
			
		||||
	// Check for references to the Jasmin registers.
 | 
			
		||||
	int register_hits = 0;
 | 
			
		||||
@@ -181,12 +145,7 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
 | 
			
		||||
@@ -194,18 +153,14 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
 | 
			
		||||
	int basic11_votes = 0;
 | 
			
		||||
 | 
			
		||||
	for(auto &tape : media.tapes) {
 | 
			
		||||
		auto serialiser = tape->serialiser();
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(*serialiser);
 | 
			
		||||
		std::vector<File> tape_files = GetFiles(tape);
 | 
			
		||||
		tape->reset();
 | 
			
		||||
		if(!tape_files.empty()) {
 | 
			
		||||
			for(const auto &file : tape_files) {
 | 
			
		||||
				if(file.data_type == File::MachineCode) {
 | 
			
		||||
					std::vector<uint16_t> entry_points = {file.starting_address};
 | 
			
		||||
					const Analyser::Static::MOS6502::Disassembly disassembly =
 | 
			
		||||
						Analyser::Static::MOS6502::Disassemble(
 | 
			
		||||
							file.data,
 | 
			
		||||
							Analyser::Static::Disassembler::OffsetMapper(file.starting_address),
 | 
			
		||||
							entry_points
 | 
			
		||||
						);
 | 
			
		||||
						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
 | 
			
		||||
 | 
			
		||||
					if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
 | 
			
		||||
				}
 | 
			
		||||
@@ -220,7 +175,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(
 | 
			
		||||
		// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
 | 
			
		||||
		// Jasmin and BD-DOS formats here.
 | 
			
		||||
		for(auto &disk: media.disks) {
 | 
			
		||||
			Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
 | 
			
		||||
			Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
 | 
			
		||||
			if(is_microdisc(parser)) {
 | 
			
		||||
				target->disk_interface = Target::DiskInterface::Microdisc;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_Oric_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Oric {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Oric {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,61 +7,61 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Tape.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Oric.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static::Oric;
 | 
			
		||||
 | 
			
		||||
std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	std::vector<File> files;
 | 
			
		||||
	Storage::Tape::Oric::Parser parser;
 | 
			
		||||
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		// sync to next lead-in, check that it's one of three 0x16s
 | 
			
		||||
		bool is_fast = parser.sync_and_get_encoding_speed(serialiser);
 | 
			
		||||
		bool is_fast = parser.sync_and_get_encoding_speed(tape);
 | 
			
		||||
		int next_bytes[2];
 | 
			
		||||
		next_bytes[0] = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		next_bytes[1] = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		next_bytes[0] = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		next_bytes[1] = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
 | 
			
		||||
		if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue;
 | 
			
		||||
 | 
			
		||||
		// get the first byte that isn't a 0x16, check it was a 0x24
 | 
			
		||||
		int byte = 0x16;
 | 
			
		||||
		while(!serialiser.is_at_end() && byte == 0x16) {
 | 
			
		||||
			byte = parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		while(!tape->is_at_end() && byte == 0x16) {
 | 
			
		||||
			byte = parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		}
 | 
			
		||||
		if(byte != 0x24) continue;
 | 
			
		||||
 | 
			
		||||
		// skip two empty bytes
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
 | 
			
		||||
		// get data and launch types
 | 
			
		||||
		File new_file;
 | 
			
		||||
		switch(parser.get_next_byte(serialiser, is_fast)) {
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
			case 0x00:	new_file.data_type = File::ProgramType::BASIC;			break;
 | 
			
		||||
			case 0x80:	new_file.data_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.data_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
		switch(parser.get_next_byte(serialiser, is_fast)) {
 | 
			
		||||
		switch(parser.get_next_byte(tape, is_fast)) {
 | 
			
		||||
			case 0x80:	new_file.launch_type = File::ProgramType::BASIC;		break;
 | 
			
		||||
			case 0xc7:	new_file.launch_type = File::ProgramType::MachineCode;	break;
 | 
			
		||||
			default:	new_file.launch_type = File::ProgramType::None;			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// read end and start addresses
 | 
			
		||||
		new_file.ending_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
		new_file.starting_address = uint16_t(parser.get_next_byte(serialiser, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= uint16_t(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
		new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
		new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
 | 
			
		||||
		new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
 | 
			
		||||
		// skip an empty byte
 | 
			
		||||
		parser.get_next_byte(serialiser, is_fast);
 | 
			
		||||
		parser.get_next_byte(tape, is_fast);
 | 
			
		||||
 | 
			
		||||
		// read file name, up to 16 characters and null terminated
 | 
			
		||||
		char file_name[17];
 | 
			
		||||
		int name_pos = 0;
 | 
			
		||||
		while(name_pos < 16) {
 | 
			
		||||
			file_name[name_pos] = char(parser.get_next_byte(serialiser, is_fast));
 | 
			
		||||
			file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
 | 
			
		||||
			if(!file_name[name_pos]) break;
 | 
			
		||||
			name_pos++;
 | 
			
		||||
		}
 | 
			
		||||
@@ -72,11 +72,11 @@ std::vector<File> Analyser::Static::Oric::GetFiles(Storage::Tape::TapeSerialiser
 | 
			
		||||
		std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
 | 
			
		||||
		new_file.data.reserve(body_length);
 | 
			
		||||
		for(std::size_t c = 0; c < body_length; c++) {
 | 
			
		||||
			new_file.data.push_back(uint8_t(parser.get_next_byte(serialiser, is_fast)));
 | 
			
		||||
			new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// only one validation check: was there enough tape?
 | 
			
		||||
		if(!serialiser.is_at_end()) {
 | 
			
		||||
		if(!tape->is_at_end()) {
 | 
			
		||||
			files.push_back(new_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,17 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Oric_Tape_hpp
 | 
			
		||||
#define StaticAnalyser_Oric_Tape_hpp
 | 
			
		||||
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Tape.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Oric {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Oric {
 | 
			
		||||
 | 
			
		||||
struct File {
 | 
			
		||||
	std::string name;
 | 
			
		||||
@@ -28,6 +31,10 @@ struct File {
 | 
			
		||||
	std::vector<uint8_t> data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::vector<File> GetFiles(Storage::Tape::TapeSerialiser &);
 | 
			
		||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Tape_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,17 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Oric_Target_h
 | 
			
		||||
#define Analyser_Static_Oric_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Oric {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Oric {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(ROM,
 | 
			
		||||
@@ -41,18 +44,20 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
	bool should_start_jasmin = false;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::Oric) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(rom);
 | 
			
		||||
		DeclareField(disk_interface);
 | 
			
		||||
		DeclareField(processor);
 | 
			
		||||
		AnnounceEnum(ROM);
 | 
			
		||||
		AnnounceEnum(DiskInterface);
 | 
			
		||||
		AnnounceEnum(Processor);
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::Oric) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(rom);
 | 
			
		||||
			DeclareField(disk_interface);
 | 
			
		||||
			DeclareField(processor);
 | 
			
		||||
			AnnounceEnum(ROM);
 | 
			
		||||
			AnnounceEnum(DiskInterface);
 | 
			
		||||
			AnnounceEnum(Processor);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Oric_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.cpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 03/10/2019.
 | 
			
		||||
//  Copyright © 2019 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::PCCompatible::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
	// This analyser can comprehend disks only.
 | 
			
		||||
	if(media.disks.empty()) return {};
 | 
			
		||||
 | 
			
		||||
	// No analysis is applied yet.
 | 
			
		||||
	Analyser::Static::TargetList targets;
 | 
			
		||||
 | 
			
		||||
	using Target = Analyser::Static::PCCompatible::Target;
 | 
			
		||||
	auto *const target = new Target();
 | 
			
		||||
	target->media = media;
 | 
			
		||||
	targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
 | 
			
		||||
 | 
			
		||||
	return targets;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  StaticAnalyser.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 29/11/2019.
 | 
			
		||||
//  Copyright © 2023 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::PCCompatible {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  Target.hpp
 | 
			
		||||
//  Clock Signal
 | 
			
		||||
//
 | 
			
		||||
//  Created by Thomas Harte on 29/11/2023.
 | 
			
		||||
//  Copyright © 2023 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::PCCompatible {
 | 
			
		||||
 | 
			
		||||
ReflectableEnum(Model,
 | 
			
		||||
	XT,
 | 
			
		||||
	TurboXT,
 | 
			
		||||
	AT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
constexpr bool is_xt(const Model model) {
 | 
			
		||||
	return model <= Model::TurboXT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr bool is_at(const Model model) {
 | 
			
		||||
	return model >= Model::AT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr bool has_ide(const Model model) {
 | 
			
		||||
	return model >= Model::AT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(VideoAdaptor,
 | 
			
		||||
		MDA,
 | 
			
		||||
		CGA,
 | 
			
		||||
	);
 | 
			
		||||
	VideoAdaptor adaptor = VideoAdaptor::CGA;
 | 
			
		||||
	Model model = Model::TurboXT;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::PCCompatible) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		AnnounceEnum(VideoAdaptor);
 | 
			
		||||
		AnnounceEnum(Model);
 | 
			
		||||
		DeclareField(adaptor);
 | 
			
		||||
		DeclareField(model);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -13,12 +13,7 @@
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &file_name,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
 | 
			
		||||
	if(media.cartridges.empty())
 | 
			
		||||
		return {};
 | 
			
		||||
 | 
			
		||||
@@ -59,8 +54,7 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
 | 
			
		||||
					if(lowercase_name.find("(jp)") == std::string::npos) {
 | 
			
		||||
						target->region =
 | 
			
		||||
							(lowercase_name.find("(us)") == std::string::npos &&
 | 
			
		||||
							lowercase_name.find("(ntsc)") == std::string::npos) ?
 | 
			
		||||
								Target::Region::Europe : Target::Region::USA;
 | 
			
		||||
							lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
 | 
			
		||||
					}
 | 
			
		||||
				} break;
 | 
			
		||||
			}
 | 
			
		||||
@@ -69,9 +63,9 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(
 | 
			
		||||
			// If one is found, set the paging scheme appropriately.
 | 
			
		||||
			const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
 | 
			
		||||
			if(
 | 
			
		||||
				data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 &&		// i.e. game is dated between 1987 and 1996
 | 
			
		||||
				data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 &&									// i.e. game is dated between 1987 and 1996
 | 
			
		||||
				(inverse_checksum&0xff) == data[0x7fe8] &&
 | 
			
		||||
				(inverse_checksum >> 8) == data[0x7fe9] &&			// i.e. the standard checksum appears to be present.
 | 
			
		||||
				(inverse_checksum >> 8) == data[0x7fe9] &&										// i.e. the standard checksum appears to be present
 | 
			
		||||
				!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
 | 
			
		||||
			) {
 | 
			
		||||
				target->paging_scheme = Target::PagingScheme::Codemasters;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_Sega_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Sega {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Sega {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,16 @@
 | 
			
		||||
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_Sega_Target_h
 | 
			
		||||
#define Analyser_Static_Sega_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::Sega {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace Sega {
 | 
			
		||||
 | 
			
		||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	enum class Model {
 | 
			
		||||
@@ -37,18 +40,18 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
 | 
			
		||||
	Region region = Region::Japan;
 | 
			
		||||
	PagingScheme paging_scheme = PagingScheme::Sega;
 | 
			
		||||
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::MasterSystem) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(region);
 | 
			
		||||
		AnnounceEnum(Region);
 | 
			
		||||
	Target() : Analyser::Static::Target(Machine::MasterSystem) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(region);
 | 
			
		||||
			AnnounceEnum(Region);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr bool is_master_system(const Analyser::Static::Sega::Target::Model model) {
 | 
			
		||||
	return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
 | 
			
		||||
}
 | 
			
		||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_Sega_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -9,90 +9,79 @@
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <bit>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
 | 
			
		||||
// Analysers
 | 
			
		||||
#include "Analyser/Static/Acorn/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Amiga/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AmstradCPC/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AppleII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AppleIIgs/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Atari2600/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/AtariST/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Coleco/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Commodore/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/DiskII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/FAT12/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Macintosh/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/MSX/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Oric/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/PCCompatible/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/Sega/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/ZX8081/StaticAnalyser.hpp"
 | 
			
		||||
#include "Analyser/Static/ZXSpectrum/StaticAnalyser.hpp"
 | 
			
		||||
#include "Acorn/StaticAnalyser.hpp"
 | 
			
		||||
#include "Amiga/StaticAnalyser.hpp"
 | 
			
		||||
#include "AmstradCPC/StaticAnalyser.hpp"
 | 
			
		||||
#include "AppleII/StaticAnalyser.hpp"
 | 
			
		||||
#include "AppleIIgs/StaticAnalyser.hpp"
 | 
			
		||||
#include "Atari2600/StaticAnalyser.hpp"
 | 
			
		||||
#include "AtariST/StaticAnalyser.hpp"
 | 
			
		||||
#include "Coleco/StaticAnalyser.hpp"
 | 
			
		||||
#include "Commodore/StaticAnalyser.hpp"
 | 
			
		||||
#include "DiskII/StaticAnalyser.hpp"
 | 
			
		||||
#include "Enterprise/StaticAnalyser.hpp"
 | 
			
		||||
#include "Macintosh/StaticAnalyser.hpp"
 | 
			
		||||
#include "MSX/StaticAnalyser.hpp"
 | 
			
		||||
#include "Oric/StaticAnalyser.hpp"
 | 
			
		||||
#include "Sega/StaticAnalyser.hpp"
 | 
			
		||||
#include "ZX8081/StaticAnalyser.hpp"
 | 
			
		||||
#include "ZXSpectrum/StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
// Cartridges
 | 
			
		||||
#include "Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
			
		||||
#include "Storage/Cartridge/Formats/PRG.hpp"
 | 
			
		||||
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
 | 
			
		||||
#include "../../Storage/Cartridge/Formats/PRG.hpp"
 | 
			
		||||
 | 
			
		||||
// Disks
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/2MG.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/FAT12.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/PCBooter.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
 | 
			
		||||
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
 | 
			
		||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
 | 
			
		||||
 | 
			
		||||
// Mass Storage Devices (i.e. usually, hard disks)
 | 
			
		||||
#include "Storage/MassStorage/Formats/DAT.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/DSK.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/HDV.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/HFV.hpp"
 | 
			
		||||
#include "Storage/MassStorage/Formats/VHD.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
 | 
			
		||||
 | 
			
		||||
// State Snapshots
 | 
			
		||||
#include "Storage/State/SNA.hpp"
 | 
			
		||||
#include "Storage/State/SZX.hpp"
 | 
			
		||||
#include "Storage/State/Z80.hpp"
 | 
			
		||||
#include "../../Storage/State/SNA.hpp"
 | 
			
		||||
#include "../../Storage/State/SZX.hpp"
 | 
			
		||||
#include "../../Storage/State/Z80.hpp"
 | 
			
		||||
 | 
			
		||||
// Tapes
 | 
			
		||||
#include "Storage/Tape/Formats/CAS.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/CommodoreTAP.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/CSW.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/OricTAP.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/TapePRG.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/TapeUEF.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/TZX.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/ZX80O81P.hpp"
 | 
			
		||||
#include "Storage/Tape/Formats/ZXSpectrumTAP.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/CAS.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/CSW.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/OricTAP.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/TapePRG.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/TZX.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
 | 
			
		||||
 | 
			
		||||
// Target Platform Types
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
 | 
			
		||||
template<class> inline constexpr bool always_false_v = false;
 | 
			
		||||
#include "../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace Analyser::Static;
 | 
			
		||||
using namespace Storage;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
@@ -106,208 +95,126 @@ std::string get_extension(const std::string &name) {
 | 
			
		||||
	return extension;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MediaAccumulator {
 | 
			
		||||
public:
 | 
			
		||||
	MediaAccumulator(const std::string &file_name, TargetPlatform::IntType &potential_platforms) :
 | 
			
		||||
		file_name_(file_name), potential_platforms_(potential_platforms), extension_(get_extension(file_name)) {}
 | 
			
		||||
 | 
			
		||||
	/// Adds @c instance to the media collection and adds @c platforms to the set of potentials.
 | 
			
		||||
	/// If @c instance is an @c TargetPlatform::TypeDistinguisher then it is given an opportunity to restrict the set of potentials.
 | 
			
		||||
	template <typename InstanceT>
 | 
			
		||||
	void insert(const TargetPlatform::IntType platforms, std::shared_ptr<InstanceT> instance) {
 | 
			
		||||
		if constexpr (std::is_base_of_v<Storage::Disk::Disk, InstanceT>) {
 | 
			
		||||
			media.disks.push_back(instance);
 | 
			
		||||
		} else if constexpr (std::is_base_of_v<Storage::Tape::Tape, InstanceT>) {
 | 
			
		||||
			media.tapes.push_back(instance);
 | 
			
		||||
		} else if constexpr (std::is_base_of_v<Storage::Cartridge::Cartridge, InstanceT>) {
 | 
			
		||||
			media.cartridges.push_back(instance);
 | 
			
		||||
		} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
 | 
			
		||||
			media.mass_storage_devices.push_back(instance);
 | 
			
		||||
		} else {
 | 
			
		||||
			static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		potential_platforms_ |= platforms;
 | 
			
		||||
 | 
			
		||||
		// Check whether the instance itself has any input on target platforms.
 | 
			
		||||
		TargetPlatform::Distinguisher *const distinguisher =
 | 
			
		||||
			dynamic_cast<TargetPlatform::Distinguisher *>(instance.get());
 | 
			
		||||
		if(distinguisher) {
 | 
			
		||||
			was_distinguished = true;
 | 
			
		||||
			potential_platforms_ &= distinguisher->target_platforms();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Concstructs a new instance of @c InstanceT supplying @c args and adds it to the back of @c list using @c insert_instance.
 | 
			
		||||
	template <typename InstanceT, typename... Args>
 | 
			
		||||
	void insert(const TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
		insert(platforms, std::make_shared<InstanceT>(std::forward<Args>(args)...));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Calls @c insert with the specified parameters, ignoring any exceptions thrown.
 | 
			
		||||
	template <typename InstanceT, typename... Args>
 | 
			
		||||
	void try_insert(const TargetPlatform::IntType platforms, Args &&... args) {
 | 
			
		||||
		try {
 | 
			
		||||
			insert<InstanceT>(platforms, std::forward<Args>(args)...);
 | 
			
		||||
		} catch(...) {}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Performs a @c try_insert for an object of @c InstanceT if @c extension matches that of the file name,
 | 
			
		||||
	/// providing the file name as the only construction argument.
 | 
			
		||||
	template <typename InstanceT>
 | 
			
		||||
	void try_standard(const TargetPlatform::IntType platforms, const char *extension) {
 | 
			
		||||
		if(name_matches(extension))	{
 | 
			
		||||
			try_insert<InstanceT>(platforms, file_name_);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool name_matches(const char *const extension) {
 | 
			
		||||
		return extension_ == extension;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Media media;
 | 
			
		||||
	bool was_distinguished = false;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::string &file_name_;
 | 
			
		||||
	TargetPlatform::IntType &potential_platforms_;
 | 
			
		||||
	const std::string extension_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
 | 
			
		||||
	MediaAccumulator accumulator(file_name, potential_platforms);
 | 
			
		||||
	Media result;
 | 
			
		||||
	const std::string extension = get_extension(file_name);
 | 
			
		||||
 | 
			
		||||
#define InsertInstance(list, instance, platforms) \
 | 
			
		||||
	list.emplace_back(instance);\
 | 
			
		||||
	potential_platforms |= platforms;\
 | 
			
		||||
	TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
 | 
			
		||||
	if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
 | 
			
		||||
 | 
			
		||||
#define Insert(list, class, platforms, ...) \
 | 
			
		||||
	InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
 | 
			
		||||
 | 
			
		||||
#define TryInsert(list, class, platforms, ...) \
 | 
			
		||||
	try {\
 | 
			
		||||
		Insert(list, class, platforms, __VA_ARGS__) \
 | 
			
		||||
	} catch(...) {}
 | 
			
		||||
 | 
			
		||||
#define Format(ext, list, class, platforms) \
 | 
			
		||||
	if(extension == ext)	{		\
 | 
			
		||||
		TryInsert(list, class, platforms, file_name)	\
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2MG
 | 
			
		||||
	if(accumulator.name_matches("2mg")) {
 | 
			
		||||
	if(extension == "2mg") {
 | 
			
		||||
		// 2MG uses a factory method; defer to it.
 | 
			
		||||
		try {
 | 
			
		||||
			const auto media = Disk::Disk2MG::open(file_name);
 | 
			
		||||
			std::visit([&](auto &&arg) {
 | 
			
		||||
				using Type = typename std::decay<decltype(arg)>::type;
 | 
			
		||||
 | 
			
		||||
				if constexpr (std::is_same<Type, std::nullptr_t>::value) {
 | 
			
		||||
					// It's valid for no media to be returned.
 | 
			
		||||
				} else if constexpr (std::is_same<Type, Disk::DiskImageHolderBase *>::value) {
 | 
			
		||||
					accumulator.insert(TargetPlatform::DiskII, std::shared_ptr<Disk::DiskImageHolderBase>(arg));
 | 
			
		||||
				} else if constexpr (std::is_same<Type, MassStorage::MassStorageDevice *>::value) {
 | 
			
		||||
					// TODO: or is it Apple IIgs?
 | 
			
		||||
					accumulator.insert(TargetPlatform::AppleII, std::shared_ptr<MassStorage::MassStorageDevice>(arg));
 | 
			
		||||
				} else {
 | 
			
		||||
					static_assert(always_false_v<Type>, "Unexpected type encountered.");
 | 
			
		||||
				}
 | 
			
		||||
			}, media);
 | 
			
		||||
			InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
 | 
			
		||||
		} catch(...) {}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "80");
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "81");
 | 
			
		||||
	Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 80
 | 
			
		||||
	Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// 81
 | 
			
		||||
	Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)							// A26
 | 
			
		||||
	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADF (Acorn)
 | 
			
		||||
	Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga)			// ADF (Amiga)
 | 
			
		||||
	Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)			// ADL
 | 
			
		||||
	Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge)						// BIN (cartridge dump)
 | 
			
		||||
	Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)													// CAS
 | 
			
		||||
	Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)											// CDT
 | 
			
		||||
	Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco)								// COL
 | 
			
		||||
	Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)												// CSW
 | 
			
		||||
	Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)			// D64
 | 
			
		||||
	Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn)							// DAT
 | 
			
		||||
	Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)					// DMK
 | 
			
		||||
	Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DO
 | 
			
		||||
	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD
 | 
			
		||||
	Format(	"dsk",
 | 
			
		||||
			result.disks,
 | 
			
		||||
			Disk::DiskImageHolder<Storage::Disk::CPCDSK>,
 | 
			
		||||
			TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)						// DSK (Amstrad CPC, etc)
 | 
			
		||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II)
 | 
			
		||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk)
 | 
			
		||||
	Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk, single volume image)
 | 
			
		||||
	Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk, full device image)
 | 
			
		||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX)				// DSK (MSX)
 | 
			
		||||
	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric)
 | 
			
		||||
	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64
 | 
			
		||||
	Format(	"hfe",
 | 
			
		||||
			result.disks,
 | 
			
		||||
			Disk::DiskImageHolder<Storage::Disk::HFE>,
 | 
			
		||||
			TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
 | 
			
		||||
			// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
 | 
			
		||||
	Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)		// IMG (DiskCopy 4.2)
 | 
			
		||||
	Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// IMG (DiskCopy 4.2)
 | 
			
		||||
	Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise)			// IMG (Enterprise/MS-DOS style)
 | 
			
		||||
	Format(	"ipf",
 | 
			
		||||
			result.disks,
 | 
			
		||||
			Disk::DiskImageHolder<Storage::Disk::IPF>,
 | 
			
		||||
			TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum)		// IPF
 | 
			
		||||
	Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST)				// MSA
 | 
			
		||||
	Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII)				// NIB
 | 
			
		||||
	Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// O
 | 
			
		||||
	Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P
 | 
			
		||||
	Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// PO (original Apple II kind)
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Atari2600, "a26");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
 | 
			
		||||
	accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
 | 
			
		||||
	accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
 | 
			
		||||
	accumulator.try_standard<MassStorage::DAT>(TargetPlatform::Acorn, "dat");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::DMK>>(TargetPlatform::MSX, "dmk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "do");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "dsd");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::CPCDSK>>(
 | 
			
		||||
		TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum, "dsk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "dsk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "dsk");
 | 
			
		||||
	accumulator.try_standard<MassStorage::HFV>(TargetPlatform::Macintosh, "dsk");
 | 
			
		||||
	accumulator.try_standard<MassStorage::DSK>(TargetPlatform::Macintosh, "dsk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::MSX, "dsk");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::OricMFMDSK>>(TargetPlatform::Oric, "dsk");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::G64>>(TargetPlatform::Commodore8bit, "g64");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<MassStorage::HDV>(TargetPlatform::AppleII, "hdv");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::HFE>>(
 | 
			
		||||
		TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore |
 | 
			
		||||
		TargetPlatform::Oric | TargetPlatform::ZXSpectrum,
 | 
			
		||||
		"hfe");	// TODO: switch to AllDisk once the MSX stops being so greedy.
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::PCCompatible, "ima");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "image");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::IMD>>(TargetPlatform::PCCompatible, "imd");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::MacintoshIMG>>(TargetPlatform::Macintosh, "img");
 | 
			
		||||
 | 
			
		||||
	// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
 | 
			
		||||
	if(accumulator.name_matches("img")) {
 | 
			
		||||
		try {
 | 
			
		||||
			accumulator.insert<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::FAT12, file_name);
 | 
			
		||||
		} catch(...) {
 | 
			
		||||
			accumulator.try_standard<Disk::DiskImageHolder<Disk::PCBooter>>(TargetPlatform::PCCompatible, "img");
 | 
			
		||||
		}
 | 
			
		||||
	// PO (Apple IIgs kind)
 | 
			
		||||
	if(extension == "po")	{
 | 
			
		||||
		TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::IPF>>(
 | 
			
		||||
		TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
 | 
			
		||||
		"ipf");
 | 
			
		||||
	Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081)											// P81
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "o");
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::AppleDSK>>(TargetPlatform::DiskII, "po");
 | 
			
		||||
 | 
			
		||||
	if(accumulator.name_matches("po"))	{
 | 
			
		||||
		accumulator.try_insert<Disk::DiskImageHolder<Disk::MacintoshIMG>>(
 | 
			
		||||
			TargetPlatform::AppleIIgs,
 | 
			
		||||
			file_name, Disk::MacintoshIMG::FixedType::GCR);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::ZX80O81P>(TargetPlatform::ZX8081, "p81");
 | 
			
		||||
 | 
			
		||||
	static constexpr auto PRGTargets = TargetPlatform::Vic20; //Commodore8bit;	// Disabled until analysis improves.
 | 
			
		||||
	if(accumulator.name_matches("prg")) {
 | 
			
		||||
		// Try instantiating as a ROM; failing that accept as a tape.
 | 
			
		||||
	// PRG
 | 
			
		||||
	if(extension == "prg") {
 | 
			
		||||
		// try instantiating as a ROM; failing that accept as a tape
 | 
			
		||||
		try {
 | 
			
		||||
			accumulator.insert<Cartridge::PRG>(PRGTargets, file_name);
 | 
			
		||||
			Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
 | 
			
		||||
		} catch(...) {
 | 
			
		||||
			try {
 | 
			
		||||
				accumulator.insert<Tape::PRG>(PRGTargets, file_name);
 | 
			
		||||
				Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
 | 
			
		||||
			} catch(...) {}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(
 | 
			
		||||
		TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX,
 | 
			
		||||
		"rom");
 | 
			
		||||
	Format(	"rom",
 | 
			
		||||
			result.cartridges,
 | 
			
		||||
			Cartridge::BinaryDump,
 | 
			
		||||
			TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX)						// ROM
 | 
			
		||||
	Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SG
 | 
			
		||||
	Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega)								// SMS
 | 
			
		||||
	Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// SSD
 | 
			
		||||
	Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST)				// ST
 | 
			
		||||
	Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST)				// STX
 | 
			
		||||
	Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)									// TAP (Commodore)
 | 
			
		||||
	Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric)											// TAP (Oric)
 | 
			
		||||
	Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum)								// TAP (ZX Spectrum)
 | 
			
		||||
	Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX)													// TSX
 | 
			
		||||
	Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum)					// TZX
 | 
			
		||||
	Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn)												// UEF (tape)
 | 
			
		||||
	Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII)				// WOZ
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sg");
 | 
			
		||||
	accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Sega, "sms");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::SSD>>(TargetPlatform::Acorn, "ssd");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::FAT12>>(TargetPlatform::AtariST, "st");
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::STX>>(TargetPlatform::AtariST, "stx");
 | 
			
		||||
#undef Format
 | 
			
		||||
#undef Insert
 | 
			
		||||
#undef TryInsert
 | 
			
		||||
#undef InsertInstance
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::CommodoreTAP>(TargetPlatform::Commodore8bit, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::OricTAP>(TargetPlatform::Oric, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::ZXSpectrumTAP>(TargetPlatform::ZXSpectrum, "tap");
 | 
			
		||||
	accumulator.try_standard<Tape::TZX>(TargetPlatform::MSX, "tsx");
 | 
			
		||||
	accumulator.try_standard<Tape::TZX>(TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum, "tzx");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<MassStorage::VHD>(TargetPlatform::PCCompatible, "vhd");
 | 
			
		||||
 | 
			
		||||
	accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
 | 
			
		||||
 | 
			
		||||
	return accumulator.media;
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Media Analyser::Static::GetMedia(const std::string &file_name) {
 | 
			
		||||
@@ -316,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:
 | 
			
		||||
	//
 | 
			
		||||
@@ -346,45 +251,37 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
 | 
			
		||||
	TargetPlatform::IntType potential_platforms = 0;
 | 
			
		||||
	Media media = GetMediaAndPlatforms(file_name, potential_platforms);
 | 
			
		||||
 | 
			
		||||
	int total_options = std::popcount(potential_platforms);
 | 
			
		||||
	const bool is_confident = total_options == 1;
 | 
			
		||||
	// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
 | 
			
		||||
	// The machine-specific static analyser will still run in case it can provide meaningful annotations on
 | 
			
		||||
	// loading command, machine configuration, etc, but the flag will be passed onwards to mean "don't reject this".
 | 
			
		||||
 | 
			
		||||
	// Hand off to platform-specific determination of whether these
 | 
			
		||||
	// things are actually compatible and, if so, how to load them.
 | 
			
		||||
	const auto append = [&](TargetPlatform::IntType platform, auto evaluator) {
 | 
			
		||||
		if(!(potential_platforms & platform)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		auto new_targets = evaluator(media, file_name, potential_platforms, is_confident);
 | 
			
		||||
		targets.insert(
 | 
			
		||||
			targets.end(),
 | 
			
		||||
			std::make_move_iterator(new_targets.begin()),
 | 
			
		||||
			std::make_move_iterator(new_targets.end())
 | 
			
		||||
		);
 | 
			
		||||
	};
 | 
			
		||||
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
 | 
			
		||||
	auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
 | 
			
		||||
	std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
 | 
			
		||||
}
 | 
			
		||||
	Append(Acorn);
 | 
			
		||||
	Append(AmstradCPC);
 | 
			
		||||
	Append(AppleII);
 | 
			
		||||
	Append(AppleIIgs);
 | 
			
		||||
	Append(Amiga);
 | 
			
		||||
	Append(Atari2600);
 | 
			
		||||
	Append(AtariST);
 | 
			
		||||
	Append(Coleco);
 | 
			
		||||
	Append(Commodore);
 | 
			
		||||
	Append(DiskII);
 | 
			
		||||
	Append(Enterprise);
 | 
			
		||||
	Append(Macintosh);
 | 
			
		||||
	Append(MSX);
 | 
			
		||||
	Append(Oric);
 | 
			
		||||
	Append(Sega);
 | 
			
		||||
	Append(ZX8081);
 | 
			
		||||
	Append(ZXSpectrum);
 | 
			
		||||
#undef Append
 | 
			
		||||
 | 
			
		||||
	append(TargetPlatform::Acorn, Acorn::GetTargets);
 | 
			
		||||
	append(TargetPlatform::AmstradCPC, AmstradCPC::GetTargets);
 | 
			
		||||
	append(TargetPlatform::AppleII, AppleII::GetTargets);
 | 
			
		||||
	append(TargetPlatform::AppleIIgs, AppleIIgs::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Amiga, Amiga::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Atari2600, Atari2600::GetTargets);
 | 
			
		||||
	append(TargetPlatform::AtariST, AtariST::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Coleco, Coleco::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Commodore8bit, Commodore::GetTargets);
 | 
			
		||||
	append(TargetPlatform::DiskII, DiskII::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Enterprise, Enterprise::GetTargets);
 | 
			
		||||
	append(TargetPlatform::FAT12, FAT12::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Macintosh, Macintosh::GetTargets);
 | 
			
		||||
	append(TargetPlatform::MSX, MSX::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Oric, Oric::GetTargets);
 | 
			
		||||
	append(TargetPlatform::PCCompatible, PCCompatible::GetTargets);
 | 
			
		||||
	append(TargetPlatform::Sega, Sega::GetTargets);
 | 
			
		||||
	append(TargetPlatform::ZX8081, ZX8081::GetTargets);
 | 
			
		||||
	append(TargetPlatform::ZXSpectrum, ZXSpectrum::GetTargets);
 | 
			
		||||
	// Reset any tapes to their initial position.
 | 
			
		||||
	for(const auto &target : targets) {
 | 
			
		||||
		for(auto &tape : target->media.tapes) {
 | 
			
		||||
			tape->reset();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
 | 
			
		||||
	// picked their insertion order carefully.
 | 
			
		||||
 
 | 
			
		||||
@@ -6,22 +6,23 @@
 | 
			
		||||
//  Copyright 2016 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef StaticAnalyser_hpp
 | 
			
		||||
#define StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Machines.hpp"
 | 
			
		||||
#include "../Machines.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "Storage/MassStorage/MassStorageDevice.hpp"
 | 
			
		||||
#include "Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../Storage/Cartridge/Cartridge.hpp"
 | 
			
		||||
#include "../../Storage/Disk/Disk.hpp"
 | 
			
		||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
 | 
			
		||||
#include "../../Storage/Tape/Tape.hpp"
 | 
			
		||||
#include "../../Reflection/Struct.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
 | 
			
		||||
struct State;
 | 
			
		||||
 | 
			
		||||
@@ -39,15 +40,12 @@ struct Media {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Media &operator +=(const Media &rhs) {
 | 
			
		||||
		const auto append = [&](auto &destination, auto &source) {
 | 
			
		||||
			destination.insert(destination.end(), source.begin(), source.end());
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		append(disks, rhs.disks);
 | 
			
		||||
		append(tapes, rhs.tapes);
 | 
			
		||||
		append(cartridges, rhs.cartridges);
 | 
			
		||||
		append(mass_storage_devices, rhs.mass_storage_devices);
 | 
			
		||||
 | 
			
		||||
#define append(name)	name.insert(name.end(), rhs.name.begin(), rhs.name.end());
 | 
			
		||||
		append(disks);
 | 
			
		||||
		append(tapes);
 | 
			
		||||
		append(cartridges);
 | 
			
		||||
		append(mass_storage_devices);
 | 
			
		||||
#undef append
 | 
			
		||||
		return *this;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -58,16 +56,16 @@ struct Media {
 | 
			
		||||
*/
 | 
			
		||||
struct Target {
 | 
			
		||||
	Target(Machine machine) : machine(machine) {}
 | 
			
		||||
	virtual ~Target() = default;
 | 
			
		||||
	virtual ~Target() {}
 | 
			
		||||
 | 
			
		||||
	// This field is entirely optional.
 | 
			
		||||
	std::unique_ptr<Reflection::Struct> state;
 | 
			
		||||
 | 
			
		||||
	Machine machine;
 | 
			
		||||
	Media media;
 | 
			
		||||
	float confidence = 0.5f;
 | 
			
		||||
	float confidence = 0.0f;
 | 
			
		||||
};
 | 
			
		||||
using TargetList = std::vector<std::unique_ptr<Target>>;
 | 
			
		||||
typedef std::vector<std::unique_ptr<Target>> TargetList;
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
	Attempts, through any available means, to return a list of potential targets for the file with the given name.
 | 
			
		||||
@@ -82,3 +80,6 @@ TargetList GetTargets(const std::string &file_name);
 | 
			
		||||
Media GetMedia(const std::string &file_name);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -12,32 +12,27 @@
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/ZX8081.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
 | 
			
		||||
 | 
			
		||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(Storage::Tape::TapeSerialiser &serialiser) {
 | 
			
		||||
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	std::vector<Storage::Data::ZX8081::File> files;
 | 
			
		||||
	Storage::Tape::ZX8081::Parser parser;
 | 
			
		||||
 | 
			
		||||
	while(!serialiser.is_at_end()) {
 | 
			
		||||
		const auto next_file = parser.get_next_file(serialiser);
 | 
			
		||||
		if(next_file) {
 | 
			
		||||
			files.push_back(std::move(*next_file));
 | 
			
		||||
	while(!tape->is_at_end()) {
 | 
			
		||||
		std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
 | 
			
		||||
		if(next_file != nullptr) {
 | 
			
		||||
			files.push_back(*next_file);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType potential_platforms,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		const auto serialiser = media.tapes.front()->serialiser();
 | 
			
		||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(*serialiser);
 | 
			
		||||
		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
 | 
			
		||||
		media.tapes.front()->reset();
 | 
			
		||||
		if(!files.empty()) {
 | 
			
		||||
			Target *const target = new Target;
 | 
			
		||||
			destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,21 @@
 | 
			
		||||
//  Copyright 2017 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_ZX8081_StaticAnalyser_hpp
 | 
			
		||||
#define Analyser_Static_ZX8081_StaticAnalyser_hpp
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include "../../../Storage/TargetPlatforms.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZX8081 {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace ZX8081 {
 | 
			
		||||
 | 
			
		||||
TargetList GetTargets(const Media &, const std::string &, TargetPlatform::IntType, bool);
 | 
			
		||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* StaticAnalyser_hpp */
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,17 @@
 | 
			
		||||
//  Copyright 2018 Thomas Harte. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifndef Analyser_Static_ZX8081_Target_h
 | 
			
		||||
#define Analyser_Static_ZX8081_Target_h
 | 
			
		||||
 | 
			
		||||
#include "Analyser/Static/StaticAnalyser.hpp"
 | 
			
		||||
#include "Reflection/Enum.hpp"
 | 
			
		||||
#include "Reflection/Struct.hpp"
 | 
			
		||||
#include "../../../Reflection/Enum.hpp"
 | 
			
		||||
#include "../../../Reflection/Struct.hpp"
 | 
			
		||||
#include "../StaticAnalyser.hpp"
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Analyser::Static::ZX8081 {
 | 
			
		||||
namespace Analyser {
 | 
			
		||||
namespace Static {
 | 
			
		||||
namespace ZX8081 {
 | 
			
		||||
 | 
			
		||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
 | 
			
		||||
	ReflectableEnum(MemoryModel,
 | 
			
		||||
@@ -27,16 +30,18 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
 | 
			
		||||
	bool ZX80_uses_ZX81_ROM = false;
 | 
			
		||||
	std::string loading_command;
 | 
			
		||||
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZX8081) {}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	friend Reflection::StructImpl<Target>;
 | 
			
		||||
	void declare_fields() {
 | 
			
		||||
		DeclareField(memory_model);
 | 
			
		||||
		DeclareField(is_ZX81);
 | 
			
		||||
		DeclareField(ZX80_uses_ZX81_ROM);
 | 
			
		||||
		AnnounceEnum(MemoryModel);
 | 
			
		||||
	Target(): Analyser::Static::Target(Machine::ZX8081) {
 | 
			
		||||
		if(needs_declare()) {
 | 
			
		||||
			DeclareField(memory_model);
 | 
			
		||||
			DeclareField(is_ZX81);
 | 
			
		||||
			DeclareField(ZX80_uses_ZX81_ROM);
 | 
			
		||||
			AnnounceEnum(MemoryModel);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* Analyser_Static_ZX8081_Target_h */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,14 @@
 | 
			
		||||
 | 
			
		||||
#include "StaticAnalyser.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Storage/Disk/Parsers/CPM.hpp"
 | 
			
		||||
#include "Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
 | 
			
		||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Target.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
 | 
			
		||||
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
 | 
			
		||||
	using Parser = Storage::Tape::ZXSpectrum::Parser;
 | 
			
		||||
	Parser parser(Parser::MachineType::ZXSpectrum);
 | 
			
		||||
 | 
			
		||||
@@ -36,79 +33,30 @@ bool IsSpectrumTape(Storage::Tape::TapeSerialiser &tape) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
 | 
			
		||||
	Storage::Encodings::MFM::Parser parser(true, disk);
 | 
			
		||||
 | 
			
		||||
	// Grab absolutely any sector from the first track to determine general encoding.
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *any_sector = parser.any_sector(0, 0);
 | 
			
		||||
	if(!any_sector) return false;
 | 
			
		||||
 | 
			
		||||
	// Determine the sector base and get logical sector 1.
 | 
			
		||||
	const uint8_t sector_base = any_sector->address.sector & 0xc0;
 | 
			
		||||
	const Storage::Encodings::MFM::Sector *boot_sector = parser.sector(0, 0, sector_base + 1);
 | 
			
		||||
	// Get logical sector 1; the Spectrum appears to support various physical
 | 
			
		||||
	// sectors as sector 1.
 | 
			
		||||
	Storage::Encodings::MFM::Sector *boot_sector = nullptr;
 | 
			
		||||
	uint8_t sector_mask = 0;
 | 
			
		||||
	while(!boot_sector) {
 | 
			
		||||
		boot_sector = parser.get_sector(0, 0, sector_mask + 1);
 | 
			
		||||
		sector_mask += 0x40;
 | 
			
		||||
		if(!sector_mask) break;
 | 
			
		||||
	}
 | 
			
		||||
	if(!boot_sector) return false;
 | 
			
		||||
 | 
			
		||||
	Storage::Disk::CPM::ParameterBlock cpm_format{};
 | 
			
		||||
	switch(sector_base) {
 | 
			
		||||
		case 0x40:	cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_system_format();	break;
 | 
			
		||||
		case 0xc0:	cpm_format = Storage::Disk::CPM::ParameterBlock::cpc_data_format();		break;
 | 
			
		||||
 | 
			
		||||
		default: {
 | 
			
		||||
			// Check the first ten bytes of the first sector for the disk format; if these are all
 | 
			
		||||
			// the same value then instead substitute a default format.
 | 
			
		||||
			std::array<uint8_t, 10> format;
 | 
			
		||||
			std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin());
 | 
			
		||||
			if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) {
 | 
			
		||||
				format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52};
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Parse those ten bytes as:
 | 
			
		||||
			//
 | 
			
		||||
			// Byte 0: disc type
 | 
			
		||||
			// Byte 1: sidedness
 | 
			
		||||
			//		bits 0-6: arrangement
 | 
			
		||||
			//			0 => single sided
 | 
			
		||||
			//			1 => double sided, flip sides
 | 
			
		||||
			//			2 => double sided, up and over
 | 
			
		||||
			//		bit 7: double-track
 | 
			
		||||
			// Byte 2: number of tracks per side
 | 
			
		||||
			// Byte 3: number of sectors per track
 | 
			
		||||
			// Byte 4: Log2(sector size) - 7
 | 
			
		||||
			// Byte 5: number of reserved tracks
 | 
			
		||||
			// Byte 6: Log2(block size) - 7
 | 
			
		||||
			// Byte 7: number of directory blocks
 | 
			
		||||
			// Byte 8: gap length (read/write)
 | 
			
		||||
			// Byte 9: gap length(format)
 | 
			
		||||
			cpm_format.sectors_per_track = format[3];
 | 
			
		||||
			cpm_format.tracks = format[2];
 | 
			
		||||
			cpm_format.block_size = 128 << format[6];
 | 
			
		||||
			cpm_format.first_sector = sector_base + 1;
 | 
			
		||||
			cpm_format.reserved_tracks = format[5];
 | 
			
		||||
 | 
			
		||||
			// i.e. bits set downward from 0x4000 for as many blocks as form the catalogue.
 | 
			
		||||
			cpm_format.catalogue_allocation_bitmap = 0x8000 - (0x8000 >> format[7]);
 | 
			
		||||
		} break;
 | 
			
		||||
	// Test that the contents of the boot sector sum to 3, modulo 256.
 | 
			
		||||
	uint8_t byte_sum = 0;
 | 
			
		||||
	for(auto byte: boot_sector->samples[0]) {
 | 
			
		||||
		byte_sum += byte;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the boot sector sums to 3 modulo 256 then this is a Spectrum disk.
 | 
			
		||||
	const auto byte_sum = static_cast<uint8_t>(
 | 
			
		||||
		std::accumulate(boot_sector->samples[0].begin(), boot_sector->samples[0].end(), 0));
 | 
			
		||||
	if(byte_sum == 3) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ... otherwise read a CPM directory and look for a BASIC program called "DISK".
 | 
			
		||||
	const auto catalogue = Storage::Disk::CPM::GetCatalogue(disk, cpm_format, false);
 | 
			
		||||
	return catalogue && catalogue->is_zx_spectrum_booter();
 | 
			
		||||
	return byte_sum == 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
 | 
			
		||||
	const Media &media,
 | 
			
		||||
	const std::string &,
 | 
			
		||||
	TargetPlatform::IntType,
 | 
			
		||||
	bool
 | 
			
		||||
) {
 | 
			
		||||
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
 | 
			
		||||
	TargetList destination;
 | 
			
		||||
	auto target = std::make_unique<Target>();
 | 
			
		||||
	target->confidence = 0.5;
 | 
			
		||||
@@ -116,8 +64,7 @@ Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(
 | 
			
		||||
	if(!media.tapes.empty()) {
 | 
			
		||||
		bool has_spectrum_tape = false;
 | 
			
		||||
		for(auto &tape: media.tapes) {
 | 
			
		||||
			auto serialiser = tape->serialiser();
 | 
			
		||||
			has_spectrum_tape |= IsSpectrumTape(*serialiser);
 | 
			
		||||
			has_spectrum_tape |= IsSpectrumTape(tape);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(has_spectrum_tape) {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user