mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-30 14:16:04 +00:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
			OptionalFu
			...
			OpDumper
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f8eb7dd015 | ||
|  | 716153cf0a | ||
|  | 821b5358ea | ||
|  | fa1139acb3 | ||
|  | 3a42b0ac3d | ||
|  | 14ff3162cf | ||
|  | 768f47198f | ||
|  | b4bb7cdaf9 | ||
|  | 3673f931b8 | ||
|  | 3dce673b37 | ||
|  | f7932d8583 | ||
|  | b6c91035f4 | ||
|  | 7f030bc282 | ||
|  | 32f946b3f0 | ||
|  | 939f015007 | ||
|  | 20d54c0397 | ||
|  | 950fddebf9 | ||
|  | 47d5d65633 | ||
|  | be1cec8f55 | ||
|  | 6207f2ab41 | ||
|  | e30a02a0c0 | ||
|  | c7097274c9 | ||
|  | e82f2a3810 | ||
|  | cbf6ae81d0 | ||
|  | 7a6fab72fe | ||
|  | 4005506e42 | 
							
								
								
									
										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,15 +6,18 @@ | ||||
| //  Copyright 2018 Thomas Harte. All rights reserved. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
| #ifndef Analyser_Static_Acorn_Target_h | ||||
| #define Analyser_Static_Acorn_Target_h | ||||
|  | ||||
| #include "Analyser/Static/StaticAnalyser.hpp" | ||||
| #include "Reflection/Struct.hpp" | ||||
| #include "../../../Reflection/Struct.hpp" | ||||
| #include "../StaticAnalyser.hpp" | ||||
| #include <string> | ||||
|  | ||||
| namespace Analyser::Static::Acorn { | ||||
| namespace Analyser { | ||||
| namespace Static { | ||||
| namespace Acorn { | ||||
|  | ||||
| struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> { | ||||
| struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { | ||||
| 	bool has_acorn_adfs = false; | ||||
| 	bool has_pres_adfs = false; | ||||
| 	bool has_dfs = false; | ||||
| @@ -23,46 +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; | ||||
|  | ||||
| 	BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {} | ||||
|  | ||||
| private: | ||||
| 	friend Reflection::StructImpl<BBCMicroTarget>; | ||||
| 	void declare_fields() { | ||||
| 		DeclareField(has_1770dfs); | ||||
| 		DeclareField(has_adfs); | ||||
| 		DeclareField(has_sideways_ram); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> { | ||||
| 	std::string main_program; | ||||
|  | ||||
| 	ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {} | ||||
|  | ||||
| private: | ||||
| 	friend Reflection::StructImpl<ArchimedesTarget>; | ||||
| 	void declare_fields() {} | ||||
| }; | ||||
|  | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif /* Analyser_Static_Acorn_Target_h */ | ||||
|   | ||||
| @@ -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,78 @@ | ||||
| #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/MacintoshIMG.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/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 +94,122 @@ 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 *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("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 +218,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 +246,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