mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			97 Commits
		
	
	
		
			2018-01-15
			...
			2018-03-04
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dfcc502a88 | ||
|  | 1c6faaae88 | ||
|  | 35c8a0dd8c | ||
|  | 38feedaf6a | ||
|  | 0a2f908af4 | ||
|  | 705d53cc21 | ||
|  | 35b18d58af | ||
|  | 3c5a8d9ff3 | ||
|  | 7ca02be578 | ||
|  | ea13c7dd32 | ||
|  | fdfd72a42c | ||
|  | da97bf95c0 | ||
|  | bdfc36427c | ||
|  | 74dfe56d2b | ||
|  | 6cce9aa54e | ||
|  | ba68b7247b | ||
|  | b02e4fbbf6 | ||
|  | 59b4c7314d | ||
|  | d328589bd0 | ||
|  | b05d2b26bf | ||
|  | 86239469e7 | ||
|  | 7890506b16 | ||
|  | 83f73c3f02 | ||
|  | 87760297fc | ||
|  | 5b854d51e7 | ||
|  | d4df101ab6 | ||
|  | 0ad2676640 | ||
|  | a074ee2071 | ||
|  | 204d5cc964 | ||
|  | 23d15a4d6c | ||
|  | 23c47e21de | ||
|  | 5530b96446 | ||
|  | 99d28a172b | ||
|  | d83178f29d | ||
|  | d9d5ffdaa2 | ||
|  | cabad6fc05 | ||
|  | a4dc9c0403 | ||
|  | 270723ae72 | ||
|  | b215cf83d5 | ||
|  | f237dcf904 | ||
|  | fc81bfa59b | ||
|  | 832ac173ae | ||
|  | 3673cfe9be | ||
|  | 6aaef97158 | ||
|  | b0ab617393 | ||
|  | 6780b0bf11 | ||
|  | 9c0a440c38 | ||
|  | 2439f5aee5 | ||
|  | 8265f289bd | ||
|  | 9728bea0a7 | ||
|  | fc9e84c72e | ||
|  | 7d75e864b1 | ||
|  | a005dabbe3 | ||
|  | c8a4432c63 | ||
|  | 7b420d56e3 | ||
|  | ddf1bf3cbf | ||
|  | 7ea4ca00dc | ||
|  | 6b8c223804 | ||
|  | 23105956d6 | ||
|  | d751b7e2cb | ||
|  | f02989649c | ||
|  | dcf313a833 | ||
|  | 9960121b08 | ||
|  | 8eea55b51c | ||
|  | e1cab52c84 | ||
|  | eb39617ad0 | ||
|  | 43b682a5af | ||
|  | 043fd5d404 | ||
|  | d63a95983d | ||
|  | 4cf258f952 | ||
|  | 4e720d57b2 | ||
|  | c12aaea747 | ||
|  | ca48497e87 | ||
|  | d493ea4bca | ||
|  | e025674eb2 | ||
|  | f2519f4fd7 | ||
|  | db914d8c56 | ||
|  | 66faed4008 | ||
|  | 11abc99ef8 | ||
|  | 21efb32b6f | ||
|  | 622a04aec8 | ||
|  | d360b2c62d | ||
|  | 6a112edc18 | ||
|  | 8fb4409ebb | ||
|  | d213341d9c | ||
|  | c2f1306d85 | ||
|  | 2143ea6f12 | ||
|  | edb30b3c6c | ||
|  | 234e4f6f66 | ||
|  | ce2d3c6e82 | ||
|  | 46c76b9c07 | ||
|  | 583c3cfe7d | ||
|  | e13312dcc5 | ||
|  | d9e49c0d5f | ||
|  | 8a370cc1ac | ||
|  | cdae0fa593 | ||
|  | 765c0d4ff8 | 
							
								
								
									
										30
									
								
								Analyser/Dynamic/ConfidenceCounter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Analyser/Dynamic/ConfidenceCounter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceCounter.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ConfidenceCounter.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | float ConfidenceCounter::get_confidence() { | ||||||
|  | 	return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_hit() { | ||||||
|  | 	hits_++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_miss() { | ||||||
|  | 	misses_++; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfidenceCounter::add_equivocal() { | ||||||
|  | 	if(hits_ > misses_) { | ||||||
|  | 		hits_++; | ||||||
|  | 		misses_++; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								Analyser/Dynamic/ConfidenceCounter.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Analyser/Dynamic/ConfidenceCounter.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceCounter.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceCounter_hpp | ||||||
|  | #define ConfidenceCounter_hpp | ||||||
|  |  | ||||||
|  | #include "ConfidenceSource.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a confidence source that calculates its probability by virtual of a history of events. | ||||||
|  |  | ||||||
|  | 	The initial value of the confidence counter is 0.5. | ||||||
|  | */ | ||||||
|  | class ConfidenceCounter: public ConfidenceSource { | ||||||
|  | 	public: | ||||||
|  | 		/*! @returns The computed probability, based on the history of events. */ | ||||||
|  | 		float get_confidence() override; | ||||||
|  |  | ||||||
|  | 		/*! Records an event that implies this is the appropriate class — pushes probability up towards 1.0. */ | ||||||
|  | 		void add_hit(); | ||||||
|  |  | ||||||
|  | 		/*! Records an event that implies this is not the appropriate class — pushes probability down towards 0.0. */ | ||||||
|  | 		void add_miss(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Records an event that could be correct but isn't necessarily so; which can push probability | ||||||
|  | 			down towards 0.5, but will never push it upwards. | ||||||
|  | 		*/ | ||||||
|  | 		void add_equivocal(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int hits_ = 1; | ||||||
|  | 		int misses_ = 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceCounter_hpp */ | ||||||
							
								
								
									
										28
									
								
								Analyser/Dynamic/ConfidenceSource.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Dynamic/ConfidenceSource.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSource.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceSource_hpp | ||||||
|  | #define ConfidenceSource_hpp | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides an abstract interface through which objects can declare the probability | ||||||
|  | 	that they are the proper target for their input; e.g. if an Acorn Electron is asked | ||||||
|  | 	to run an Atari 2600 program then its confidence should shrink towards 0.0; if the | ||||||
|  | 	program is handed to an Atari 2600 then its confidence should grow towards 1.0. | ||||||
|  | */ | ||||||
|  | struct ConfidenceSource { | ||||||
|  | 	virtual float get_confidence() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceSource_hpp */ | ||||||
							
								
								
									
										28
									
								
								Analyser/Dynamic/ConfidenceSummary.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Analyser/Dynamic/ConfidenceSummary.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSummary.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ConfidenceSummary.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <numeric> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) : | ||||||
|  | 	sources_(sources), weights_(weights) { | ||||||
|  | 	assert(weights.size() == sources.size()); | ||||||
|  | 	weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ConfidenceSummary::get_confidence() { | ||||||
|  | 	float result = 0.0f; | ||||||
|  | 	for(std::size_t index = 0; index < sources_.size(); ++index) { | ||||||
|  | 		result += sources_[index]->get_confidence() * weights_[index]; | ||||||
|  | 	} | ||||||
|  | 	return result / weight_sum_; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								Analyser/Dynamic/ConfidenceSummary.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Analyser/Dynamic/ConfidenceSummary.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // | ||||||
|  | //  ConfidenceSummary.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ConfidenceSummary_hpp | ||||||
|  | #define ConfidenceSummary_hpp | ||||||
|  |  | ||||||
|  | #include "ConfidenceSource.hpp" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Summaries a collection of confidence sources by calculating their weighted sum. | ||||||
|  | */ | ||||||
|  | class ConfidenceSummary: public ConfidenceSource { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Instantiates a summary that will produce the weighted sum of | ||||||
|  | 			@c sources, each using the corresponding entry of @c weights. | ||||||
|  |  | ||||||
|  | 			Requires that @c sources and @c weights are of the same length. | ||||||
|  | 		*/ | ||||||
|  | 		ConfidenceSummary( | ||||||
|  | 			const std::vector<ConfidenceSource *> &sources, | ||||||
|  | 			const std::vector<float> &weights); | ||||||
|  |  | ||||||
|  | 		/*! @returns The weighted sum of all sources. */ | ||||||
|  | 		float get_confidence() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<ConfidenceSource *> sources_; | ||||||
|  | 		std::vector<float> weights_; | ||||||
|  | 		float weight_sum_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ConfidenceSummary_hpp */ | ||||||
							
								
								
									
										115
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								Analyser/Dynamic/MultiMachine/Implementation/MultiCRTMachine.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | // | ||||||
|  | //  MultiCRTMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiCRTMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <mutex> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) : | ||||||
|  | 	machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) { | ||||||
|  | 	speaker_ = MultiSpeaker::create(machines); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) { | ||||||
|  | 	// Apply a blunt force parallelisation of the machines; each run_for is dispatched | ||||||
|  | 	// to a separate queue and this queue will block until all are done. | ||||||
|  | 	volatile std::size_t outstanding_machines; | ||||||
|  | 	std::condition_variable condition; | ||||||
|  | 	std::mutex mutex; | ||||||
|  | 	{ | ||||||
|  | 		std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 		std::lock_guard<std::mutex> lock(mutex); | ||||||
|  | 		outstanding_machines = machines_.size(); | ||||||
|  |  | ||||||
|  | 		for(std::size_t index = 0; index < machines_.size(); ++index) { | ||||||
|  | 			CRTMachine::Machine *crt_machine = machines_[index]->crt_machine(); | ||||||
|  | 			queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() { | ||||||
|  | 				if(crt_machine) function(crt_machine); | ||||||
|  |  | ||||||
|  | 				std::lock_guard<std::mutex> lock(mutex); | ||||||
|  | 				outstanding_machines--; | ||||||
|  | 				condition.notify_all(); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::unique_lock<std::mutex> lock(mutex); | ||||||
|  | 	condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		CRTMachine::Machine *crt_machine = machine->crt_machine(); | ||||||
|  | 		if(crt_machine) function(crt_machine); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::setup_output(float aspect_ratio) { | ||||||
|  | 	perform_serial([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		machine->setup_output(aspect_ratio); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::close_output() { | ||||||
|  | 	perform_serial([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		machine->close_output(); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::CRT::CRT *MultiCRTMachine::get_crt() { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); | ||||||
|  | 	return crt_machine ? crt_machine->get_crt() : nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() { | ||||||
|  | 	return speaker_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::run_for(const Cycles cycles) { | ||||||
|  | 	perform_parallel([=](::CRTMachine::Machine *machine) { | ||||||
|  | 		if(machine->get_confidence() >= 0.01f) machine->run_for(cycles); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	if(delegate_) delegate_->multi_crt_did_run_machines(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | double MultiCRTMachine::get_clock_rate() { | ||||||
|  | 	// TODO: something smarter than this? Not all clock rates will necessarily be the same. | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); | ||||||
|  | 	return crt_machine ? crt_machine->get_clock_rate() : 0.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiCRTMachine::get_clock_is_unlimited() { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	CRTMachine::Machine *crt_machine = machines_.front()->crt_machine(); | ||||||
|  | 	return crt_machine ? crt_machine->get_clock_is_unlimited() : false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::did_change_machine_order() { | ||||||
|  | 	if(speaker_) { | ||||||
|  | 		speaker_->set_new_front_machine(machines_.front().get()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::set_delegate(::CRTMachine::Machine::Delegate *delegate) { | ||||||
|  | 	// TODO:  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::machine_did_change_clock_rate(Machine *machine) { | ||||||
|  | 	// TODO: consider passing along. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiCRTMachine::machine_did_change_clock_is_unlimited(Machine *machine) { | ||||||
|  | 	// TODO: consider passing along. | ||||||
|  | } | ||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | // | ||||||
|  | //  MultiCRTMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiCRTMachine_hpp | ||||||
|  | #define MultiCRTMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  | #include "../../../../Machines/CRTMachine.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "MultiSpeaker.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the CRT machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Keeps a reference to the original vector of machines; will access it only after | ||||||
|  | 	acquiring a supplied mutex. The owner should also call did_change_machine_order() | ||||||
|  | 	if the order of machines changes. | ||||||
|  | */ | ||||||
|  | class MultiCRTMachine: public CRTMachine::Machine, public CRTMachine::Machine::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine | ||||||
|  | 			uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that | ||||||
|  | 			are necessary to bridge the gap between one machine and the next. | ||||||
|  | 		*/ | ||||||
|  | 		void did_change_machine_order(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Provides a mechanism by which a delegate can be informed each time a call to run_for has | ||||||
|  | 			been received. | ||||||
|  | 		*/ | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void multi_crt_did_run_machines() = 0; | ||||||
|  | 		}; | ||||||
|  | 		/// Sets @c delegate as the receiver of delegate messages. | ||||||
|  | 		void set_delegate(Delegate *delegate) { | ||||||
|  | 			delegate_ = delegate; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Below is the standard CRTMachine::Machine interface; see there for documentation. | ||||||
|  | 		void setup_output(float aspect_ratio) override; | ||||||
|  | 		void close_output() override; | ||||||
|  | 		Outputs::CRT::CRT *get_crt() override; | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override; | ||||||
|  | 		void run_for(const Cycles cycles) override; | ||||||
|  | 		double get_clock_rate() override; | ||||||
|  | 		bool get_clock_is_unlimited() override; | ||||||
|  | 		void set_delegate(::CRTMachine::Machine::Delegate *delegate) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		// CRTMachine::Machine::Delegate | ||||||
|  | 		void machine_did_change_clock_rate(Machine *machine) override; | ||||||
|  | 		void machine_did_change_clock_is_unlimited(Machine *machine) override; | ||||||
|  |  | ||||||
|  | 		const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_; | ||||||
|  | 		std::mutex &machines_mutex_; | ||||||
|  | 		std::vector<Concurrency::AsyncTaskQueue> queues_; | ||||||
|  | 		MultiSpeaker *speaker_ = nullptr; | ||||||
|  | 		Delegate *delegate_ = nullptr; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs a parallel for operation across all machines, performing the supplied | ||||||
|  | 			function on each and returning only once all applications have completed. | ||||||
|  |  | ||||||
|  | 			No guarantees are extended as to which thread operations will occur on. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Performs a serial for operation across all machines, performing the supplied | ||||||
|  | 			function on each on the calling thread. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_serial(const std::function<void(::CRTMachine::Machine *)> &); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* MultiCRTMachine_hpp */ | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurable.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiConfigurable.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		Configurable::Device *device = machine->configurable_device(); | ||||||
|  | 		if(device) devices_.push_back(device); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() { | ||||||
|  | 	std::vector<std::unique_ptr<Configurable::Option>> options; | ||||||
|  |  | ||||||
|  | 	// Produce the list of unique options. | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options(); | ||||||
|  | 		for(auto &option : device_options) { | ||||||
|  | 			if(std::find(options.begin(), options.end(), option) == options.end()) { | ||||||
|  | 				options.push_back(std::move(option)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return options; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) { | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		device->set_selections(selection_by_option); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::SelectionSet MultiConfigurable::get_accurate_selections() { | ||||||
|  | 	Configurable::SelectionSet set; | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		Configurable::SelectionSet device_set = device->get_accurate_selections(); | ||||||
|  | 		for(auto &selection : device_set) { | ||||||
|  | 			set.insert(std::move(selection)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return set; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() { | ||||||
|  | 	Configurable::SelectionSet set; | ||||||
|  | 	for(const auto &device : devices_) { | ||||||
|  | 		Configurable::SelectionSet device_set = device->get_user_friendly_selections(); | ||||||
|  | 		for(auto &selection : device_set) { | ||||||
|  | 			set.insert(std::move(selection)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return set; | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurable.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiConfigurable_hpp | ||||||
|  | #define MultiConfigurable_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the configurable interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiConfigurable: public Configurable::Device { | ||||||
|  | 	public: | ||||||
|  | 		MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard Configurable::Device interface; see there for documentation. | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override; | ||||||
|  | 		void set_selections(const Configurable::SelectionSet &selection_by_option) override; | ||||||
|  | 		Configurable::SelectionSet get_accurate_selections() override; | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<Configurable::Device *> devices_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiConfigurable_hpp */ | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurationTarget.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiConfigurationTarget.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); | ||||||
|  | 		if(configuration_target) targets_.push_back(configuration_target); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target &target) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) { | ||||||
|  | 	bool inserted = false; | ||||||
|  | 	for(const auto &target : targets_) { | ||||||
|  | 		inserted |= target->insert_media(media); | ||||||
|  | 	} | ||||||
|  | 	return inserted; | ||||||
|  | } | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | //  MultiConfigurationTarget.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiConfigurationTarget_hpp | ||||||
|  | #define MultiConfigurationTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/ConfigurationTarget.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the configuration target interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | struct MultiConfigurationTarget: public ConfigurationTarget::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard ConfigurationTarget::Machine interface; see there for documentation. | ||||||
|  | 		void configure_as_target(const Analyser::Static::Target &target) override; | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<ConfigurationTarget::Machine *> targets_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiConfigurationTarget_hpp */ | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | // | ||||||
|  | //  MultiJoystickMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiJoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		JoystickMachine::Machine *joystick_machine = machine->joystick_machine(); | ||||||
|  | 		if(joystick_machine) machines_.push_back(joystick_machine); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() { | ||||||
|  | 	return joysticks_; | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  MultiJoystickMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiJoystickMachine_hpp | ||||||
|  | #define MultiJoystickMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the joystick machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiJoystickMachine: public JoystickMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard JoystickMachine::Machine interface; see there for documentation. | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<JoystickMachine::Machine *> machines_; | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiJoystickMachine_hpp */ | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | //  MultiKeyboardMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiKeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine(); | ||||||
|  | 		if(keyboard_machine) machines_.push_back(keyboard_machine); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::clear_all_keys() { | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		machine->clear_all_keys(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  |         machine->set_key_state(key, is_pressed); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::type_string(const std::string &string) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  |         machine->type_string(string); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | ||||||
|  |     for(const auto &machine: machines_) { | ||||||
|  | 		uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key); | ||||||
|  | 		if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | // | ||||||
|  | //  MultiKeyboardMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 09/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiKeyboardMachine_hpp | ||||||
|  | #define MultiKeyboardMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  | #include "../../../../Machines/KeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the keyboard machine interface to multiple machines. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; makes no guarantees about the | ||||||
|  | 	order of delivered messages. | ||||||
|  | */ | ||||||
|  | class MultiKeyboardMachine: public KeyboardMachine::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard KeyboardMachine::Machine interface; see there for documentation. | ||||||
|  | 		void clear_all_keys() override; | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override; | ||||||
|  | 		void type_string(const std::string &) override; | ||||||
|  | 		void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<::KeyboardMachine::Machine *> machines_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiKeyboardMachine_hpp */ | ||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | // | ||||||
|  | //  MultiSpeaker.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiSpeaker.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	std::vector<Outputs::Speaker::Speaker *> speakers; | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker(); | ||||||
|  | 		if(speaker) speakers.push_back(speaker); | ||||||
|  | 	} | ||||||
|  | 	if(speakers.empty()) return nullptr; | ||||||
|  |  | ||||||
|  | 	return new MultiSpeaker(speakers); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) : | ||||||
|  | 	speakers_(speakers), front_speaker_(speakers.front()) { | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		speaker->set_delegate(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) { | ||||||
|  | 	float ideal = 0.0f; | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ideal / static_cast<float>(speakers_.size()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) { | ||||||
|  | 	for(const auto &speaker: speakers_) { | ||||||
|  | 		speaker->set_output_rate(cycles_per_second, buffer_size); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) { | ||||||
|  | 	delegate_ = delegate; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) { | ||||||
|  | 	std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 	if(delegate_ && speaker == front_speaker_) { | ||||||
|  | 		delegate_->speaker_did_complete_samples(this, buffer); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) { | ||||||
|  | 	std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_); | ||||||
|  | 	front_speaker_ = machine->crt_machine()->get_speaker(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | // | ||||||
|  | //  MultiSpeaker.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 18/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiSpeaker_hpp | ||||||
|  | #define MultiSpeaker_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  | #include "../../../../Outputs/Speaker/Speaker.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order | ||||||
|  | 	transparently to connect a single caller to multiple destinations. | ||||||
|  |  | ||||||
|  | 	Makes a static internal copy of the list of machines; expects the owner to keep it | ||||||
|  | 	abreast of the current frontmost machine. | ||||||
|  | */ | ||||||
|  | class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Provides a construction mechanism that may return nullptr, in the case that all included | ||||||
|  | 			machines return nullptr as their speaker. | ||||||
|  | 		*/ | ||||||
|  | 		static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		/// This class requires the caller to nominate changes in the frontmost machine. | ||||||
|  | 		void set_new_front_machine(::Machine::DynamicMachine *machine); | ||||||
|  |  | ||||||
|  | 		// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation. | ||||||
|  | 		float get_ideal_clock_rate_in_range(float minimum, float maximum); | ||||||
|  | 		void set_output_rate(float cycles_per_second, int buffer_size); | ||||||
|  | 		void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer); | ||||||
|  | 		MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers); | ||||||
|  |  | ||||||
|  | 		std::vector<Outputs::Speaker::Speaker *> speakers_; | ||||||
|  | 		Outputs::Speaker::Speaker *front_speaker_ = nullptr; | ||||||
|  | 		Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr; | ||||||
|  | 		std::mutex front_speaker_mutex_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiSpeaker_hpp */ | ||||||
							
								
								
									
										104
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | // | ||||||
|  | //  MultiMachine.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||||
|  | 	machines_(std::move(machines)), | ||||||
|  | 	configurable_(machines_), | ||||||
|  | 	configuration_target_(machines_), | ||||||
|  | 	crt_machine_(machines_, machines_mutex_), | ||||||
|  | 	joystick_machine_(machines), | ||||||
|  | 	keyboard_machine_(machines_) { | ||||||
|  | 	crt_machine_.set_delegate(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ConfigurationTarget::Machine *MultiMachine::configuration_target() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->configuration_target(); | ||||||
|  | 	} else { | ||||||
|  | 		return &configuration_target_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CRTMachine::Machine *MultiMachine::crt_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->crt_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &crt_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | JoystickMachine::Machine *MultiMachine::joystick_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->joystick_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &joystick_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardMachine::Machine *MultiMachine::keyboard_machine() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->keyboard_machine(); | ||||||
|  | 	} else { | ||||||
|  | 		return &keyboard_machine_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Configurable::Device *MultiMachine::configurable_device() { | ||||||
|  | 	if(has_picked_) { | ||||||
|  | 		return machines_.front()->configurable_device(); | ||||||
|  | 	} else { | ||||||
|  | 		return &configurable_; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiMachine::multi_crt_did_run_machines() { | ||||||
|  | 	std::lock_guard<std::mutex> machines_lock(machines_mutex_); | ||||||
|  | 	for(const auto &machine: machines_) { | ||||||
|  | 		CRTMachine::Machine *crt = machine->crt_machine(); | ||||||
|  | 		printf("%0.2f ", crt->get_confidence()); | ||||||
|  | 		crt->print_type(); | ||||||
|  | 		printf("; "); | ||||||
|  | 	} | ||||||
|  | 	printf("\n"); | ||||||
|  |  | ||||||
|  | 	DynamicMachine *front = machines_.front().get(); | ||||||
|  | 	std::stable_sort(machines_.begin(), machines_.end(), | ||||||
|  |         [] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){ | ||||||
|  | 		    CRTMachine::Machine *lhs_crt = lhs->crt_machine(); | ||||||
|  | 		    CRTMachine::Machine *rhs_crt = rhs->crt_machine(); | ||||||
|  | 		    return lhs_crt->get_confidence() > rhs_crt->get_confidence(); | ||||||
|  | 	    }); | ||||||
|  |  | ||||||
|  | 	if(machines_.front().get() != front) { | ||||||
|  | 		crt_machine_.did_change_machine_order(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if( | ||||||
|  | 		(machines_.front()->crt_machine()->get_confidence() > 0.9f) || | ||||||
|  | 		(machines_.front()->crt_machine()->get_confidence() >= 2.0f * machines_[1]->crt_machine()->get_confidence()) | ||||||
|  | 	) { | ||||||
|  | 		pick_first(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MultiMachine::pick_first() { | ||||||
|  | 	has_picked_ = true; | ||||||
|  | //	machines_.erase(machines_.begin() + 1, machines_.end()); | ||||||
|  | 	// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to | ||||||
|  | 	// request a close_output while the context is active. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void *MultiMachine::raw_pointer() { | ||||||
|  | 	return nullptr; | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Analyser/Dynamic/MultiMachine/MultiMachine.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // | ||||||
|  | //  MultiMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 28/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiMachine_hpp | ||||||
|  | #define MultiMachine_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "Implementation/MultiConfigurable.hpp" | ||||||
|  | #include "Implementation/MultiConfigurationTarget.hpp" | ||||||
|  | #include "Implementation/MultiCRTMachine.hpp" | ||||||
|  | #include "Implementation/MultiJoystickMachine.hpp" | ||||||
|  | #include "Implementation/MultiKeyboardMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides the same interface as to a single machine, while multiplexing all | ||||||
|  | 	underlying calls to an array of real dynamic machines. | ||||||
|  |  | ||||||
|  | 	Calls to crt_machine->get_crt will return that for the frontmost machine; | ||||||
|  | 	anything installed as the speaker's delegate will similarly receive | ||||||
|  | 	feedback only from that machine. | ||||||
|  |  | ||||||
|  | 	Following each crt_machine->run_for, reorders the supplied machines by | ||||||
|  | 	confidence. | ||||||
|  |  | ||||||
|  | 	If confidence for any machine becomes disproportionately low compared to | ||||||
|  | 	the others in the set, that machine stops running. | ||||||
|  | */ | ||||||
|  | class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate { | ||||||
|  | 	public: | ||||||
|  | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
|  | 		ConfigurationTarget::Machine *configuration_target() override; | ||||||
|  | 		CRTMachine::Machine *crt_machine() override; | ||||||
|  | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
|  | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
|  | 		Configurable::Device *configurable_device() override; | ||||||
|  | 		void *raw_pointer() override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void multi_crt_did_run_machines() override; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<DynamicMachine>> machines_; | ||||||
|  | 		std::mutex machines_mutex_; | ||||||
|  |  | ||||||
|  | 		MultiConfigurable configurable_; | ||||||
|  | 		MultiConfigurationTarget configuration_target_; | ||||||
|  | 		MultiCRTMachine crt_machine_; | ||||||
|  | 		MultiJoystickMachine joystick_machine_; | ||||||
|  | 		MultiKeyboardMachine keyboard_machine_; | ||||||
|  |  | ||||||
|  | 		void pick_first(); | ||||||
|  | 		bool has_picked_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiMachine_hpp */ | ||||||
							
								
								
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Analyser/Machines.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | //  Machines.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 24/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Machines_h | ||||||
|  | #define Machines_h | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  |  | ||||||
|  | enum class Machine { | ||||||
|  | 	AmstradCPC, | ||||||
|  | 	Atari2600, | ||||||
|  | 	ColecoVision, | ||||||
|  | 	Electron, | ||||||
|  | 	MSX, | ||||||
|  | 	Oric, | ||||||
|  | 	Vic20, | ||||||
|  | 	ZX8081 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Machines_h */ | ||||||
| @@ -7,14 +7,16 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "../../Storage/Disk/Controller/DiskController.hpp" | 
 | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include "../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
| #include "../../NumberTheory/CRC.hpp" | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
|  | #include "../../../NumberTheory/CRC.hpp" | ||||||
|  | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | 	// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
 | ||||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||||
| 	Storage::Encodings::MFM::Parser parser(false, disk); | 	Storage::Encodings::MFM::Parser parser(false, disk); | ||||||
| @@ -41,9 +43,7 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | |||||||
| 		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break; | 		case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT;	break; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// DFS files are stored contiguously, and listed in descending order of distance from track 0.
 | 	for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) { | ||||||
| 	// So iterating backwards implies the least amount of seeking.
 |  | ||||||
| 	for(std::size_t file_offset = final_file_offset - 8; file_offset > 0; file_offset -= 8) { |  | ||||||
| 		File new_file; | 		File new_file; | ||||||
| 		char name[10]; | 		char name[10]; | ||||||
| 		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); | 		snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]); | ||||||
| @@ -69,12 +69,12 @@ std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetDFSCatalogue(const std::sha | |||||||
| 			new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector); | 			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; | 			data_length -= length_from_sector; | ||||||
| 		} | 		} | ||||||
| 		if(!data_length) catalogue->files.push_front(new_file); | 		if(!data_length) catalogue->files.push_back(new_file); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return catalogue; | 	return catalogue; | ||||||
| } | } | ||||||
| std::unique_ptr<Catalogue> StaticAnalyser::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | 	std::unique_ptr<Catalogue> catalogue(new Catalogue); | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 
 | 
 | ||||||
| @@ -10,15 +10,16 @@ | |||||||
| #define StaticAnalyser_Acorn_Disk_hpp | #define StaticAnalyser_Acorn_Disk_hpp | ||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| /// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
 | /// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
 | ||||||
| struct Catalogue { | struct Catalogue { | ||||||
| 	std::string name; | 	std::string name; | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	enum class BootOption { | 	enum class BootOption { | ||||||
| 		None, | 		None, | ||||||
| 		LoadBOOT, | 		LoadBOOT, | ||||||
| @@ -30,6 +31,7 @@ struct Catalogue { | |||||||
| std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::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); | std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -9,12 +9,12 @@ | |||||||
| #ifndef StaticAnalyser_Acorn_File_hpp | #ifndef StaticAnalyser_Acorn_File_hpp | ||||||
| #define StaticAnalyser_Acorn_File_hpp | #define StaticAnalyser_Acorn_File_hpp | ||||||
| 
 | 
 | ||||||
| #include <list> |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -38,9 +38,10 @@ struct File { | |||||||
| 		std::vector<uint8_t> data; | 		std::vector<uint8_t> data; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	std::list<Chunk> chunks; | 	std::vector<Chunk> chunks; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -11,11 +11,11 @@ | |||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
| 		AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | 		AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
| 	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges; | ||||||
| 
 | 
 | ||||||
| 	for(const auto &cartridge : cartridges) { | 	for(const auto &cartridge : cartridges) { | ||||||
| 		const auto &segments = cartridge->get_segments(); | 		const auto &segments = cartridge->get_segments(); | ||||||
| @@ -23,9 +23,9 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 		// only one mapped item is allowed
 | 		// only one mapped item is allowed
 | ||||||
| 		if(segments.size() != 1) continue; | 		if(segments.size() != 1) continue; | ||||||
| 
 | 
 | ||||||
| 		// which must be 16 kb in size
 | 		// which must be 8 or 16 kb in size
 | ||||||
| 		Storage::Cartridge::Cartridge::Segment segment = segments.front(); | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 		if(segment.data.size() != 0x4000) continue; | 		if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue; | ||||||
| 
 | 
 | ||||||
| 		// is a copyright string present?
 | 		// is a copyright string present?
 | ||||||
| 		uint8_t copyright_offset = segment.data[7]; | 		uint8_t copyright_offset = segment.data[7]; | ||||||
| @@ -56,21 +56,21 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return acorn_cartridges; | 	return acorn_cartridges; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Electron; | 	target->machine = Machine::Electron; | ||||||
| 	target.probability = 1.0; // TODO: a proper estimation
 | 	target->confidence = 1.0; // TODO: a proper estimation
 | ||||||
| 	target.acorn.has_dfs = false; | 	target->acorn.has_dfs = false; | ||||||
| 	target.acorn.has_adfs = false; | 	target->acorn.has_adfs = false; | ||||||
| 	target.acorn.should_shift_restart = false; | 	target->acorn.should_shift_restart = false; | ||||||
| 
 | 
 | ||||||
| 	// strip out inappropriate cartridges
 | 	// strip out inappropriate cartridges
 | ||||||
| 	target.media.cartridges = AcornCartridgesFrom(media.cartridges); | 	target->media.cartridges = AcornCartridgesFrom(media.cartridges); | ||||||
| 
 | 
 | ||||||
| 	// if there are any tapes, attempt to get data from the first
 | 	// if there are any tapes, attempt to get data from the first
 | ||||||
| 	if(media.tapes.size() > 0) { | 	if(media.tapes.size() > 0) { | ||||||
| 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | ||||||
| 		std::list<File> files = GetFiles(tape); | 		std::vector<File> files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 
 | 
 | ||||||
| 		// continue if there are any files
 | 		// continue if there are any files
 | ||||||
| @@ -96,9 +96,9 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 
 | 
 | ||||||
| 			// Inspect first file. If it's protected or doesn't look like BASIC
 | 			// Inspect first file. If it's protected or doesn't look like BASIC
 | ||||||
| 			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | 			// then the loading command is *RUN. Otherwise it's CHAIN"".
 | ||||||
| 			target.loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n"; | 			target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n"; | ||||||
| 
 | 
 | ||||||
| 			target.media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -108,18 +108,18 @@ void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | 		dfs_catalogue = GetDFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | 		if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); | ||||||
| 		if(dfs_catalogue || adfs_catalogue) { | 		if(dfs_catalogue || adfs_catalogue) { | ||||||
| 			target.media.disks = media.disks; | 			target->media.disks = media.disks; | ||||||
| 			target.acorn.has_dfs = !!dfs_catalogue; | 			target->acorn.has_dfs = !!dfs_catalogue; | ||||||
| 			target.acorn.has_adfs = !!adfs_catalogue; | 			target->acorn.has_adfs = !!adfs_catalogue; | ||||||
| 
 | 
 | ||||||
| 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | ||||||
| 			if(bootOption != Catalogue::BootOption::None) | 			if(bootOption != Catalogue::BootOption::None) | ||||||
| 				target.acorn.should_shift_restart = true; | 				target->acorn.should_shift_restart = true; | ||||||
| 			else | 			else | ||||||
| 				target.loading_command = "*CAT\n"; | 				target->loading_command = "*CAT\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | ||||||
| 		destination.push_back(target); | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -9,10 +9,11 @@ | |||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include <deque> | #include <deque> | ||||||
| #include "../../NumberTheory/CRC.hpp" |  | ||||||
| #include "../../Storage/Tape/Parsers/Acorn.hpp" |  | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Acorn; | #include "../../../NumberTheory/CRC.hpp" | ||||||
|  | #include "../../../Storage/Tape/Parsers/Acorn.hpp" | ||||||
|  | 
 | ||||||
|  | using namespace Analyser::Static::Acorn; | ||||||
| 
 | 
 | ||||||
| static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) { | static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) { | ||||||
| 	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk); | 	std::unique_ptr<File::Chunk> new_chunk(new File::Chunk); | ||||||
| @@ -118,7 +119,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) { | |||||||
| 	return file; | 	return file; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	Storage::Tape::Acorn::Parser parser; | 	Storage::Tape::Acorn::Parser parser; | ||||||
| 
 | 
 | ||||||
| 	// populate chunk list
 | 	// populate chunk list
 | ||||||
| @@ -131,7 +132,7 @@ std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::T | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// decompose into file list
 | 	// decompose into file list
 | ||||||
| 	std::list<File> file_list; | 	std::vector<File> file_list; | ||||||
| 
 | 
 | ||||||
| 	while(chunk_list.size()) { | 	while(chunk_list.size()) { | ||||||
| 		std::unique_ptr<File> next_file = GetNextFile(chunk_list); | 		std::unique_ptr<File> next_file = GetNextFile(chunk_list); | ||||||
| @@ -12,13 +12,15 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Acorn { | namespace Acorn { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -11,8 +11,8 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstring> | #include <cstring> | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Disk/Parsers/CPM.hpp" | #include "../../../Storage/Disk/Parsers/CPM.hpp" | ||||||
| #include "../../Storage/Disk/Encodings/MFM/Parser.hpp" | #include "../../../Storage/Disk/Encodings/MFM/Parser.hpp" | ||||||
| 
 | 
 | ||||||
| static bool strcmp_insensitive(const char *a, const char *b) { | static bool strcmp_insensitive(const char *a, const char *b) { | ||||||
| 	if(std::strlen(a) != std::strlen(b)) return false; | 	if(std::strlen(a) != std::strlen(b)) return false; | ||||||
| @@ -58,7 +58,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) { | |||||||
| 
 | 
 | ||||||
| static void InspectCatalogue( | static void InspectCatalogue( | ||||||
| 	const Storage::Disk::CPM::Catalogue &catalogue, | 	const Storage::Disk::CPM::Catalogue &catalogue, | ||||||
| 	StaticAnalyser::Target &target) { | 	const std::unique_ptr<Analyser::Static::Target> &target) { | ||||||
| 
 | 
 | ||||||
| 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | 	std::vector<const Storage::Disk::CPM::File *> candidate_files; | ||||||
| 	candidate_files.reserve(catalogue.files.size()); | 	candidate_files.reserve(catalogue.files.size()); | ||||||
| @@ -95,7 +95,7 @@ static void InspectCatalogue( | |||||||
| 
 | 
 | ||||||
| 	// If there's just one file, run that.
 | 	// If there's just one file, run that.
 | ||||||
| 	if(candidate_files.size() == 1) { | 	if(candidate_files.size() == 1) { | ||||||
| 		target.loading_command = RunCommandFor(*candidate_files[0]); | 		target->loading_command = RunCommandFor(*candidate_files[0]); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -126,7 +126,7 @@ static void InspectCatalogue( | |||||||
| 	} | 	} | ||||||
| 	if(basic_files == 1 || implicit_suffixed_files == 1) { | 	if(basic_files == 1 || implicit_suffixed_files == 1) { | ||||||
| 		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file; | 		std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file; | ||||||
| 		target.loading_command = RunCommandFor(*candidate_files[selected_file]); | 		target->loading_command = RunCommandFor(*candidate_files[selected_file]); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -143,17 +143,17 @@ static void InspectCatalogue( | |||||||
| 	if(name_counts.size() == 2) { | 	if(name_counts.size() == 2) { | ||||||
| 		for(auto &pair : name_counts) { | 		for(auto &pair : name_counts) { | ||||||
| 			if(pair.second == 1) { | 			if(pair.second == 1) { | ||||||
| 				target.loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | 				target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Desperation.
 | 	// Desperation.
 | ||||||
| 	target.loading_command = "cat\n"; | 	target->loading_command = "cat\n"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, StaticAnalyser::Target &target) { | static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::Target> &target) { | ||||||
| 	Storage::Encodings::MFM::Parser parser(true, disk); | 	Storage::Encodings::MFM::Parser parser(true, disk); | ||||||
| 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | 	Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41); | ||||||
| 	if(boot_sector != nullptr && !boot_sector->samples.empty()) { | 	if(boot_sector != nullptr && !boot_sector->samples.empty()) { | ||||||
| @@ -169,7 +169,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St | |||||||
| 
 | 
 | ||||||
| 		// This is a system disk, then launch it as though it were CP/M.
 | 		// This is a system disk, then launch it as though it were CP/M.
 | ||||||
| 		if(!matched) { | 		if(!matched) { | ||||||
| 			target.loading_command = "|cpm\n"; | 			target->loading_command = "|cpm\n"; | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -177,24 +177,24 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, St | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::AmstradCPC; | 	target->machine = Machine::AmstradCPC; | ||||||
| 	target.probability = 1.0; | 	target->confidence = 1.0; | ||||||
| 	target.media.disks = media.disks; | 	target->media.disks = media.disks; | ||||||
| 	target.media.tapes = media.tapes; | 	target->media.tapes = media.tapes; | ||||||
| 	target.media.cartridges = media.cartridges; | 	target->media.cartridges = media.cartridges; | ||||||
| 
 | 
 | ||||||
| 	target.amstradcpc.model = AmstradCPCModel::CPC6128; | 	target->amstradcpc.model = AmstradCPCModel::CPC6128; | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.tapes.empty()) { | 	if(!target->media.tapes.empty()) { | ||||||
| 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | 		// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
 | ||||||
| 		// enter and responding to the follow-on prompt to press a key, so just type for
 | 		// enter and responding to the follow-on prompt to press a key, so just type for
 | ||||||
| 		// a while. Yuck!
 | 		// a while. Yuck!
 | ||||||
| 		target.loading_command = "|tape\nrun\"\n1234567890"; | 		target->loading_command = "|tape\nrun\"\n1234567890"; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.disks.empty()) { | 	if(!target->media.disks.empty()) { | ||||||
| 		Storage::Disk::CPM::ParameterBlock data_format; | 		Storage::Disk::CPM::ParameterBlock data_format; | ||||||
| 		data_format.sectors_per_track = 9; | 		data_format.sectors_per_track = 9; | ||||||
| 		data_format.tracks = 40; | 		data_format.tracks = 40; | ||||||
| @@ -203,11 +203,11 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target | |||||||
| 		data_format.catalogue_allocation_bitmap = 0xc000; | 		data_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 		data_format.reserved_tracks = 0; | 		data_format.reserved_tracks = 0; | ||||||
| 
 | 
 | ||||||
| 		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), data_format); | 		std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), data_format); | ||||||
| 		if(data_catalogue) { | 		if(data_catalogue) { | ||||||
| 			InspectCatalogue(*data_catalogue, target); | 			InspectCatalogue(*data_catalogue, target); | ||||||
| 		} else { | 		} else { | ||||||
| 			if(!CheckBootSector(target.media.disks.front(), target)) { | 			if(!CheckBootSector(target->media.disks.front(), target)) { | ||||||
| 				Storage::Disk::CPM::ParameterBlock system_format; | 				Storage::Disk::CPM::ParameterBlock system_format; | ||||||
| 				system_format.sectors_per_track = 9; | 				system_format.sectors_per_track = 9; | ||||||
| 				system_format.tracks = 40; | 				system_format.tracks = 40; | ||||||
| @@ -216,7 +216,7 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target | |||||||
| 				system_format.catalogue_allocation_bitmap = 0xc000; | 				system_format.catalogue_allocation_bitmap = 0xc000; | ||||||
| 				system_format.reserved_tracks = 2; | 				system_format.reserved_tracks = 2; | ||||||
| 
 | 
 | ||||||
| 				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target.media.disks.front(), system_format); | 				std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(target->media.disks.front(), system_format); | ||||||
| 				if(system_catalogue) { | 				if(system_catalogue) { | ||||||
| 					InspectCatalogue(*system_catalogue, target); | 					InspectCatalogue(*system_catalogue, target); | ||||||
| 				} | 				} | ||||||
| @@ -224,5 +224,5 @@ void StaticAnalyser::AmstradCPC::AddTargets(const Media &media, std::list<Target | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	destination.push_back(target); | 	destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace AmstradCPC { | namespace AmstradCPC { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,9 +10,9 @@ | |||||||
| 
 | 
 | ||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Atari; | using namespace Analyser::Static::Atari; | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingFor2kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | 	// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
 | ||||||
| 	uint16_t entry_address, break_address; | 	uint16_t entry_address, break_address; | ||||||
| 
 | 
 | ||||||
| @@ -26,17 +26,17 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		address &= 0x1fff; | 		address &= 0x1fff; | ||||||
| 		return static_cast<std::size_t>(address - 0x1800); | 		return static_cast<std::size_t>(address - 0x1800); | ||||||
| 	}; | 	}; | ||||||
| 	StaticAnalyser::MOS6502::Disassembly high_location_disassembly = | 	Analyser::Static::MOS6502::Disassembly high_location_disassembly = | ||||||
| 		StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address}); | 		Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address}); | ||||||
| 
 | 
 | ||||||
| 	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | 	// assume that any kind of store that looks likely to be intended for large amounts of memory implies
 | ||||||
| 	// large amounts of memory
 | 	// large amounts of memory
 | ||||||
| 	bool has_wide_area_store = false; | 	bool has_wide_area_store = false; | ||||||
| 	for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) { | 	for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) { | ||||||
| 		if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA) { | 		if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) { | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect; | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX; | ||||||
| 			has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY; | 			has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY; | ||||||
| 
 | 
 | ||||||
| 			if(has_wide_area_store) break; | 			if(has_wide_area_store) break; | ||||||
| 		} | 		} | ||||||
| @@ -46,10 +46,10 @@ static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const | |||||||
| 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | 	// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
 | ||||||
| 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | 	// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
 | ||||||
| 	// attempts to modify itself but it probably doesn't
 | 	// attempts to modify itself but it probably doesn't
 | ||||||
| 	if(has_wide_area_store) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid; | 	if(has_wide_area_store) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CommaVid; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor8kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// Activision stack titles have their vectors at the top of the low 4k, not the top, and
 | 	// 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
 | 	// 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?)
 | 	// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
 | ||||||
| @@ -58,12 +58,12 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | 		(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && | ||||||
| 		segment.data[0] == 0x78 | 		segment.data[0] == 0x78 | ||||||
| 	) { | 	) { | ||||||
| 		target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ActivisionStack; | 		target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ActivisionStack; | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// make an assumption that this is the Atari paging model
 | 	// make an assumption that this is the Atari paging model
 | ||||||
| 	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k; | 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari8k; | ||||||
| 
 | 
 | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -83,13 +83,13 @@ static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		tigervision_access_count += masked_address == 0x3f; | 		tigervision_access_count += masked_address == 0x3f; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros; | 	if(parker_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::ParkerBros; | ||||||
| 	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision; | 	else if(tigervision_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor16kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is the Atari paging model
 | 	// make an assumption that this is the Atari paging model
 | ||||||
| 	target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k; | 	target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari16k; | ||||||
| 
 | 
 | ||||||
| 	std::set<uint16_t> internal_accesses; | 	std::set<uint16_t> internal_accesses; | ||||||
| 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | 	internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); | ||||||
| @@ -104,17 +104,17 @@ static void DeterminePagingFor16kCartridge(StaticAnalyser::Target &target, const | |||||||
| 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | 		mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::MNetwork; | 	if(mnetwork_access_count > atari_access_count) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::MNetwork; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingFor64kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static void DeterminePagingFor64kCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	// make an assumption that this is a Tigervision if there is a write to 3F
 | 	// make an assumption that this is a Tigervision if there is a write to 3F
 | ||||||
| 	target.atari.paging_model = | 	target.atari.paging_model = | ||||||
| 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | 		(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? | ||||||
| 			StaticAnalyser::Atari2600PagingModel::Tigervision : StaticAnalyser::Atari2600PagingModel::MegaBoy; | 			Analyser::Static::Atari2600PagingModel::Tigervision : Analyser::Static::Atari2600PagingModel::MegaBoy; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | static void DeterminePagingForCartridge(Analyser::Static::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { | ||||||
| 	if(segment.data.size() == 2048) { | 	if(segment.data.size() == 2048) { | ||||||
| 		DeterminePagingFor2kCartridge(target, segment); | 		DeterminePagingFor2kCartridge(target, segment); | ||||||
| 		return; | 		return; | ||||||
| @@ -131,23 +131,23 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St | |||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | 	std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end()); | ||||||
| 	StaticAnalyser::MOS6502::Disassembly disassembly = StaticAnalyser::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address}); | 	Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address}); | ||||||
| 
 | 
 | ||||||
| 	switch(segment.data.size()) { | 	switch(segment.data.size()) { | ||||||
| 		case 8192: | 		case 8192: | ||||||
| 			DeterminePagingFor8kCartridge(target, segment, disassembly); | 			DeterminePagingFor8kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 10495: | 		case 10495: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Pitfall2; | 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Pitfall2; | ||||||
| 		break; | 		break; | ||||||
| 		case 12288: | 		case 12288: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus; | 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::CBSRamPlus; | ||||||
| 		break; | 		break; | ||||||
| 		case 16384: | 		case 16384: | ||||||
| 			DeterminePagingFor16kCartridge(target, segment, disassembly); | 			DeterminePagingFor16kCartridge(target, segment, disassembly); | ||||||
| 		break; | 		break; | ||||||
| 		case 32768: | 		case 32768: | ||||||
| 			target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k; | 			target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Atari32k; | ||||||
| 		break; | 		break; | ||||||
| 		case 65536: | 		case 65536: | ||||||
| 			DeterminePagingFor64kCartridge(target, segment, disassembly); | 			DeterminePagingFor64kCartridge(target, segment, disassembly); | ||||||
| @@ -159,8 +159,8 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St | |||||||
| 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | 	// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
 | ||||||
| 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | 	// regions; when they don't they at least seem to have the first 128 bytes be the same as the
 | ||||||
| 	// next 128 bytes. So check for that.
 | 	// next 128 bytes. So check for that.
 | ||||||
| 	if(	target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus && | 	if(	target.atari.paging_model != Analyser::Static::Atari2600PagingModel::CBSRamPlus && | ||||||
| 		target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork) { | 		target.atari.paging_model != Analyser::Static::Atari2600PagingModel::MNetwork) { | ||||||
| 		bool has_superchip = true; | 		bool has_superchip = true; | ||||||
| 		for(std::size_t address = 0; address < 128; address++) { | 		for(std::size_t address = 0; address < 128; address++) { | ||||||
| 			if(segment.data[address] != segment.data[address+128]) { | 			if(segment.data[address] != segment.data[address+128]) { | ||||||
| @@ -172,20 +172,20 @@ static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const St | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check for a Tigervision or Tigervision-esque scheme
 | 	// check for a Tigervision or Tigervision-esque scheme
 | ||||||
| 	if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096) { | 	if(target.atari.paging_model == Analyser::Static::Atari2600PagingModel::None && segment.data.size() > 4096) { | ||||||
| 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | 		bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); | ||||||
| 		if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision; | 		if(looks_like_tigervision) target.atari.paging_model = Analyser::Static::Atari2600PagingModel::Tigervision; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	// TODO: sanity checking; is this image really for an Atari 2600.
 | 	// TODO: sanity checking; is this image really for an Atari 2600.
 | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Atari2600; | 	target->machine = Machine::Atari2600; | ||||||
| 	target.probability = 1.0; | 	target->confidence = 1.0; | ||||||
| 	target.media.cartridges = media.cartridges; | 	target->media.cartridges = media.cartridges; | ||||||
| 	target.atari.paging_model = Atari2600PagingModel::None; | 	target->atari.paging_model = Atari2600PagingModel::None; | ||||||
| 	target.atari.uses_superchip = false; | 	target->atari.uses_superchip = false; | ||||||
| 
 | 
 | ||||||
| 	// try to figure out the paging scheme
 | 	// try to figure out the paging scheme
 | ||||||
| 	if(!media.cartridges.empty()) { | 	if(!media.cartridges.empty()) { | ||||||
| @@ -193,9 +193,9 @@ void StaticAnalyser::Atari::AddTargets(const Media &media, std::list<Target> &de | |||||||
| 
 | 
 | ||||||
| 		if(segments.size() == 1) { | 		if(segments.size() == 1) { | ||||||
| 			const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | 			const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 			DeterminePagingForCartridge(target, segment); | 			DeterminePagingForCartridge(*target, segment); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	destination.push_back(target); | 	destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Atari { | namespace Atari { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Analyser/Static/Coleco/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
|  | 		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) { | ||||||
|  | 		const auto &segments = cartridge->get_segments(); | ||||||
|  |  | ||||||
|  | 		// only one mapped item is allowed | ||||||
|  | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
|  | 		// which must be 8, 12, 16, 24 or 32 kb in size | ||||||
|  | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
|  | 		const std::size_t data_size = segment.data.size(); | ||||||
|  | 		const std::size_t overflow = data_size&8191; | ||||||
|  | 		if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue; | ||||||
|  | 		if(data_size < 8192) continue; | ||||||
|  |  | ||||||
|  | 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||||
|  | 		auto *start = &segment.data[0]; | ||||||
|  | 		if((data_size & static_cast<std::size_t>(~8191)) > 32768) { | ||||||
|  | 			start = &segment.data[segment.data.size() - 16384]; | ||||||
|  | 		} | ||||||
|  | 		if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue; | ||||||
|  | 		if(start[0] == start[1]) continue; | ||||||
|  |  | ||||||
|  | 		// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. | ||||||
|  | 		if(!overflow) { | ||||||
|  | 			coleco_cartridges.push_back(cartridge); | ||||||
|  | 		} else { | ||||||
|  | 			// Size down to a multiple of 8kb and apply the start address. | ||||||
|  | 			std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  |  | ||||||
|  | 			std::vector<uint8_t> truncated_data; | ||||||
|  | 			std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191; | ||||||
|  | 			truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | ||||||
|  | 			output_segments.emplace_back(0x8000, truncated_data); | ||||||
|  |  | ||||||
|  | 			coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return coleco_cartridges; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
|  | 	std::unique_ptr<Target> target(new Target); | ||||||
|  | 	target->machine = Machine::ColecoVision; | ||||||
|  | 	target->confidence = 0.5; | ||||||
|  | 	target->media.cartridges = ColecoCartridgesFrom(media.cartridges); | ||||||
|  | 	if(!target->media.empty()) | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Analyser/Static/Coleco/StaticAnalyser.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
|  | #define StaticAnalyser_Coleco_StaticAnalyser_hpp | ||||||
|  |  | ||||||
|  | #include "../StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace Coleco { | ||||||
|  |  | ||||||
|  | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -7,15 +7,15 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Disk.hpp" | #include "Disk.hpp" | ||||||
| #include "../../Storage/Disk/Controller/DiskController.hpp" | #include "../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
| #include "../../Storage/Disk/Encodings/CommodoreGCR.hpp" | #include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp" | ||||||
| #include "../../Storage/Data/Commodore.hpp" | #include "../../../Storage/Data/Commodore.hpp" | ||||||
| 
 | 
 | ||||||
| #include <limits> | #include <limits> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <array> | #include <array> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| class CommodoreGCRParser: public Storage::Disk::Controller { | class CommodoreGCRParser: public Storage::Disk::Controller { | ||||||
| 	public: | 	public: | ||||||
| @@ -165,8 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller { | |||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) { | std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) { | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	CommodoreGCRParser parser; | 	CommodoreGCRParser parser; | ||||||
| 	parser.drive->set_disk(disk); | 	parser.drive->set_disk(disk); | ||||||
| 
 | 
 | ||||||
| @@ -9,17 +9,19 @@ | |||||||
| #ifndef StaticAnalyser_Commodore_Disk_hpp | #ifndef StaticAnalyser_Commodore_Disk_hpp | ||||||
| #define StaticAnalyser_Commodore_Disk_hpp | #define StaticAnalyser_Commodore_Disk_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include <list> |  | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | #include <vector> | ||||||
|  | 
 | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | } | ||||||
| 
 | 
 | ||||||
| #endif /* Disk_hpp */ | #endif /* Disk_hpp */ | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| 
 | 
 | ||||||
| bool StaticAnalyser::Commodore::File::is_basic() { | bool Analyser::Static::Commodore::File::is_basic() { | ||||||
| 	// BASIC files are always relocatable (?)
 | 	// BASIC files are always relocatable (?)
 | ||||||
| 	if(type != File::RelocatableProgram) return false; | 	if(type != File::RelocatableProgram) return false; | ||||||
| 
 | 
 | ||||||
| @@ -12,7 +12,8 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -34,6 +35,7 @@ struct File { | |||||||
| 	bool is_basic(); | 	bool is_basic(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,18 +8,18 @@ | |||||||
| 
 | 
 | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
|  | #include "Disk.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| #include "Disk.hpp" | #include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp" | ||||||
| #include "../../Storage/Cartridge/Encodings/CommodoreROM.hpp" |  | ||||||
| 
 | 
 | ||||||
| #include <sstream> | #include <sstream> | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | ||||||
| 		Vic20CartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | 		Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
| 	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges; | ||||||
| 
 | 
 | ||||||
| 	for(const auto &cartridge : cartridges) { | 	for(const auto &cartridge : cartridges) { | ||||||
| 		const auto &segments = cartridge->get_segments(); | 		const auto &segments = cartridge->get_segments(); | ||||||
| @@ -38,42 +38,42 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 	return vic20_cartridges; | 	return vic20_cartridges; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Vic20;	// TODO: machine estimation
 | 	target->machine = Machine::Vic20;	// TODO: machine estimation
 | ||||||
| 	target.probability = 1.0; // TODO: a proper estimation
 | 	target->confidence = 1.0; // TODO: a proper estimation
 | ||||||
| 
 | 
 | ||||||
| 	int device = 0; | 	int device = 0; | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	bool is_disk = false; | 	bool is_disk = false; | ||||||
| 
 | 
 | ||||||
| 	// strip out inappropriate cartridges
 | 	// strip out inappropriate cartridges
 | ||||||
| 	target.media.cartridges = Vic20CartridgesFrom(media.cartridges); | 	target->media.cartridges = Vic20CartridgesFrom(media.cartridges); | ||||||
| 
 | 
 | ||||||
| 	// check disks
 | 	// check disks
 | ||||||
| 	for(auto &disk : media.disks) { | 	for(auto &disk : media.disks) { | ||||||
| 		std::list<File> disk_files = GetFiles(disk); | 		std::vector<File> disk_files = GetFiles(disk); | ||||||
| 		if(!disk_files.empty()) { | 		if(!disk_files.empty()) { | ||||||
| 			is_disk = true; | 			is_disk = true; | ||||||
| 			files.splice(files.end(), disk_files); | 			files.insert(files.end(), disk_files.begin(), disk_files.end()); | ||||||
| 			target.media.disks.push_back(disk); | 			target->media.disks.push_back(disk); | ||||||
| 			if(!device) device = 8; | 			if(!device) device = 8; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check tapes
 | 	// check tapes
 | ||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::list<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(!tape_files.empty()) { | 		if(!tape_files.empty()) { | ||||||
| 			files.splice(files.end(), tape_files); | 			files.insert(files.end(), tape_files.begin(), tape_files.end()); | ||||||
| 			target.media.tapes.push_back(tape); | 			target->media.tapes.push_back(tape); | ||||||
| 			if(!device) device = 1; | 			if(!device) device = 1; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(!files.empty()) { | 	if(!files.empty()) { | ||||||
| 		target.vic20.memory_model = Vic20MemoryModel::Unexpanded; | 		target->vic20.memory_model = Vic20MemoryModel::Unexpanded; | ||||||
| 		std::ostringstream string_stream; | 		std::ostringstream string_stream; | ||||||
| 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | 		string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ","; | ||||||
|   		if(files.front().is_basic()) { |   		if(files.front().is_basic()) { | ||||||
| @@ -82,17 +82,17 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 			string_stream << "1"; | 			string_stream << "1"; | ||||||
| 		} | 		} | ||||||
| 		string_stream << "\nRUN\n"; | 		string_stream << "\nRUN\n"; | ||||||
| 		target.loading_command = string_stream.str(); | 		target->loading_command = string_stream.str(); | ||||||
| 
 | 
 | ||||||
| 		// make a first guess based on loading address
 | 		// make a first guess based on loading address
 | ||||||
| 		switch(files.front().starting_address) { | 		switch(files.front().starting_address) { | ||||||
| 			case 0x1001: | 			case 0x1001: | ||||||
| 			default: break; | 			default: break; | ||||||
| 			case 0x1201: | 			case 0x1201: | ||||||
| 				target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 				target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | ||||||
| 			break; | 			break; | ||||||
| 			case 0x0401: | 			case 0x0401: | ||||||
| 				target.vic20.memory_model = Vic20MemoryModel::EightKB; | 				target->vic20.memory_model = Vic20MemoryModel::EightKB; | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @@ -108,9 +108,9 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 				// An unexpanded machine has 3583 bytes free for BASIC;
 | 				// An unexpanded machine has 3583 bytes free for BASIC;
 | ||||||
| 				// a 3kb expanded machine has 6655 bytes free.
 | 				// a 3kb expanded machine has 6655 bytes free.
 | ||||||
| 				if(file_size > 6655) | 				if(file_size > 6655) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | ||||||
| 				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583) | 				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::EightKB; | 					target->vic20.memory_model = Vic20MemoryModel::EightKB; | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{*/ | 			{*/ | ||||||
| @@ -129,13 +129,13 @@ void StaticAnalyser::Commodore::AddTargets(const Media &media, std::list<Target> | |||||||
| 				// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
 | 				// 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.
 | 				// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
 | ||||||
| 				if(starting_address + file_size > 0x2000) | 				if(starting_address + file_size > 0x2000) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | ||||||
| 				else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | 				else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400)) | ||||||
| 					target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | 					target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB; | ||||||
| //			}
 | //			}
 | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(!target.media.tapes.empty() || !target.media.cartridges.empty() || !target.media.disks.empty()) | 	if(!target->media.empty()) | ||||||
| 		destination.push_back(target); | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,13 +8,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Parsers/Commodore.hpp" | #include "../../../Storage/Tape/Parsers/Commodore.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Commodore; | using namespace Analyser::Static::Commodore; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	Storage::Tape::Commodore::Parser parser; | 	Storage::Tape::Commodore::Parser parser; | ||||||
| 	std::list<File> file_list; | 	std::vector<File> file_list; | ||||||
| 
 | 
 | ||||||
| 	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape); | 	std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape); | ||||||
| 
 | 
 | ||||||
| @@ -9,15 +9,16 @@ | |||||||
| #ifndef StaticAnalyser_Commodore_Tape_hpp | #ifndef StaticAnalyser_Commodore_Tape_hpp | ||||||
| #define StaticAnalyser_Commodore_Tape_hpp | #define StaticAnalyser_Commodore_Tape_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| #include "File.hpp" | #include "File.hpp" | ||||||
| #include <list> |  | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,10 +10,10 @@ | |||||||
| 
 | 
 | ||||||
| #include "Kernel.hpp" | #include "Kernel.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::MOS6502; | using namespace Analyser::Static::MOS6502; | ||||||
| namespace  { | namespace  { | ||||||
| 
 | 
 | ||||||
| using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | ||||||
| 
 | 
 | ||||||
| struct MOS6502Disassembler { | struct MOS6502Disassembler { | ||||||
| 
 | 
 | ||||||
| @@ -312,9 +312,9 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< | |||||||
| 
 | 
 | ||||||
| }	// end of anonymous namespace
 | }	// end of anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| Disassembly StaticAnalyser::MOS6502::Disassemble( | Disassembly Analyser::Static::MOS6502::Disassemble( | ||||||
| 	const std::vector<uint8_t> &memory, | 	const std::vector<uint8_t> &memory, | ||||||
| 	const std::function<std::size_t(uint16_t)> &address_mapper, | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
| 	std::vector<uint16_t> entry_points) { | 	std::vector<uint16_t> entry_points) { | ||||||
| 	return StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points); | 	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points); | ||||||
| } | } | ||||||
| @@ -16,7 +16,8 @@ | |||||||
| #include <set> | #include <set> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace MOS6502 { | namespace MOS6502 { | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| @@ -95,5 +96,6 @@ Disassembly Disassemble( | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #endif /* Disassembler6502_hpp */ | #endif /* Disassembler6502_hpp */ | ||||||
| @@ -11,7 +11,8 @@ | |||||||
| 
 | 
 | ||||||
| #include <functional> | #include <functional> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Disassembler { | namespace Disassembler { | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| @@ -24,6 +25,7 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -9,7 +9,8 @@ | |||||||
| #ifndef Kernel_hpp | #ifndef Kernel_hpp | ||||||
| #define Kernel_hpp | #define Kernel_hpp | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Disassembly { | namespace Disassembly { | ||||||
| 
 | 
 | ||||||
| template <typename D, typename S> struct PartialDisassembly { | template <typename D, typename S> struct PartialDisassembly { | ||||||
| @@ -44,6 +45,7 @@ template <typename D, typename S, typename Disassembler> D Disassemble( | |||||||
| 	return partial_disassembly.disassembly; | 	return partial_disassembly.disassembly; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,10 +10,10 @@ | |||||||
| 
 | 
 | ||||||
| #include "Kernel.hpp" | #include "Kernel.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Z80; | using namespace Analyser::Static::Z80; | ||||||
| namespace  { | namespace  { | ||||||
| 
 | 
 | ||||||
| using PartialDisassembly = StaticAnalyser::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>; | ||||||
| 
 | 
 | ||||||
| class Accessor { | class Accessor { | ||||||
| 	public: | 	public: | ||||||
| @@ -611,9 +611,9 @@ struct Z80Disassembler { | |||||||
| 
 | 
 | ||||||
| }	// end of anonymous namespace
 | }	// end of anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| Disassembly StaticAnalyser::Z80::Disassemble( | Disassembly Analyser::Static::Z80::Disassemble( | ||||||
| 	const std::vector<uint8_t> &memory, | 	const std::vector<uint8_t> &memory, | ||||||
| 	const std::function<std::size_t(uint16_t)> &address_mapper, | 	const std::function<std::size_t(uint16_t)> &address_mapper, | ||||||
| 	std::vector<uint16_t> entry_points) { | 	std::vector<uint16_t> entry_points) { | ||||||
| 	return StaticAnalyser::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points); | 	return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points); | ||||||
| } | } | ||||||
| @@ -15,7 +15,8 @@ | |||||||
| #include <set> | #include <set> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Z80 { | namespace Z80 { | ||||||
| 
 | 
 | ||||||
| struct Instruction { | struct Instruction { | ||||||
| @@ -84,5 +85,6 @@ Disassembly Disassemble( | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #endif /* StaticAnalyser_Disassembler_Z80_hpp */ | #endif /* StaticAnalyser_Disassembler_Z80_hpp */ | ||||||
							
								
								
									
										40
									
								
								Analyser/Static/MSX/Cartridge.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Analyser/Static/MSX/Cartridge.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // | ||||||
|  | //  Cartridge.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Cartridge_hpp | ||||||
|  | #define Cartridge_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Storage/Cartridge/Cartridge.hpp" | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
|  | namespace MSX { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Extends the base cartridge class by adding a (guess at) the banking scheme. | ||||||
|  | */ | ||||||
|  | struct Cartridge: public ::Storage::Cartridge::Cartridge { | ||||||
|  | 	enum Type { | ||||||
|  | 		None, | ||||||
|  | 		Konami, | ||||||
|  | 		KonamiWithSCC, | ||||||
|  | 		ASCII8kb, | ||||||
|  | 		ASCII16kb, | ||||||
|  | 		FMPac | ||||||
|  | 	}; | ||||||
|  | 	const Type type; | ||||||
|  |  | ||||||
|  | 	Cartridge(const std::vector<Segment> &segments, Type type) : | ||||||
|  | 		Storage::Cartridge::Cartridge(segments), type(type) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Cartridge_hpp */ | ||||||
							
								
								
									
										292
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								Analyser/Static/MSX/StaticAnalyser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | |||||||
|  | // | ||||||
|  | //  StaticAnalyser.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/11/2017. | ||||||
|  | //  Copyright © 2017 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "StaticAnalyser.hpp" | ||||||
|  |  | ||||||
|  | #include "Cartridge.hpp" | ||||||
|  | #include "Tape.hpp" | ||||||
|  | #include "../Disassembler/Z80.hpp" | ||||||
|  | #include "../Disassembler/AddressMapper.hpp" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  |  | ||||||
|  | static std::unique_ptr<Analyser::Static::Target> CartridgeTarget( | ||||||
|  | 	const Storage::Cartridge::Cartridge::Segment &segment, | ||||||
|  | 	uint16_t start_address, | ||||||
|  | 	Analyser::Static::MSX::Cartridge::Type type, | ||||||
|  | 	float confidence) { | ||||||
|  |  | ||||||
|  | 	// Size down to a multiple of 8kb in size and apply the start address. | ||||||
|  | 	std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  | 	if(segment.data.size() & 0x1fff) { | ||||||
|  | 		std::vector<uint8_t> truncated_data; | ||||||
|  | 		std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff; | ||||||
|  | 		truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | ||||||
|  | 		output_segments.emplace_back(start_address, truncated_data); | ||||||
|  | 	} else { | ||||||
|  | 		output_segments.emplace_back(start_address, segment.data); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::unique_ptr<Analyser::Static::Target> target(new Analyser::Static::Target); | ||||||
|  | 	target->machine = Analyser::Machine::MSX; | ||||||
|  | 	target->confidence = confidence; | ||||||
|  |  | ||||||
|  | 	if(type == Analyser::Static::MSX::Cartridge::Type::None) { | ||||||
|  | 		target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
|  | 	} else { | ||||||
|  | 		target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return target; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Expected standard cartridge format: | ||||||
|  |  | ||||||
|  | 		DEFB "AB" ; expansion ROM header | ||||||
|  | 		DEFW initcode ; start of the init code, 0 if no initcode | ||||||
|  | 		DEFW callstat; pointer to CALL statement handler, 0 if no such handler | ||||||
|  | 		DEFW device; pointer to expansion device handler, 0 if no such handler | ||||||
|  | 		DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram | ||||||
|  | 		DEFS 6,0 ; room reserved for future extensions | ||||||
|  |  | ||||||
|  | 	MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file | ||||||
|  | 	format that the MSX community has decided upon doesn't retain the type of hardware included, so | ||||||
|  | 	this analyser has to guess. | ||||||
|  |  | ||||||
|  | 	(additional audio hardware is also sometimes included, but it's implied by the banking hardware) | ||||||
|  | */ | ||||||
|  | static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom( | ||||||
|  | 	const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { | ||||||
|  | 	// No cartridges implies no targets. | ||||||
|  | 	if(cartridges.empty()) { | ||||||
|  | 		return {}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::vector<std::unique_ptr<Analyser::Static::Target>> targets; | ||||||
|  | 	for(const auto &cartridge : cartridges) { | ||||||
|  | 		const auto &segments = cartridge->get_segments(); | ||||||
|  |  | ||||||
|  | 		// Only one mapped item is allowed. | ||||||
|  | 		if(segments.size() != 1) continue; | ||||||
|  |  | ||||||
|  | 		// Which must be no more than 63 bytes larger than a multiple of 8 kb in size. | ||||||
|  | 		Storage::Cartridge::Cartridge::Segment segment = segments.front(); | ||||||
|  | 		const size_t data_size = segment.data.size(); | ||||||
|  | 		if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue; | ||||||
|  |  | ||||||
|  | 		// Check for a ROM header at address 0; if it's not found then try 0x4000 | ||||||
|  | 		// and adjust the start address; | ||||||
|  | 		uint16_t start_address = 0; | ||||||
|  | 		bool found_start = false; | ||||||
|  | 		if(segment.data[0] == 0x41 && segment.data[1] == 0x42) { | ||||||
|  | 			start_address = 0x4000; | ||||||
|  | 			found_start = true; | ||||||
|  | 		} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) { | ||||||
|  | 			start_address = 0; | ||||||
|  | 			found_start = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Reject cartridge if the ROM header wasn't found. | ||||||
|  | 		if(!found_start) continue; | ||||||
|  |  | ||||||
|  | 		uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8)); | ||||||
|  | 		// TODO: check for a rational init address? | ||||||
|  |  | ||||||
|  | 		// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on. | ||||||
|  | 		if(data_size <= 0xc000) { | ||||||
|  | 			targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0)); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must | ||||||
|  | 		// be at play; disassemble to try to figure it out. | ||||||
|  | 		std::vector<uint8_t> first_8k; | ||||||
|  | 		first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192); | ||||||
|  | 		Analyser::Static::Z80::Disassembly disassembly = | ||||||
|  | 			Analyser::Static::Z80::Disassemble( | ||||||
|  | 				first_8k, | ||||||
|  | 				Analyser::Static::Disassembler::OffsetMapper(start_address), | ||||||
|  | 				{ init_address } | ||||||
|  | 			); | ||||||
|  |  | ||||||
|  | //		// Look for a indirect store followed by an unconditional JP or CALL into another | ||||||
|  | //		// segment, that's a fairly explicit sign where found. | ||||||
|  | 		using Instruction = Analyser::Static::Z80::Instruction; | ||||||
|  | 		std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address; | ||||||
|  | 		bool is_ascii = false; | ||||||
|  | //		auto iterator = instructions.begin(); | ||||||
|  | //		while(iterator != instructions.end()) { | ||||||
|  | //			auto next_iterator = iterator; | ||||||
|  | //			next_iterator++; | ||||||
|  | //			if(next_iterator == instructions.end()) break; | ||||||
|  | // | ||||||
|  | //			if(	iterator->second.operation == Instruction::Operation::LD && | ||||||
|  | //				iterator->second.destination == Instruction::Location::Operand_Indirect && | ||||||
|  | //				( | ||||||
|  | //					iterator->second.operand == 0x5000 || | ||||||
|  | //					iterator->second.operand == 0x6000 || | ||||||
|  | //					iterator->second.operand == 0x6800 || | ||||||
|  | //					iterator->second.operand == 0x7000 || | ||||||
|  | //					iterator->second.operand == 0x77ff || | ||||||
|  | //					iterator->second.operand == 0x7800 || | ||||||
|  | //					iterator->second.operand == 0x8000 || | ||||||
|  | //					iterator->second.operand == 0x9000 || | ||||||
|  | //					iterator->second.operand == 0xa000 | ||||||
|  | //				) && | ||||||
|  | //				( | ||||||
|  | //					next_iterator->second.operation == Instruction::Operation::CALL || | ||||||
|  | //					next_iterator->second.operation == Instruction::Operation::JP | ||||||
|  | //				) && | ||||||
|  | //				((next_iterator->second.operand >> 13) != (0x4000 >> 13)) | ||||||
|  | //			) { | ||||||
|  | //				const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand); | ||||||
|  | //				switch(iterator->second.operand) { | ||||||
|  | //					case 0x6000: | ||||||
|  | //						if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x6800: | ||||||
|  | //						if(address >= 0x6000 && address < 0x6800) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x7000: | ||||||
|  | //						if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //						if(address >= 0x7000 && address < 0x7800) { | ||||||
|  | //							is_ascii = true; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x77ff: | ||||||
|  | //						if(address >= 0x7000 && address < 0x7800) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x7800: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x8000: | ||||||
|  | //						if(address >= 0x8000 && address < 0xa000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0x9000: | ||||||
|  | //						if(address >= 0x8000 && address < 0xa000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0xa000: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //					case 0xb000: | ||||||
|  | //						if(address >= 0xa000 && address < 0xc000) { | ||||||
|  | //							target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; | ||||||
|  | //						} | ||||||
|  | //					break; | ||||||
|  | //				} | ||||||
|  | //			} | ||||||
|  | // | ||||||
|  | //			iterator = next_iterator; | ||||||
|  |  | ||||||
|  | 		// Look for LD (nnnn), A instructions, and collate those addresses. | ||||||
|  | 		std::map<uint16_t, int> address_counts; | ||||||
|  | 		for(const auto &instruction_pair : instructions) { | ||||||
|  | 			if(	instruction_pair.second.operation == Instruction::Operation::LD && | ||||||
|  | 				instruction_pair.second.destination == Instruction::Location::Operand_Indirect && | ||||||
|  | 				instruction_pair.second.source == Instruction::Location::A) { | ||||||
|  | 				address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Weight confidences by number of observed hits. | ||||||
|  | 		float total_hits = | ||||||
|  | 			static_cast<float>( | ||||||
|  | 				address_counts[0x6000] + address_counts[0x6800] + | ||||||
|  | 				address_counts[0x7000] + address_counts[0x7800] + | ||||||
|  | 				address_counts[0x77ff] + address_counts[0x8000] + | ||||||
|  | 				address_counts[0xa000] + address_counts[0x5000] + | ||||||
|  | 				address_counts[0x9000] + address_counts[0xb000] | ||||||
|  | 			); | ||||||
|  |  | ||||||
|  | 		targets.push_back(CartridgeTarget( | ||||||
|  | 			segment, | ||||||
|  | 			start_address, | ||||||
|  | 			Analyser::Static::MSX::Cartridge::ASCII8kb, | ||||||
|  | 			static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 								address_counts[0x6800] + | ||||||
|  | 								address_counts[0x7000] + | ||||||
|  | 								address_counts[0x7800]) / total_hits)); | ||||||
|  | 		targets.push_back(CartridgeTarget( | ||||||
|  | 			segment, | ||||||
|  | 			start_address, | ||||||
|  | 			Analyser::Static::MSX::Cartridge::ASCII16kb, | ||||||
|  | 			static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 								address_counts[0x7000] + | ||||||
|  | 								address_counts[0x77ff]) / total_hits)); | ||||||
|  | 		if(!is_ascii) { | ||||||
|  | 			targets.push_back(CartridgeTarget( | ||||||
|  | 				segment, | ||||||
|  | 				start_address, | ||||||
|  | 				Analyser::Static::MSX::Cartridge::Konami, | ||||||
|  | 				static_cast<float>(	address_counts[0x6000] + | ||||||
|  | 									address_counts[0x8000] + | ||||||
|  | 									address_counts[0xa000]) / total_hits)); | ||||||
|  | 		} | ||||||
|  | 		if(!is_ascii) { | ||||||
|  | 			targets.push_back(CartridgeTarget( | ||||||
|  | 				segment, | ||||||
|  | 				start_address, | ||||||
|  | 				Analyser::Static::MSX::Cartridge::KonamiWithSCC, | ||||||
|  | 				static_cast<float>(	address_counts[0x5000] + | ||||||
|  | 									address_counts[0x7000] + | ||||||
|  | 									address_counts[0x9000] + | ||||||
|  | 									address_counts[0xb000]) / total_hits)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return targets; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
|  | 	// Append targets for any cartridges that look correct. | ||||||
|  | 	std::vector<std::unique_ptr<Target>> cartridge_targets = CartridgeTargetsFrom(media.cartridges); | ||||||
|  | 	std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); | ||||||
|  |  | ||||||
|  | 	// Consider building a target for disks and/or tapes. | ||||||
|  | 	std::unique_ptr<Target> target(new Target); | ||||||
|  |  | ||||||
|  | 	// Check tapes for loadable files. | ||||||
|  | 	for(const auto &tape : media.tapes) { | ||||||
|  | 		std::vector<File> files_on_tape = GetFiles(tape); | ||||||
|  | 		if(!files_on_tape.empty()) { | ||||||
|  | 			switch(files_on_tape.front().type) { | ||||||
|  | 				case File::Type::ASCII:				target->loading_command = "RUN\"CAS:\r";		break; | ||||||
|  | 				case File::Type::TokenisedBASIC:	target->loading_command = "CLOAD\rRUN\r";		break; | ||||||
|  | 				case File::Type::Binary:			target->loading_command = "BLOAD\"CAS:\",R\r";	break; | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  | 			target->media.tapes.push_back(tape); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Blindly accept disks for now. | ||||||
|  | 	target->media.disks = media.disks; | ||||||
|  |  | ||||||
|  | 	if(!target->media.empty()) { | ||||||
|  | 		target->machine = Machine::MSX; | ||||||
|  | 		target->confidence = 1.0; | ||||||
|  | 		destination.push_back(std::move(target)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -11,11 +11,13 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace MSX { | namespace MSX { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,9 +8,9 @@ | |||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Parsers/MSX.hpp" | #include "../../../Storage/Tape/Parsers/MSX.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::MSX; | using namespace Analyser::Static::MSX; | ||||||
| 
 | 
 | ||||||
| File::File(File &&rhs) : | File::File(File &&rhs) : | ||||||
| 	name(std::move(rhs.name)), | 	name(std::move(rhs.name)), | ||||||
| @@ -24,7 +24,7 @@ File::File() : | |||||||
| 	starting_address(0), | 	starting_address(0), | ||||||
| 	entry_address(0) {}	// For the sake of initialising in a defined state.
 | 	entry_address(0) {}	// For the sake of initialising in a defined state.
 | ||||||
| 
 | 
 | ||||||
| std::vector<File> StaticAnalyser::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	std::vector<File> files; | 	std::vector<File> files; | ||||||
| 
 | 
 | ||||||
| 	Storage::Tape::BinaryTapePlayer tape_player(1000000); | 	Storage::Tape::BinaryTapePlayer tape_player(1000000); | ||||||
| @@ -9,12 +9,13 @@ | |||||||
| #ifndef StaticAnalyser_MSX_Tape_hpp | #ifndef StaticAnalyser_MSX_Tape_hpp | ||||||
| #define StaticAnalyser_MSX_Tape_hpp | #define StaticAnalyser_MSX_Tape_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| 
 | 
 | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace MSX { | namespace MSX { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -36,6 +37,7 @@ struct File { | |||||||
| 
 | 
 | ||||||
| std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -12,9 +12,9 @@ | |||||||
| #include "../Disassembler/6502.hpp" | #include "../Disassembler/6502.hpp" | ||||||
| #include "../Disassembler/AddressMapper.hpp" | #include "../Disassembler/AddressMapper.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Oric; | using namespace Analyser::Static::Oric; | ||||||
| 
 | 
 | ||||||
| static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) { | ||||||
| 	int score = 0; | 	int score = 0; | ||||||
| 
 | 
 | ||||||
| 	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | 	for(auto address : disassembly.outward_calls)	score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1; | ||||||
| @@ -24,7 +24,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const | |||||||
| 	return score; | 	return score; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	std::set<uint16_t> rom_functions = { | ||||||
| 		0x0228,	0x022b, | 		0x0228,	0x022b, | ||||||
| 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | 		0xc3ca,	0xc3f8,	0xc448,	0xc47c,	0xc4b5,	0xc4e3,	0xc4e0,	0xc524,	0xc56f,	0xc5a2,	0xc5f8,	0xc60a,	0xc6a5,	0xc6de,	0xc719,	0xc738, | ||||||
| @@ -48,7 +48,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) | |||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) { | static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) { | ||||||
| 	std::set<uint16_t> rom_functions = { | 	std::set<uint16_t> rom_functions = { | ||||||
| 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | 		0x0238,	0x023b,	0x023e,	0x0241,	0x0244,	0x0247, | ||||||
| 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | 		0xc3c6,	0xc3f4,	0xc444,	0xc47c,	0xc4a8,	0xc4d3,	0xc4e0,	0xc524,	0xc55f,	0xc592,	0xc5e8,	0xc5fa,	0xc692,	0xc6b3,	0xc6ee,	0xc70d, | ||||||
| @@ -73,23 +73,23 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly) | |||||||
| 	return Score(disassembly, rom_functions, variable_locations); | 	return Score(disassembly, rom_functions, variable_locations); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &destination) { | void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) { | ||||||
| 	Target target; | 	std::unique_ptr<Target> target(new Target); | ||||||
| 	target.machine = Target::Oric; | 	target->machine = Machine::Oric; | ||||||
| 	target.probability = 1.0; | 	target->confidence = 1.0; | ||||||
| 
 | 
 | ||||||
| 	int basic10_votes = 0; | 	int basic10_votes = 0; | ||||||
| 	int basic11_votes = 0; | 	int basic11_votes = 0; | ||||||
| 
 | 
 | ||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::list<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(tape_files.size()) { | 		if(tape_files.size()) { | ||||||
| 			for(auto file : tape_files) { | 			for(auto file : tape_files) { | ||||||
| 				if(file.data_type == File::MachineCode) { | 				if(file.data_type == File::MachineCode) { | ||||||
| 					std::vector<uint16_t> entry_points = {file.starting_address}; | 					std::vector<uint16_t> entry_points = {file.starting_address}; | ||||||
| 					StaticAnalyser::MOS6502::Disassembly disassembly = | 					Analyser::Static::MOS6502::Disassembly disassembly = | ||||||
| 						StaticAnalyser::MOS6502::Disassemble(file.data, StaticAnalyser::Disassembler::OffsetMapper(file.starting_address), entry_points); | 						Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points); | ||||||
| 
 | 
 | ||||||
| 					int basic10_score = Basic10Score(disassembly); | 					int basic10_score = Basic10Score(disassembly); | ||||||
| 					int basic11_score = Basic11Score(disassembly); | 					int basic11_score = Basic11Score(disassembly); | ||||||
| @@ -97,23 +97,23 @@ void StaticAnalyser::Oric::AddTargets(const Media &media, std::list<Target> &des | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			target.media.tapes.push_back(tape); | 			target->media.tapes.push_back(tape); | ||||||
| 			target.loading_command = "CLOAD\"\"\n"; | 			target->loading_command = "CLOAD\"\"\n"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// trust that any disk supplied can be handled by the Microdisc. TODO: check.
 | 	// trust that any disk supplied can be handled by the Microdisc. TODO: check.
 | ||||||
| 	if(!media.disks.empty()) { | 	if(!media.disks.empty()) { | ||||||
| 		target.oric.has_microdisc = true; | 		target->oric.has_microdisc = true; | ||||||
| 		target.media.disks = media.disks; | 		target->media.disks = media.disks; | ||||||
| 	} else { | 	} else { | ||||||
| 		target.oric.has_microdisc = false; | 		target->oric.has_microdisc = false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO: really this should add two targets if not all votes agree
 | 	// TODO: really this should add two targets if not all votes agree
 | ||||||
| 	target.oric.use_atmos_rom = basic11_votes >= basic10_votes; | 	target->oric.use_atmos_rom = basic11_votes >= basic10_votes; | ||||||
| 	if(target.oric.has_microdisc) target.oric.use_atmos_rom = true; | 	if(target->oric.has_microdisc) target->oric.use_atmos_rom = true; | ||||||
| 
 | 
 | ||||||
| 	if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size()) | 	if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) | ||||||
| 		destination.push_back(target); | 		destination.push_back(std::move(target)); | ||||||
| } | } | ||||||
| @@ -11,13 +11,14 @@ | |||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Oric { | namespace Oric { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | } | ||||||
| 
 | 
 | ||||||
| #endif /* StaticAnalyser_hpp */ | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -7,12 +7,12 @@ | |||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "Tape.hpp" | #include "Tape.hpp" | ||||||
| #include "../../Storage/Tape/Parsers/Oric.hpp" | #include "../../../Storage/Tape/Parsers/Oric.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser::Oric; | using namespace Analyser::Static::Oric; | ||||||
| 
 | 
 | ||||||
| std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	std::list<File> files; | 	std::vector<File> files; | ||||||
| 	Storage::Tape::Oric::Parser parser; | 	Storage::Tape::Oric::Parser parser; | ||||||
| 
 | 
 | ||||||
| 	while(!tape->is_at_end()) { | 	while(!tape->is_at_end()) { | ||||||
| @@ -9,12 +9,13 @@ | |||||||
| #ifndef StaticAnalyser_Oric_Tape_hpp | #ifndef StaticAnalyser_Oric_Tape_hpp | ||||||
| #define StaticAnalyser_Oric_Tape_hpp | #define StaticAnalyser_Oric_Tape_hpp | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Tape.hpp" | #include "../../../Storage/Tape/Tape.hpp" | ||||||
| #include <list> | 
 | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace Oric { | namespace Oric { | ||||||
| 
 | 
 | ||||||
| struct File { | struct File { | ||||||
| @@ -30,8 +31,9 @@ struct File { | |||||||
| 	std::vector<uint8_t> data; | 	std::vector<uint8_t> data; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,6 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "StaticAnalyser.hpp" | #include "StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
|  | #include <algorithm> | ||||||
| #include <cstdlib> | #include <cstdlib> | ||||||
| #include <cstring> | #include <cstring> | ||||||
| 
 | 
 | ||||||
| @@ -15,40 +16,41 @@ | |||||||
| #include "Acorn/StaticAnalyser.hpp" | #include "Acorn/StaticAnalyser.hpp" | ||||||
| #include "AmstradCPC/StaticAnalyser.hpp" | #include "AmstradCPC/StaticAnalyser.hpp" | ||||||
| #include "Atari/StaticAnalyser.hpp" | #include "Atari/StaticAnalyser.hpp" | ||||||
|  | #include "Coleco/StaticAnalyser.hpp" | ||||||
| #include "Commodore/StaticAnalyser.hpp" | #include "Commodore/StaticAnalyser.hpp" | ||||||
| #include "MSX/StaticAnalyser.hpp" | #include "MSX/StaticAnalyser.hpp" | ||||||
| #include "Oric/StaticAnalyser.hpp" | #include "Oric/StaticAnalyser.hpp" | ||||||
| #include "ZX8081/StaticAnalyser.hpp" | #include "ZX8081/StaticAnalyser.hpp" | ||||||
| 
 | 
 | ||||||
| // Cartridges
 | // Cartridges
 | ||||||
| #include "../Storage/Cartridge/Formats/BinaryDump.hpp" | #include "../../Storage/Cartridge/Formats/BinaryDump.hpp" | ||||||
| #include "../Storage/Cartridge/Formats/PRG.hpp" | #include "../../Storage/Cartridge/Formats/PRG.hpp" | ||||||
| 
 | 
 | ||||||
| // Disks
 | // Disks
 | ||||||
| #include "../Storage/Disk/DiskImage/Formats/AcornADF.hpp" | #include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/D64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/G64.hpp" | #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/DMK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/HFE.hpp" | #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" | ||||||
| #include "../Storage/Disk/DiskImage/Formats/SSD.hpp" | #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | ||||||
| 
 | 
 | ||||||
| // Tapes
 | // Tapes
 | ||||||
| #include "../Storage/Tape/Formats/CAS.hpp" | #include "../../Storage/Tape/Formats/CAS.hpp" | ||||||
| #include "../Storage/Tape/Formats/CommodoreTAP.hpp" | #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" | ||||||
| #include "../Storage/Tape/Formats/CSW.hpp" | #include "../../Storage/Tape/Formats/CSW.hpp" | ||||||
| #include "../Storage/Tape/Formats/OricTAP.hpp" | #include "../../Storage/Tape/Formats/OricTAP.hpp" | ||||||
| #include "../Storage/Tape/Formats/TapePRG.hpp" | #include "../../Storage/Tape/Formats/TapePRG.hpp" | ||||||
| #include "../Storage/Tape/Formats/TapeUEF.hpp" | #include "../../Storage/Tape/Formats/TapeUEF.hpp" | ||||||
| #include "../Storage/Tape/Formats/TZX.hpp" | #include "../../Storage/Tape/Formats/TZX.hpp" | ||||||
| #include "../Storage/Tape/Formats/ZX80O81P.hpp" | #include "../../Storage/Tape/Formats/ZX80O81P.hpp" | ||||||
| 
 | 
 | ||||||
| // Target Platform Types
 | // Target Platform Types
 | ||||||
| #include "../Storage/TargetPlatforms.hpp" | #include "../../Storage/TargetPlatforms.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace StaticAnalyser; | using namespace Analyser::Static; | ||||||
| 
 | 
 | ||||||
| static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) { | static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType &potential_platforms) { | ||||||
| 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
 | 	// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
 | ||||||
| @@ -88,8 +90,9 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 		Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF
 | 		Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn)		// ADF
 | ||||||
| 		Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// BIN
 | 		Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600)						// BIN
 | ||||||
| 		Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS
 | 		Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX)												// CAS
 | ||||||
| 		Format("cdt", result.tapes, Tape::TZX,	TargetPlatform::AmstradCPC)										// CDT
 | 		Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC)										// CDT
 | ||||||
| 		Format("csw", result.tapes, Tape::CSW,	TargetPlatform::AllTape)										// CSW
 | 		Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision)					// COL
 | ||||||
|  | 		Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape)											// CSW
 | ||||||
| 		Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64
 | 		Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore)		// D64
 | ||||||
| 		Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK
 | 		Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX)				// DMK
 | ||||||
| 		Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD
 | 		Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// DSD
 | ||||||
| @@ -114,7 +117,10 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX)	// ROM
 | 		Format(	"rom", | ||||||
|  | 				result.cartridges, | ||||||
|  | 				Cartridge::BinaryDump, | ||||||
|  | 				TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision)						// ROM
 | ||||||
| 		Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD
 | 		Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)			// SSD
 | ||||||
| 		Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore)								// TAP (Commodore)
 | 		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::OricTAP, TargetPlatform::Oric)										// TAP (Oric)
 | ||||||
| @@ -134,13 +140,13 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Media StaticAnalyser::GetMedia(const char *file_name) { | Media Analyser::Static::GetMedia(const char *file_name) { | ||||||
| 	TargetPlatform::IntType throwaway; | 	TargetPlatform::IntType throwaway; | ||||||
| 	return GetMediaAndPlatforms(file_name, throwaway); | 	return GetMediaAndPlatforms(file_name, throwaway); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::list<Target> StaticAnalyser::GetTargets(const char *file_name) { | std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *file_name) { | ||||||
| 	std::list<Target> targets; | 	std::vector<std::unique_ptr<Target>> targets; | ||||||
| 
 | 
 | ||||||
| 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
 | 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
 | ||||||
| 	// union of all platforms this file might be a target for.
 | 	// union of all platforms this file might be a target for.
 | ||||||
| @@ -152,17 +158,25 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name) { | |||||||
| 	if(potential_platforms & TargetPlatform::Acorn)			Acorn::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Acorn)			Acorn::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::AmstradCPC)	AmstradCPC::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Atari2600)		Atari::AddTargets(media, targets); | ||||||
|  | 	if(potential_platforms & TargetPlatform::ColecoVision)	Coleco::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Commodore)		Commodore::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::MSX)			MSX::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | 	if(potential_platforms & TargetPlatform::Oric)			Oric::AddTargets(media, targets); | ||||||
| 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | 	if(potential_platforms & TargetPlatform::ZX8081)		ZX8081::AddTargets(media, targets, potential_platforms); | ||||||
| 
 | 
 | ||||||
| 	// Reset any tapes to their initial position
 | 	// Reset any tapes to their initial position
 | ||||||
| 	for(auto target : targets) { | 	for(auto &target : targets) { | ||||||
| 		for(auto tape : media.tapes) { | 		for(auto &tape : target->media.tapes) { | ||||||
| 			tape->reset(); | 			tape->reset(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
 | ||||||
|  | 	// picked their insertion order carefully.
 | ||||||
|  | 	std::stable_sort(targets.begin(), targets.end(), | ||||||
|  |         [] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) { | ||||||
|  | 		    return a->confidence > b->confidence; | ||||||
|  | 	    }); | ||||||
|  | 
 | ||||||
| 	return targets; | 	return targets; | ||||||
| } | } | ||||||
| @@ -9,15 +9,17 @@ | |||||||
| #ifndef StaticAnalyser_hpp | #ifndef StaticAnalyser_hpp | ||||||
| #define StaticAnalyser_hpp | #define StaticAnalyser_hpp | ||||||
| 
 | 
 | ||||||
| #include "../Storage/Tape/Tape.hpp" | #include "../Machines.hpp" | ||||||
| #include "../Storage/Disk/Disk.hpp" | 
 | ||||||
| #include "../Storage/Cartridge/Cartridge.hpp" | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  | #include "../../Storage/Disk/Disk.hpp" | ||||||
|  | #include "../../Storage/Cartridge/Cartridge.hpp" | ||||||
| 
 | 
 | ||||||
| #include <string> | #include <string> | ||||||
| #include <list> |  | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| 
 | 
 | ||||||
| enum class Vic20MemoryModel { | enum class Vic20MemoryModel { | ||||||
| 	Unexpanded, | 	Unexpanded, | ||||||
| @@ -40,15 +42,6 @@ enum class Atari2600PagingModel { | |||||||
| 	Pitfall2 | 	Pitfall2 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum class MSXCartridgeType { |  | ||||||
| 	None, |  | ||||||
| 	Konami, |  | ||||||
| 	KonamiWithSCC, |  | ||||||
| 	ASCII8kb, |  | ||||||
| 	ASCII16kb, |  | ||||||
| 	FMPac |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| enum class ZX8081MemoryModel { | enum class ZX8081MemoryModel { | ||||||
| 	Unexpanded, | 	Unexpanded, | ||||||
| 	SixteenKB, | 	SixteenKB, | ||||||
| @@ -65,9 +58,9 @@ enum class AmstradCPCModel { | |||||||
| 	A list of disks, tapes and cartridges. | 	A list of disks, tapes and cartridges. | ||||||
| */ | */ | ||||||
| struct Media { | struct Media { | ||||||
| 	std::list<std::shared_ptr<Storage::Disk::Disk>> disks; | 	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks; | ||||||
| 	std::list<std::shared_ptr<Storage::Tape::Tape>> tapes; | 	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes; | ||||||
| 	std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | ||||||
| 
 | 
 | ||||||
| 	bool empty() const { | 	bool empty() const { | ||||||
| 		return disks.empty() && tapes.empty() && cartridges.empty(); | 		return disks.empty() && tapes.empty() && cartridges.empty(); | ||||||
| @@ -79,16 +72,11 @@ struct Media { | |||||||
| 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | 	and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. | ||||||
| */ | */ | ||||||
| struct Target { | struct Target { | ||||||
| 	enum Machine { | 	Machine machine; | ||||||
| 		AmstradCPC, | 	Media media; | ||||||
| 		Atari2600, | 
 | ||||||
| 		Electron, | 	float confidence; | ||||||
| 		MSX, | 	std::string loading_command; | ||||||
| 		Oric, |  | ||||||
| 		Vic20, |  | ||||||
| 		ZX8081 |  | ||||||
| 	} machine; |  | ||||||
| 	float probability; |  | ||||||
| 
 | 
 | ||||||
| 	// TODO: this is too C-like a solution; make Target a base class and
 | 	// TODO: this is too C-like a solution; make Target a base class and
 | ||||||
| 	// turn the following into information held by more specific subclasses.
 | 	// turn the following into information held by more specific subclasses.
 | ||||||
| @@ -122,14 +110,7 @@ struct Target { | |||||||
| 		struct { | 		struct { | ||||||
| 			AmstradCPCModel model; | 			AmstradCPCModel model; | ||||||
| 		} amstradcpc; | 		} amstradcpc; | ||||||
| 
 |  | ||||||
| 		struct { |  | ||||||
| 			MSXCartridgeType cartridge_type; |  | ||||||
| 		} msx; |  | ||||||
| 	}; | 	}; | ||||||
| 
 |  | ||||||
| 	std::string loading_command; |  | ||||||
| 	Media media; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| @@ -137,13 +118,14 @@ struct Target { | |||||||
| 
 | 
 | ||||||
| 	@returns The list of potential targets, sorted from most to least probable. | 	@returns The list of potential targets, sorted from most to least probable. | ||||||
| */ | */ | ||||||
| std::list<Target> GetTargets(const char *file_name); | std::vector<std::unique_ptr<Target>> GetTargets(const char *file_name); | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| 	Inspects the supplied file and determines the media included. | 	Inspects the supplied file and determines the media included. | ||||||
| */ | */ | ||||||
| Media GetMedia(const char *file_name); | Media GetMedia(const char *file_name); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif /* StaticAnalyser_hpp */ | #endif /* StaticAnalyser_hpp */ | ||||||
| @@ -11,7 +11,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include "../../Storage/Tape/Parsers/ZX8081.hpp" | #include "../../../Storage/Tape/Parsers/ZX8081.hpp" | ||||||
| 
 | 
 | ||||||
| static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) { | ||||||
| 	std::vector<Storage::Data::ZX8081::File> files; | 	std::vector<Storage::Data::ZX8081::File> files; | ||||||
| @@ -27,41 +27,41 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S | |||||||
| 	return files; | 	return files; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StaticAnalyser::ZX8081::AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms) { | void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms) { | ||||||
| 	if(!media.tapes.empty()) { | 	if(!media.tapes.empty()) { | ||||||
| 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | 		std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front()); | ||||||
| 		media.tapes.front()->reset(); | 		media.tapes.front()->reset(); | ||||||
| 		if(!files.empty()) { | 		if(!files.empty()) { | ||||||
| 			StaticAnalyser::Target target; | 			std::unique_ptr<Target> target(new Target); | ||||||
| 			target.machine = Target::ZX8081; | 			target->machine = Machine::ZX8081; | ||||||
| 
 | 
 | ||||||
| 			// Guess the machine type from the file only if it isn't already known.
 | 			// Guess the machine type from the file only if it isn't already known.
 | ||||||
| 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | 			switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) { | ||||||
| 				default: | 				default: | ||||||
| 					target.zx8081.isZX81 = files.front().isZX81; | 					target->zx8081.isZX81 = files.front().isZX81; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 				case TargetPlatform::ZX80:	target.zx8081.isZX81 = false;	break; | 				case TargetPlatform::ZX80:	target->zx8081.isZX81 = false;	break; | ||||||
| 				case TargetPlatform::ZX81:	target.zx8081.isZX81 = true;	break; | 				case TargetPlatform::ZX81:	target->zx8081.isZX81 = true;	break; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			/*if(files.front().data.size() > 16384) {
 | 			/*if(files.front().data.size() > 16384) {
 | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | 				target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB; | ||||||
| 			} else*/ if(files.front().data.size() > 1024) { | 			} else*/ if(files.front().data.size() > 1024) { | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB; | 				target->zx8081.memory_model = ZX8081MemoryModel::SixteenKB; | ||||||
| 			} else { | 			} else { | ||||||
| 				target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded; | 				target->zx8081.memory_model = ZX8081MemoryModel::Unexpanded; | ||||||
| 			} | 			} | ||||||
| 			target.media.tapes = media.tapes; | 			target->media.tapes = media.tapes; | ||||||
| 
 | 
 | ||||||
| 			// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | 			// TODO: how to run software once loaded? Might require a BASIC detokeniser.
 | ||||||
| 			if(target.zx8081.isZX81) { | 			if(target->zx8081.isZX81) { | ||||||
| 				target.loading_command = "J\"\"\n"; | 				target->loading_command = "J\"\"\n"; | ||||||
| 			} else { | 			} else { | ||||||
| 				target.loading_command = "W\n"; | 				target->loading_command = "W\n"; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			destination.push_back(target); | 			destination.push_back(std::move(target)); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -10,13 +10,15 @@ | |||||||
| #define StaticAnalyser_ZX8081_StaticAnalyser_hpp | #define StaticAnalyser_ZX8081_StaticAnalyser_hpp | ||||||
| 
 | 
 | ||||||
| #include "../StaticAnalyser.hpp" | #include "../StaticAnalyser.hpp" | ||||||
| #include "../../Storage/TargetPlatforms.hpp" | #include "../../../Storage/TargetPlatforms.hpp" | ||||||
| 
 | 
 | ||||||
| namespace StaticAnalyser { | namespace Analyser { | ||||||
|  | namespace Static { | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
| 
 | 
 | ||||||
| void AddTargets(const Media &media, std::list<Target> &destination, TargetPlatform::IntType potential_platforms); | void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms); | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
| } | } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -53,6 +53,34 @@ const uint8_t StatusFifthSprite = 0x40; | |||||||
| const int StatusSpriteCollisionShift = 5; | const int StatusSpriteCollisionShift = 5; | ||||||
| const uint8_t StatusSpriteCollision = 0x20; | const uint8_t StatusSpriteCollision = 0x20; | ||||||
|  |  | ||||||
|  | struct ReverseTable { | ||||||
|  | 	std::uint8_t map[256]; | ||||||
|  |  | ||||||
|  | 	ReverseTable() { | ||||||
|  | 		for(int c = 0; c < 256; ++c) { | ||||||
|  | 			map[c] = static_cast<uint8_t>( | ||||||
|  | 				((c & 0x80) >> 7) | | ||||||
|  | 				((c & 0x40) >> 5) | | ||||||
|  | 				((c & 0x20) >> 3) | | ||||||
|  | 				((c & 0x10) >> 1) | | ||||||
|  | 				((c & 0x08) << 1) | | ||||||
|  | 				((c & 0x04) << 3) | | ||||||
|  | 				((c & 0x02) << 5) | | ||||||
|  | 				((c & 0x01) << 7) | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } reverse_table; | ||||||
|  |  | ||||||
|  | // Bits are reversed in the internal mode value; they're stored | ||||||
|  | // in the order M1 M2 M3. Hence the definitions below. | ||||||
|  | enum ScreenMode { | ||||||
|  | 	Text = 4, | ||||||
|  | 	MultiColour = 2, | ||||||
|  | 	ColouredText = 0, | ||||||
|  | 	Graphics = 1 | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| TMS9918Base::TMS9918Base() : | TMS9918Base::TMS9918Base() : | ||||||
| @@ -71,6 +99,12 @@ TMS9918::TMS9918(Personality p) { | |||||||
| 	crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | 	crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor); | ||||||
| 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f)); | ||||||
| 	crt_->set_input_gamma(2.8f); | 	crt_->set_input_gamma(2.8f); | ||||||
|  |  | ||||||
|  | 	// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement | ||||||
|  | 	// intended to produce the correct relationship between the hard edges between pixels and | ||||||
|  | 	// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS | ||||||
|  | 	// colour burst generator because I've yet to find any. | ||||||
|  | 	crt_->set_immediate_default_phase(0.85f); | ||||||
| } | } | ||||||
|  |  | ||||||
| Outputs::CRT::CRT *TMS9918::get_crt() { | Outputs::CRT::CRT *TMS9918::get_crt() { | ||||||
| @@ -274,7 +308,8 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 						int row_base = pattern_name_address_; | 						int row_base = pattern_name_address_; | ||||||
| 						int pattern_base = pattern_generator_table_address_; | 						int pattern_base = pattern_generator_table_address_; | ||||||
| 						int colour_base = colour_table_address_; | 						int colour_base = colour_table_address_; | ||||||
| 						if(screen_mode_ == 1) { | 						if(screen_mode_ == ScreenMode::Graphics) { | ||||||
|  | 							// If this is high resolution mode, allow the row number to affect the pattern and colour addresses. | ||||||
| 							pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); | 							pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
| 							colour_base &= 0x2000 | ((row_ & 0xc0) << 5); | 							colour_base &= 0x2000 | ((row_ & 0xc0) << 5); | ||||||
| 						} | 						} | ||||||
| @@ -285,7 +320,7 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 						const int pattern_names_end = (end - 27 + 3) >> 2; | 						const int pattern_names_end = (end - 27 + 3) >> 2; | ||||||
| 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | 						std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start)); | ||||||
|  |  | ||||||
| 						// Colours are collected ever fourth window starting from window 29. | 						// Colours are collected every fourth window starting from window 29. | ||||||
| 						const int colours_start = (access_pointer_ - 29 + 3) >> 2; | 						const int colours_start = (access_pointer_ - 29 + 3) >> 2; | ||||||
| 						const int colours_end = (end - 29 + 3) >> 2; | 						const int colours_end = (end - 29 + 3) >> 2; | ||||||
| 						if(screen_mode_ != 1) { | 						if(screen_mode_ != 1) { | ||||||
| @@ -301,8 +336,11 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 						// Patterns are collected ever fourth window starting from window 30. | 						// Patterns are collected ever fourth window starting from window 30. | ||||||
| 						const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; | 						const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; | ||||||
| 						const int pattern_buffer_end = (end - 30 + 3) >> 2; | 						const int pattern_buffer_end = (end - 30 + 3) >> 2; | ||||||
|  |  | ||||||
|  | 						// Multicolour mode uss a different function of row to pick bytes | ||||||
|  | 						const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7); | ||||||
| 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | 						for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { | ||||||
| 							pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + (row_ & 7)]; | 							pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row]; | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						// Sprite slots occur in three quarters of ever fourth window starting from window 28. | 						// Sprite slots occur in three quarters of ever fourth window starting from window 28. | ||||||
| @@ -380,21 +418,21 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
|  |  | ||||||
| 							const int shift = (output_column_ - first_pixel_column_) % 6; | 							const int shift = (output_column_ - first_pixel_column_) % 6; | ||||||
| 							int byte_column = (output_column_ - first_pixel_column_) / 6; | 							int byte_column = (output_column_ - first_pixel_column_) / 6; | ||||||
| 							int pattern = pattern_buffer_[byte_column] << shift; | 							int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; | ||||||
| 							int pixels_left = pixels_end - output_column_; | 							int pixels_left = pixels_end - output_column_; | ||||||
| 							int length = std::min(pixels_left, 6 - shift); | 							int length = std::min(pixels_left, 6 - shift); | ||||||
| 							while(true) { | 							while(true) { | ||||||
| 								pixels_left -= length; | 								pixels_left -= length; | ||||||
| 								while(length--) { | 								for(int c = 0; c < length; ++c) { | ||||||
| 									*pixel_target_ = colours[(pattern >> 7)&0x01]; | 									pixel_target_[c] = colours[pattern&0x01]; | ||||||
| 									pixel_target_++; | 									pattern >>= 1; | ||||||
| 									pattern <<= 1; |  | ||||||
| 								} | 								} | ||||||
|  | 								pixel_target_ += length; | ||||||
|  |  | ||||||
| 								if(!pixels_left) break; | 								if(!pixels_left) break; | ||||||
| 								length = std::min(6, pixels_left); | 								length = std::min(6, pixels_left); | ||||||
| 								byte_column++; | 								byte_column++; | ||||||
| 								pattern = pattern_buffer_[byte_column]; | 								pattern = reverse_table.map[pattern_buffer_[byte_column]]; | ||||||
| 							} | 							} | ||||||
| 							output_column_ = pixels_end; | 							output_column_ = pixels_end; | ||||||
| 						} break; | 						} break; | ||||||
| @@ -402,7 +440,7 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 						case LineMode::Character: { | 						case LineMode::Character: { | ||||||
| 							// If this is the start of the visible area, seed sprite shifter positions. | 							// If this is the start of the visible area, seed sprite shifter positions. | ||||||
| 							SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; | 							SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; | ||||||
| 							if(line_mode_ == LineMode::Character && output_column_ == first_pixel_column_) { | 							if(output_column_ == first_pixel_column_) { | ||||||
| 								int c = sprite_set.active_sprite_slot; | 								int c = sprite_set.active_sprite_slot; | ||||||
| 								while(c--) { | 								while(c--) { | ||||||
| 									SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | 									SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; | ||||||
| @@ -416,36 +454,46 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// Paint the background tiles. | 							// Paint the background tiles. | ||||||
| 							const int shift = (output_column_ - first_pixel_column_) & 7; |  | ||||||
| 							int byte_column = (output_column_ - first_pixel_column_) >> 3; |  | ||||||
|  |  | ||||||
| 							const int pixels_left = pixels_end - output_column_; | 							const int pixels_left = pixels_end - output_column_; | ||||||
| 							int length = std::min(pixels_left, 8 - shift); | 							if(screen_mode_ == ScreenMode::MultiColour) { | ||||||
|  | 								int pixel_location = output_column_ - first_pixel_column_; | ||||||
| 							int pattern = pattern_buffer_[byte_column] << shift; | 								for(int c = 0; c < pixels_left; ++c) { | ||||||
| 							uint8_t colour = colour_buffer_[byte_column]; | 									pixel_target_[c] = palette[ | ||||||
| 							uint32_t colours[2] = { | 										(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15 | ||||||
| 								palette[(colour & 15) ? (colour & 15) : background_colour_], | 									]; | ||||||
| 								palette[(colour >> 4) ? (colour >> 4) : background_colour_] |  | ||||||
| 							}; |  | ||||||
|  |  | ||||||
| 							int background_pixels_left = pixels_left; |  | ||||||
| 							while(true) { |  | ||||||
| 								background_pixels_left -= length; |  | ||||||
| 								while(length--) { |  | ||||||
| 									*pixel_target_ = colours[(pattern >> 7)&0x01]; |  | ||||||
| 									pixel_target_++; |  | ||||||
| 									pattern <<= 1; |  | ||||||
| 								} | 								} | ||||||
|  | 								pixel_target_ += pixels_left; | ||||||
|  | 							} else { | ||||||
|  | 								const int shift = (output_column_ - first_pixel_column_) & 7; | ||||||
|  | 								int byte_column = (output_column_ - first_pixel_column_) >> 3; | ||||||
|  |  | ||||||
| 								if(!background_pixels_left) break; | 								int length = std::min(pixels_left, 8 - shift); | ||||||
| 								length = std::min(8, background_pixels_left); |  | ||||||
| 								byte_column++; |  | ||||||
|  |  | ||||||
| 								pattern = pattern_buffer_[byte_column]; | 								int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; | ||||||
| 								colour = colour_buffer_[byte_column]; | 								uint8_t colour = colour_buffer_[byte_column]; | ||||||
| 								colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; | 								uint32_t colours[2] = { | ||||||
| 								colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; | 									palette[(colour & 15) ? (colour & 15) : background_colour_], | ||||||
|  | 									palette[(colour >> 4) ? (colour >> 4) : background_colour_] | ||||||
|  | 								}; | ||||||
|  |  | ||||||
|  | 								int background_pixels_left = pixels_left; | ||||||
|  | 								while(true) { | ||||||
|  | 									background_pixels_left -= length; | ||||||
|  | 									for(int c = 0; c < length; ++c) { | ||||||
|  | 										pixel_target_[c] = colours[pattern&0x01]; | ||||||
|  | 										pattern >>= 1; | ||||||
|  | 									} | ||||||
|  | 									pixel_target_ += length; | ||||||
|  |  | ||||||
|  | 									if(!background_pixels_left) break; | ||||||
|  | 									length = std::min(8, background_pixels_left); | ||||||
|  | 									byte_column++; | ||||||
|  |  | ||||||
|  | 									pattern = reverse_table.map[pattern_buffer_[byte_column]]; | ||||||
|  | 									colour = colour_buffer_[byte_column]; | ||||||
|  | 									colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; | ||||||
|  | 									colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; | ||||||
|  | 								} | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// Paint sprites and check for collisions. | 							// Paint sprites and check for collisions. | ||||||
| @@ -535,7 +583,7 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 			screen_mode_ = next_screen_mode_; | 			screen_mode_ = next_screen_mode_; | ||||||
| 			blank_screen_ = next_blank_screen_; | 			blank_screen_ = next_blank_screen_; | ||||||
| 			switch(screen_mode_) { | 			switch(screen_mode_) { | ||||||
| 				case 2: | 				case ScreenMode::Text: | ||||||
| 					line_mode_ = LineMode::Text; | 					line_mode_ = LineMode::Text; | ||||||
| 					first_pixel_column_ = 69; | 					first_pixel_column_ = 69; | ||||||
| 					first_right_border_column_ = 309; | 					first_right_border_column_ = 309; | ||||||
| @@ -589,7 +637,7 @@ void TMS9918::set_register(int address, uint8_t value) { | |||||||
| 			case 1: | 			case 1: | ||||||
| 				next_blank_screen_ = !(low_write_ & 0x40); | 				next_blank_screen_ = !(low_write_ & 0x40); | ||||||
| 				generate_interrupts_ = !!(low_write_ & 0x20); | 				generate_interrupts_ = !!(low_write_ & 0x20); | ||||||
| 				next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 3); | 				next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2); | ||||||
| 				sprites_16x16_ = !!(low_write_ & 0x02); | 				sprites_16x16_ = !!(low_write_ & 0x02); | ||||||
| 				sprites_magnified_ = !!(low_write_ & 0x01); | 				sprites_magnified_ = !!(low_write_ & 0x01); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -156,6 +156,11 @@ void AY38910::evaluate_output_volume() { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool AY38910::is_zero_level() { | ||||||
|  | 	// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero. | ||||||
|  | 	return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| // MARK: - Register manipulation | // MARK: - Register manipulation | ||||||
|  |  | ||||||
| void AY38910::select_register(uint8_t r) { | void AY38910::select_register(uint8_t r) { | ||||||
|   | |||||||
| @@ -85,6 +85,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource { | |||||||
|  |  | ||||||
| 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | 		// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption | ||||||
| 		void get_samples(std::size_t number_of_samples, int16_t *target); | 		void get_samples(std::size_t number_of_samples, int16_t *target); | ||||||
|  | 		bool is_zero_level(); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|   | |||||||
| @@ -15,12 +15,12 @@ using namespace Konami; | |||||||
| SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : | SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : | ||||||
| 	task_queue_(task_queue) {} | 	task_queue_(task_queue) {} | ||||||
|  |  | ||||||
| bool SCC::is_silent() { | bool SCC::is_zero_level() { | ||||||
| 	return !(channel_enable_ & 0x1f); | 	return !(channel_enable_ & 0x1f); | ||||||
| } | } | ||||||
|  |  | ||||||
| void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||||
| 	if(is_silent()) { | 	if(is_zero_level()) { | ||||||
| 		std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); | 		std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { | |||||||
| 		SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); | 		SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); | ||||||
|  |  | ||||||
| 		/// As per ::SampleSource; provides a broadphase test for silence. | 		/// As per ::SampleSource; provides a broadphase test for silence. | ||||||
| 		bool is_silent(); | 		bool is_zero_level(); | ||||||
|  |  | ||||||
| 		/// As per ::SampleSource; provides audio output. | 		/// As per ::SampleSource; provides audio output. | ||||||
| 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|   | |||||||
							
								
								
									
										157
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								Components/SN76489/SN76489.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | |||||||
|  | // | ||||||
|  | //  SN76489.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 26/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "SN76489.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
|  | using namespace TI; | ||||||
|  |  | ||||||
|  | SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { | ||||||
|  | 	// Build a volume table. | ||||||
|  | 	double multiplier = pow(10.0, -0.1); | ||||||
|  | 	double volume = 8191.0f; | ||||||
|  | 	for(int c = 0; c < 16; ++c) { | ||||||
|  | 		volumes_[c] = (int)round(volume); | ||||||
|  | 		volume *= multiplier; | ||||||
|  | 	} | ||||||
|  | 	volumes_[15] = 0; | ||||||
|  | 	evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 	switch(personality) { | ||||||
|  | 		case Personality::SN76494: | ||||||
|  | 			master_divider_period_ = 2; | ||||||
|  | 			shifter_is_16bit_ = false; | ||||||
|  | 		break; | ||||||
|  | 		case Personality::SN76489: | ||||||
|  | 			master_divider_period_ = 16; | ||||||
|  | 			shifter_is_16bit_ = false; | ||||||
|  | 		break; | ||||||
|  | 		case Personality::SMS: | ||||||
|  | 			master_divider_period_ = 16; | ||||||
|  | 			shifter_is_16bit_ = true; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	assert((master_divider_period_ % additional_divider) == 0); | ||||||
|  | 	assert(additional_divider < master_divider_period_); | ||||||
|  | 	master_divider_period_ /= additional_divider; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::set_register(uint8_t value) { | ||||||
|  | 	task_queue_.defer([value, this] () { | ||||||
|  | 		if(value & 0x80) { | ||||||
|  | 			active_register_ = value; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const int channel = (active_register_ >> 5)&3; | ||||||
|  | 		if(active_register_ & 0x10) { | ||||||
|  | 			// latch for volume | ||||||
|  | 			channels_[channel].volume = value & 0xf; | ||||||
|  | 			evaluate_output_volume(); | ||||||
|  | 		} else { | ||||||
|  | 			// latch for tone/data | ||||||
|  | 			if(channel < 3) { | ||||||
|  | 				if(value & 0x80) { | ||||||
|  | 					channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf); | ||||||
|  | 				} else { | ||||||
|  | 					channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4)); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// writes to the noise register always reset the shifter | ||||||
|  | 				noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000; | ||||||
|  |  | ||||||
|  | 				if(value & 4) { | ||||||
|  | 					noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15; | ||||||
|  | 				} else { | ||||||
|  | 					noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3)); | ||||||
|  | 				// Special case: if these bits are both set, the noise channel should track channel 2, | ||||||
|  | 				// which is marked with a divider of 0xffff. | ||||||
|  | 				if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SN76489::is_zero_level() { | ||||||
|  | 	return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::evaluate_output_volume() { | ||||||
|  | 	output_volume_ = static_cast<int16_t>( | ||||||
|  | 		channels_[0].level * volumes_[channels_[0].volume] + | ||||||
|  | 		channels_[1].level * volumes_[channels_[1].volume] + | ||||||
|  | 		channels_[2].level * volumes_[channels_[2].volume] + | ||||||
|  | 		channels_[3].level * volumes_[channels_[3].volume] | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { | ||||||
|  | 	std::size_t c = 0; | ||||||
|  | 	while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) { | ||||||
|  | 		target[c] = output_volume_; | ||||||
|  | 		master_divider_++; | ||||||
|  | 		c++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	while(c < number_of_samples) { | ||||||
|  | 		bool did_flip = false; | ||||||
|  |  | ||||||
|  | #define step_channel(x, s) \ | ||||||
|  | 		if(channels_[x].counter) channels_[x].counter--;\ | ||||||
|  | 		else {\ | ||||||
|  | 			channels_[x].level ^= 1;\ | ||||||
|  | 			channels_[x].counter = channels_[x].divider;\ | ||||||
|  | 			s;\ | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		step_channel(0, /**/); | ||||||
|  | 		step_channel(1, /**/); | ||||||
|  | 		step_channel(2, did_flip = true); | ||||||
|  |  | ||||||
|  | #undef step_channel | ||||||
|  |  | ||||||
|  | 		if(channels_[3].divider != 0xffff) { | ||||||
|  | 			if(channels_[3].counter) channels_[3].counter--; | ||||||
|  | 			else { | ||||||
|  | 				did_flip = true; | ||||||
|  | 				channels_[3].counter = channels_[3].divider; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(did_flip) { | ||||||
|  | 			channels_[3].level = noise_shifter_ & 1; | ||||||
|  | 			int new_bit = channels_[3].level; | ||||||
|  | 			switch(noise_mode_) { | ||||||
|  | 				default: break; | ||||||
|  | 				case Noise15: | ||||||
|  | 					new_bit ^= (noise_shifter_ >> 1); | ||||||
|  | 				break; | ||||||
|  | 				case Noise16: | ||||||
|  | 					new_bit ^= (noise_shifter_ >> 3); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			noise_shifter_ >>= 1; | ||||||
|  | 			noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		evaluate_output_volume(); | ||||||
|  |  | ||||||
|  | 		for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) { | ||||||
|  | 			target[c] = output_volume_; | ||||||
|  | 			c++; | ||||||
|  | 			master_divider_++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	master_divider_ &= (master_divider_period_ - 1); | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Components/SN76489/SN76489.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | // | ||||||
|  | //  SN76489.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 26/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef SN76489_hpp | ||||||
|  | #define SN76489_hpp | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  | #include "../../Concurrency/AsyncTaskQueue.hpp" | ||||||
|  |  | ||||||
|  | namespace TI { | ||||||
|  |  | ||||||
|  | class SN76489: public Outputs::Speaker::SampleSource { | ||||||
|  | 	public: | ||||||
|  | 		enum class Personality { | ||||||
|  | 			SN76489, | ||||||
|  | 			SN76494, | ||||||
|  | 			SMS | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Creates a new SN76489. | ||||||
|  | 		SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1); | ||||||
|  |  | ||||||
|  | 		/// Writes a new value to the SN76489. | ||||||
|  | 		void set_register(uint8_t value); | ||||||
|  |  | ||||||
|  | 		// As per SampleSource. | ||||||
|  | 		void get_samples(std::size_t number_of_samples, std::int16_t *target); | ||||||
|  | 		bool is_zero_level(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int master_divider_ = 0; | ||||||
|  | 		int master_divider_period_ = 16; | ||||||
|  | 		int16_t output_volume_ = 0; | ||||||
|  | 		void evaluate_output_volume(); | ||||||
|  | 		int volumes_[16]; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue &task_queue_; | ||||||
|  |  | ||||||
|  | 		struct ToneChannel { | ||||||
|  | 			// Programmatically-set state; updated by the processor. | ||||||
|  | 			uint16_t divider = 0; | ||||||
|  | 			uint8_t volume = 0xf; | ||||||
|  |  | ||||||
|  | 			// Active state; self-evolving as a function of time. | ||||||
|  | 			uint16_t counter = 0; | ||||||
|  | 			int level = 0; | ||||||
|  | 		} channels_[4]; | ||||||
|  | 		enum { | ||||||
|  | 			Periodic15, | ||||||
|  | 			Periodic16, | ||||||
|  | 			Noise15, | ||||||
|  | 			Noise16 | ||||||
|  | 		} noise_mode_ = Periodic15; | ||||||
|  | 		uint16_t noise_shifter_ = 0; | ||||||
|  | 		int active_register_ = 0; | ||||||
|  |  | ||||||
|  | 		bool shifter_is_16bit_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* SN76489_hpp */ | ||||||
| @@ -80,6 +80,10 @@ void AsyncTaskQueue::flush() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { | ||||||
|  | 	perform(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) { | ||||||
| 	if(!deferred_tasks_) { | 	if(!deferred_tasks_) { | ||||||
| 		deferred_tasks_.reset(new std::list<std::function<void(void)>>); | 		deferred_tasks_.reset(new std::list<std::function<void(void)>>); | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ namespace Concurrency { | |||||||
| class AsyncTaskQueue { | class AsyncTaskQueue { | ||||||
| 	public: | 	public: | ||||||
| 		AsyncTaskQueue(); | 		AsyncTaskQueue(); | ||||||
| 		~AsyncTaskQueue(); | 		virtual ~AsyncTaskQueue(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Adds @c function to the queue. | 			Adds @c function to the queue. | ||||||
| @@ -69,6 +69,8 @@ class AsyncTaskQueue { | |||||||
| */ | */ | ||||||
| class DeferringAsyncTaskQueue: public AsyncTaskQueue { | class DeferringAsyncTaskQueue: public AsyncTaskQueue { | ||||||
| 	public: | 	public: | ||||||
|  | 		~DeferringAsyncTaskQueue(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Adds a function to the deferral list. | 			Adds a function to the deferral list. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,6 +17,11 @@ BestEffortUpdater::BestEffortUpdater() { | |||||||
| 	update_is_ongoing_.clear(); | 	update_is_ongoing_.clear(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | BestEffortUpdater::~BestEffortUpdater() { | ||||||
|  | 	// Don't allow further deconstruction until the task queue is stopped. | ||||||
|  | 	flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BestEffortUpdater::update() { | void BestEffortUpdater::update() { | ||||||
| 	// Perform an update only if one is not currently ongoing. | 	// Perform an update only if one is not currently ongoing. | ||||||
| 	if(!update_is_ongoing_.test_and_set()) { | 	if(!update_is_ongoing_.test_and_set()) { | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ namespace Concurrency { | |||||||
| class BestEffortUpdater { | class BestEffortUpdater { | ||||||
| 	public: | 	public: | ||||||
| 		BestEffortUpdater(); | 		BestEffortUpdater(); | ||||||
|  | 		~BestEffortUpdater(); | ||||||
|  |  | ||||||
| 		/// A delegate receives timing cues. | 		/// A delegate receives timing cues. | ||||||
| 		struct Delegate { | 		struct Delegate { | ||||||
|   | |||||||
| @@ -26,6 +26,10 @@ struct Option { | |||||||
| 	virtual ~Option() {} | 	virtual ~Option() {} | ||||||
|  |  | ||||||
| 	Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {} | 	Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {} | ||||||
|  |  | ||||||
|  | 	virtual bool operator==(const Option &rhs) { | ||||||
|  | 		return long_name == rhs.long_name && short_name == rhs.short_name; | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BooleanOption: public Option { | struct BooleanOption: public Option { | ||||||
| @@ -35,6 +39,12 @@ struct BooleanOption: public Option { | |||||||
| struct ListOption: public Option { | struct ListOption: public Option { | ||||||
| 	std::vector<std::string> options; | 	std::vector<std::string> options; | ||||||
| 	ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {} | 	ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {} | ||||||
|  |  | ||||||
|  | 	virtual bool operator==(const Option &rhs) { | ||||||
|  | 		const ListOption *list_rhs = dynamic_cast<const ListOption *>(&rhs); | ||||||
|  | 		if(!list_rhs) return false; | ||||||
|  | 		return long_name == rhs.long_name && short_name == rhs.short_name && options == list_rhs->options; | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BooleanSelection; | struct BooleanSelection; | ||||||
|   | |||||||
| @@ -21,18 +21,45 @@ class Joystick { | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Joystick() {} | 		virtual ~Joystick() {} | ||||||
|  |  | ||||||
| 		enum class DigitalInput { | 		struct DigitalInput { | ||||||
| 			Up, Down, Left, Right, Fire | 			enum Type { | ||||||
|  | 				Up, Down, Left, Right, Fire, | ||||||
|  | 				Key | ||||||
|  | 			} type; | ||||||
|  | 			union { | ||||||
|  | 				struct { | ||||||
|  | 					int index; | ||||||
|  | 				} control; | ||||||
|  | 				struct { | ||||||
|  | 					wchar_t symbol; | ||||||
|  | 				} key; | ||||||
|  | 			} info; | ||||||
|  |  | ||||||
|  | 			DigitalInput(Type type, int index = 0) : type(type) { | ||||||
|  | 				info.control.index = index; | ||||||
|  | 			} | ||||||
|  | 			DigitalInput(wchar_t symbol) : type(Key) { | ||||||
|  | 				info.key.symbol = symbol; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bool operator == (const DigitalInput &rhs) { | ||||||
|  | 				if(rhs.type != type) return false; | ||||||
|  | 				if(rhs.type == Key) { | ||||||
|  | 					return rhs.info.key.symbol == info.key.symbol; | ||||||
|  | 				} else { | ||||||
|  | 					return rhs.info.control.index == info.control.index; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | 		virtual std::vector<DigitalInput> get_inputs() = 0; | ||||||
|  |  | ||||||
| 		// Host interface. | 		// Host interface. | ||||||
| 		virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0; | 		virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0; | ||||||
| 		virtual void reset_all_inputs() { | 		virtual void reset_all_inputs() { | ||||||
| 			set_digital_input(DigitalInput::Up, false); | 			for(const auto &input: get_inputs()) { | ||||||
| 			set_digital_input(DigitalInput::Down, false); | 				set_digital_input(input, false); | ||||||
| 			set_digital_input(DigitalInput::Left, false); | 			} | ||||||
| 			set_digital_input(DigitalInput::Right, false); |  | ||||||
| 			set_digital_input(DigitalInput::Fire, false); |  | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -867,19 +867,19 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final  { | 		void configure_as_target(const Analyser::Static::Target &target) override final  { | ||||||
| 			switch(target.amstradcpc.model) { | 			switch(target.amstradcpc.model) { | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC464: | 				case Analyser::Static::AmstradCPCModel::CPC464: | ||||||
| 					rom_model_ = ROMType::OS464; | 					rom_model_ = ROMType::OS464; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = false; | 					has_fdc_ = false; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC664: | 				case Analyser::Static::AmstradCPCModel::CPC664: | ||||||
| 					rom_model_ = ROMType::OS664; | 					rom_model_ = ROMType::OS664; | ||||||
| 					has_128k_ = false; | 					has_128k_ = false; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::AmstradCPCModel::CPC6128: | 				case Analyser::Static::AmstradCPCModel::CPC6128: | ||||||
| 					rom_model_ = ROMType::OS6128; | 					rom_model_ = ROMType::OS6128; | ||||||
| 					has_128k_ = true; | 					has_128k_ = true; | ||||||
| 					has_fdc_ = true; | 					has_fdc_ = true; | ||||||
| @@ -908,7 +908,7 @@ class ConcreteMachine: | |||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			// If there are any tapes supplied, use the first of them. | 			// If there are any tapes supplied, use the first of them. | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| @@ -976,8 +976,8 @@ class ConcreteMachine: | |||||||
| 			key_state_.clear_all_keys(); | 			key_state_.clear_all_keys(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -36,8 +36,18 @@ class Joystick: public Inputs::Joystick { | |||||||
| 		Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : | 		Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : | ||||||
| 			bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} | 			bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} | ||||||
|  |  | ||||||
| 		void set_digital_input(DigitalInput digital_input, bool is_active) { | 		std::vector<DigitalInput> get_inputs() override { | ||||||
| 			switch(digital_input) { | 			return { | ||||||
|  | 				DigitalInput(DigitalInput::Up), | ||||||
|  | 				DigitalInput(DigitalInput::Down), | ||||||
|  | 				DigitalInput(DigitalInput::Left), | ||||||
|  | 				DigitalInput(DigitalInput::Right), | ||||||
|  | 				DigitalInput(DigitalInput::Fire) | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | ||||||
|  | 			switch(digital_input.type) { | ||||||
| 				case DigitalInput::Up:		bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active);		break; | 				case DigitalInput::Up:		bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active);		break; | ||||||
| 				case DigitalInput::Down:	bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active);		break; | 				case DigitalInput::Down:	bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active);		break; | ||||||
| 				case DigitalInput::Left:	bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active);		break; | 				case DigitalInput::Left:	bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active);		break; | ||||||
| @@ -50,6 +60,8 @@ class Joystick: public Inputs::Joystick { | |||||||
| 					else | 					else | ||||||
| 						bus_->tia_input_value_[fire_tia_input_] |= 0x80; | 						bus_->tia_input_value_[fire_tia_input_] |= 0x80; | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
|  | 				default: break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -72,34 +84,34 @@ class ConcreteMachine: | |||||||
| 			close_output(); | 			close_output(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override { | 		void configure_as_target(const Analyser::Static::Target &target) override { | ||||||
| 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||||
| 			switch(target.atari.paging_model) { | 			switch(target.atari.paging_model) { | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | 				case Analyser::Static::Atari2600PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | 				case Analyser::Static::Atari2600PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::CommaVid:		bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | 				case Analyser::Static::Atari2600PagingModel::CommaVid:		bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | 				case Analyser::Static::Atari2600PagingModel::MegaBoy:			bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom));			break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::MNetwork:		bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | 				case Analyser::Static::Atari2600PagingModel::MNetwork:		bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom));			break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::None:			bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | 				case Analyser::Static::Atari2600PagingModel::None:			bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom));			break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | 				case Analyser::Static::Atari2600PagingModel::ParkerBros:		bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom));		break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::Pitfall2:		bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | 				case Analyser::Static::Atari2600PagingModel::Pitfall2:		bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom));			break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | 				case Analyser::Static::Atari2600PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::Atari8k: | 				case Analyser::Static::Atari2600PagingModel::Atari8k: | ||||||
| 					if(target.atari.uses_superchip) { | 					if(target.atari.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom)); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::Atari16k: | 				case Analyser::Static::Atari2600PagingModel::Atari16k: | ||||||
| 					if(target.atari.uses_superchip) { | 					if(target.atari.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom)); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::Atari2600PagingModel::Atari32k: | 				case Analyser::Static::Atari2600PagingModel::Atari32k: | ||||||
| 					if(target.atari.uses_superchip) { | 					if(target.atari.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| @@ -112,7 +124,7 @@ class ConcreteMachine: | |||||||
| 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -130,6 +142,18 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		bool get_switch_is_enabled(Atari2600Switch input) override { | ||||||
|  | 			uint8_t port_input = bus_->mos6532_.get_port_input(1); | ||||||
|  | 			switch(input) { | ||||||
|  | 				case Atari2600SwitchReset:					return !!(port_input & 0x01); | ||||||
|  | 				case Atari2600SwitchSelect:					return !!(port_input & 0x02); | ||||||
|  | 				case Atari2600SwitchColour:					return !!(port_input & 0x08); | ||||||
|  | 				case Atari2600SwitchLeftPlayerDifficulty:	return !!(port_input & 0x40); | ||||||
|  | 				case Atari2600SwitchRightPlayerDifficulty:	return !!(port_input & 0x80); | ||||||
|  | 				default:									return false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_reset_switch(bool state) override { | 		void set_reset_switch(bool state) override { | ||||||
| 			bus_->set_reset_line(state); | 			bus_->set_reset_line(state); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -33,6 +33,9 @@ class Machine: | |||||||
| 		/// Sets the switch @c input to @c state. | 		/// Sets the switch @c input to @c state. | ||||||
| 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | 		virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; | ||||||
|  |  | ||||||
|  | 		/// Gets the state of switch @c input. | ||||||
|  | 		virtual bool get_switch_is_enabled(Atari2600Switch input) = 0; | ||||||
|  |  | ||||||
| 		// Presses or releases the reset button. | 		// Presses or releases the reset button. | ||||||
| 		virtual void set_reset_switch(bool state) = 0; | 		virtual void set_reset_switch(bool state) = 0; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -44,11 +44,15 @@ class Machine: public ROMMachine::Machine { | |||||||
| 		/// Runs the machine for @c cycles. | 		/// Runs the machine for @c cycles. | ||||||
| 		virtual void run_for(const Cycles cycles) = 0; | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
|  |  | ||||||
|  | 		/// @returns The confidence that this machine is running content it understands. | ||||||
|  | 		virtual float get_confidence() { return 0.5f; } | ||||||
|  | 		virtual void print_type() {} | ||||||
|  |  | ||||||
| 		// TODO: sever the clock-rate stuff. | 		// TODO: sever the clock-rate stuff. | ||||||
| 		double get_clock_rate() { | 		virtual double get_clock_rate() { | ||||||
| 			return clock_rate_; | 			return clock_rate_; | ||||||
| 		} | 		} | ||||||
| 		bool get_clock_is_unlimited() { | 		virtual bool get_clock_is_unlimited() { | ||||||
| 			return clock_is_unlimited_; | 			return clock_is_unlimited_; | ||||||
| 		} | 		} | ||||||
| 		class Delegate { | 		class Delegate { | ||||||
| @@ -56,7 +60,7 @@ class Machine: public ROMMachine::Machine { | |||||||
| 				virtual void machine_did_change_clock_rate(Machine *machine) = 0; | 				virtual void machine_did_change_clock_rate(Machine *machine) = 0; | ||||||
| 				virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; | 				virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0; | ||||||
| 		}; | 		}; | ||||||
| 		void set_delegate(Delegate *delegate) { delegate_ = delegate; } | 		virtual void set_delegate(Delegate *delegate) { delegate_ = delegate; } | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		void set_clock_rate(double clock_rate) { | 		void set_clock_rate(double clock_rate) { | ||||||
|   | |||||||
							
								
								
									
										382
									
								
								Machines/ColecoVision/ColecoVision.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								Machines/ColecoVision/ColecoVision.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,382 @@ | |||||||
|  | // | ||||||
|  | //  ColecoVision.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ColecoVision.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Processors/Z80/Z80.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Components/9918/9918.hpp" | ||||||
|  | #include "../../Components/AY38910/AY38910.hpp"	// For the Super Game Module. | ||||||
|  | #include "../../Components/SN76489/SN76489.hpp" | ||||||
|  |  | ||||||
|  | #include "../ConfigurationTarget.hpp" | ||||||
|  | #include "../CRTMachine.hpp" | ||||||
|  | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | ||||||
|  | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  | const int sn76489_divider = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Coleco { | ||||||
|  | namespace Vision { | ||||||
|  |  | ||||||
|  | class Joystick: public Inputs::Joystick { | ||||||
|  | 	public: | ||||||
|  | 		std::vector<DigitalInput> get_inputs() override { | ||||||
|  | 			return { | ||||||
|  | 				DigitalInput(DigitalInput::Up), | ||||||
|  | 				DigitalInput(DigitalInput::Down), | ||||||
|  | 				DigitalInput(DigitalInput::Left), | ||||||
|  | 				DigitalInput(DigitalInput::Right), | ||||||
|  |  | ||||||
|  | 				DigitalInput(DigitalInput::Fire, 0), | ||||||
|  | 				DigitalInput(DigitalInput::Fire, 1), | ||||||
|  |  | ||||||
|  | 				DigitalInput('0'),	DigitalInput('1'),	DigitalInput('2'), | ||||||
|  | 				DigitalInput('3'),	DigitalInput('4'),	DigitalInput('5'), | ||||||
|  | 				DigitalInput('6'),	DigitalInput('7'),	DigitalInput('8'), | ||||||
|  | 				DigitalInput('9'),	DigitalInput('*'),	DigitalInput('#'), | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | ||||||
|  | 			switch(digital_input.type) { | ||||||
|  | 				default: return; | ||||||
|  |  | ||||||
|  | 				case DigitalInput::Key: | ||||||
|  | 					if(!is_active) keypad_ |= 0xf; | ||||||
|  | 					else { | ||||||
|  | 						uint8_t mask = 0xf; | ||||||
|  | 						switch(digital_input.info.key.symbol) { | ||||||
|  | 							case '8':	mask = 0x1;		break; | ||||||
|  | 							case '4':	mask = 0x2;		break; | ||||||
|  | 							case '5':	mask = 0x3;		break; | ||||||
|  | 							case '7':	mask = 0x5;		break; | ||||||
|  | 							case '#':	mask = 0x6;		break; | ||||||
|  | 							case '2':	mask = 0x7;		break; | ||||||
|  | 							case '*':	mask = 0x9;		break; | ||||||
|  | 							case '0':	mask = 0xa;		break; | ||||||
|  | 							case '9':	mask = 0xb;		break; | ||||||
|  | 							case '3':	mask = 0xc;		break; | ||||||
|  | 							case '1':	mask = 0xd;		break; | ||||||
|  | 							case '6':	mask = 0xe;		break; | ||||||
|  | 							default: break; | ||||||
|  | 						} | ||||||
|  | 						keypad_ = (keypad_ & 0xf0) | mask; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case DigitalInput::Up: 		if(is_active) direction_ &= ~0x01; else direction_ |= 0x01;	break; | ||||||
|  | 				case DigitalInput::Right:	if(is_active) direction_ &= ~0x02; else direction_ |= 0x02;	break; | ||||||
|  | 				case DigitalInput::Down:	if(is_active) direction_ &= ~0x04; else direction_ |= 0x04;	break; | ||||||
|  | 				case DigitalInput::Left:	if(is_active) direction_ &= ~0x08; else direction_ |= 0x08;	break; | ||||||
|  | 				case DigitalInput::Fire: | ||||||
|  | 					switch(digital_input.info.control.index) { | ||||||
|  | 						default: break; | ||||||
|  | 						case 0:	if(is_active) direction_ &= ~0x40; else direction_ |= 0x40;	break; | ||||||
|  | 						case 1:	if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40;		break; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t get_direction_input() { | ||||||
|  | 			return direction_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uint8_t get_keypad_input() { | ||||||
|  | 			return keypad_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint8_t direction_ = 0xff; | ||||||
|  | 		uint8_t keypad_ = 0xff; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ConcreteMachine: | ||||||
|  | 	public Machine, | ||||||
|  | 	public CPU::Z80::BusHandler, | ||||||
|  | 	public CRTMachine::Machine, | ||||||
|  | 	public ConfigurationTarget::Machine, | ||||||
|  | 	public JoystickMachine::Machine { | ||||||
|  |  | ||||||
|  | 	public: | ||||||
|  | 		ConcreteMachine() : | ||||||
|  | 			z80_(*this), | ||||||
|  | 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), | ||||||
|  | 			ay_(audio_queue_), | ||||||
|  | 			mixer_(sn76489_, ay_), | ||||||
|  | 			speaker_(mixer_) { | ||||||
|  | 			speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider)); | ||||||
|  | 			set_clock_rate(3579545); | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void setup_output(float aspect_ratio) override { | ||||||
|  | 			vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); | ||||||
|  | 			get_crt()->set_output_device(Outputs::CRT::OutputDevice::Television); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void close_output() override { | ||||||
|  | 			vdp_.reset(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::CRT::CRT *get_crt() override { | ||||||
|  | 			return vdp_->get_crt(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Outputs::Speaker::Speaker *get_speaker() override { | ||||||
|  | 			return &speaker_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void run_for(const Cycles cycles) override { | ||||||
|  | 			z80_.run_for(cycles); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const Analyser::Static::Target &target) override { | ||||||
|  | 			// Insert the media. | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
|  | 			if(!media.cartridges.empty()) { | ||||||
|  | 				const auto &segment = media.cartridges.front()->get_segments().front(); | ||||||
|  | 				cartridge_ = segment.data; | ||||||
|  | 				if(cartridge_.size() >= 32768) | ||||||
|  | 					cartridge_address_limit_ = 0xffff; | ||||||
|  | 				else | ||||||
|  | 					cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1); | ||||||
|  |  | ||||||
|  | 				if(cartridge_.size() > 32768) { | ||||||
|  | 					cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; | ||||||
|  | 					cartridge_pages_[1] = cartridge_.data(); | ||||||
|  | 					is_megacart_ = true; | ||||||
|  | 				} else { | ||||||
|  | 					cartridge_pages_[0] = cartridge_.data(); | ||||||
|  | 					cartridge_pages_[1] = cartridge_.data() + 16384; | ||||||
|  | 					is_megacart_ = false; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Obtains the system ROMs. | ||||||
|  | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
|  | 			auto roms = roms_with_names( | ||||||
|  | 				"ColecoVision", | ||||||
|  | 				{ "coleco.rom" }); | ||||||
|  |  | ||||||
|  | 			if(!roms[0]) return false; | ||||||
|  |  | ||||||
|  | 			bios_ = *roms[0]; | ||||||
|  | 			bios_.resize(8192); | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: Z80::BusHandler | ||||||
|  | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
|  | 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | ||||||
|  | 			switch(cycle.operation) { | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Read: | ||||||
|  | 					if(address < 0x2000) { | ||||||
|  | 						if(super_game_module_.replace_bios) { | ||||||
|  | 							*cycle.value = super_game_module_.ram[address]; | ||||||
|  | 						} else { | ||||||
|  | 							*cycle.value = bios_[address]; | ||||||
|  | 						} | ||||||
|  | 					} else if(super_game_module_.replace_ram && address < 0x8000) { | ||||||
|  | 						*cycle.value = super_game_module_.ram[address]; | ||||||
|  | 					} else if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | 						*cycle.value = ram_[address & 1023]; | ||||||
|  | 					} else if(address >= 0x8000 && address <= cartridge_address_limit_) { | ||||||
|  | 						if(is_megacart_ && address >= 0xffc0) { | ||||||
|  | 							page_megacart(address); | ||||||
|  | 						} | ||||||
|  | 						*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; | ||||||
|  | 					} else { | ||||||
|  | 						*cycle.value = 0xff; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Write: | ||||||
|  | 					if(super_game_module_.replace_bios && address < 0x2000) { | ||||||
|  | 						super_game_module_.ram[address] = *cycle.value; | ||||||
|  | 					} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { | ||||||
|  | 						super_game_module_.ram[address] = *cycle.value; | ||||||
|  | 					} else if(address >= 0x6000 && address < 0x8000) { | ||||||
|  | 						ram_[address & 1023] = *cycle.value; | ||||||
|  | 					} else if(is_megacart_ && address >= 0xffc0) { | ||||||
|  | 						page_megacart(address); | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Input: | ||||||
|  | 					switch((address >> 5) & 7) { | ||||||
|  | 						case 5: | ||||||
|  | 							update_video(); | ||||||
|  | 							*cycle.value = vdp_->get_register(address); | ||||||
|  | 							z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); | ||||||
|  | 							time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
|  | 						break; | ||||||
|  |  | ||||||
|  | 						case 7: { | ||||||
|  | 							const std::size_t joystick_id = (address&2) >> 1; | ||||||
|  | 							Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get()); | ||||||
|  | 							if(joysticks_in_keypad_mode_) { | ||||||
|  | 								*cycle.value = joystick->get_keypad_input(); | ||||||
|  | 							} else { | ||||||
|  | 								*cycle.value = joystick->get_direction_input(); | ||||||
|  | 							} | ||||||
|  | 						} break; | ||||||
|  |  | ||||||
|  | 						default: | ||||||
|  | 							switch(address&0xff) { | ||||||
|  | 								default: *cycle.value = 0xff; break; | ||||||
|  | 								case 0x52: | ||||||
|  | 									// Read AY data. | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); | ||||||
|  | 									*cycle.value = ay_.get_data_output(); | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case CPU::Z80::PartialMachineCycle::Output: { | ||||||
|  | 					const int eighth = (address >> 5) & 7; | ||||||
|  | 					switch(eighth) { | ||||||
|  | 						case 4: case 6: | ||||||
|  | 							joysticks_in_keypad_mode_ = eighth == 4; | ||||||
|  | 						break; | ||||||
|  |  | ||||||
|  | 						case 5: | ||||||
|  | 							update_video(); | ||||||
|  | 							vdp_->set_register(address, *cycle.value); | ||||||
|  | 							z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); | ||||||
|  | 							time_until_interrupt_ = vdp_->get_time_until_interrupt(); | ||||||
|  | 						break; | ||||||
|  |  | ||||||
|  | 						case 7: | ||||||
|  | 							update_audio(); | ||||||
|  | 							sn76489_.set_register(*cycle.value); | ||||||
|  | 						break; | ||||||
|  |  | ||||||
|  | 						default: | ||||||
|  | 							// Catch Super Game Module accesses; it decodes more thoroughly. | ||||||
|  | 							switch(address&0xff) { | ||||||
|  | 								default: break; | ||||||
|  | 								case 0x7f: | ||||||
|  | 									super_game_module_.replace_bios = !((*cycle.value)&0x2); | ||||||
|  | 								break; | ||||||
|  | 								case 0x50: | ||||||
|  | 									// Set AY address. | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::BC1); | ||||||
|  | 									ay_.set_data_input(*cycle.value); | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 								break; | ||||||
|  | 								case 0x51: | ||||||
|  | 									// Set AY data. | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); | ||||||
|  | 									ay_.set_data_input(*cycle.value); | ||||||
|  | 									ay_.set_control_lines(GI::AY38910::ControlLines(0)); | ||||||
|  | 								break; | ||||||
|  | 								case 0x53: | ||||||
|  | 									super_game_module_.replace_ram = !!((*cycle.value)&0x1); | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			time_since_vdp_update_ += cycle.length; | ||||||
|  | 			time_since_sn76489_update_ += cycle.length; | ||||||
|  |  | ||||||
|  | 			if(time_until_interrupt_ > 0) { | ||||||
|  | 				time_until_interrupt_ -= cycle.length; | ||||||
|  | 				if(time_until_interrupt_ <= HalfCycles(0)) { | ||||||
|  | 					z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return HalfCycles(0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void flush() { | ||||||
|  | 			update_video(); | ||||||
|  | 			update_audio(); | ||||||
|  | 			audio_queue_.perform(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		inline void page_megacart(uint16_t address) { | ||||||
|  | 			const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size(); | ||||||
|  | 			cartridge_pages_[1] = &cartridge_[selected_start]; | ||||||
|  | 		} | ||||||
|  | 		inline void update_audio() { | ||||||
|  | 			speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); | ||||||
|  | 		} | ||||||
|  | 		inline void update_video() { | ||||||
|  | 			vdp_->run_for(time_since_vdp_update_.flush()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		CPU::Z80::Processor<ConcreteMachine, false, false> z80_; | ||||||
|  | 		std::unique_ptr<TI::TMS9918> vdp_; | ||||||
|  |  | ||||||
|  | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
|  | 		TI::SN76489 sn76489_; | ||||||
|  | 		GI::AY38910::AY38910 ay_; | ||||||
|  | 		Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910> mixer_; | ||||||
|  | 		Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910>> speaker_; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> bios_; | ||||||
|  | 		std::vector<uint8_t> cartridge_; | ||||||
|  | 		uint8_t *cartridge_pages_[2]; | ||||||
|  | 		uint8_t ram_[1024]; | ||||||
|  | 		bool is_megacart_ = false; | ||||||
|  | 		uint16_t cartridge_address_limit_ = 0; | ||||||
|  | 		struct { | ||||||
|  | 			bool replace_bios = false; | ||||||
|  | 			bool replace_ram = false; | ||||||
|  | 			uint8_t ram[32768]; | ||||||
|  | 		} super_game_module_; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | 		bool joysticks_in_keypad_mode_ = false; | ||||||
|  |  | ||||||
|  | 		HalfCycles time_since_vdp_update_; | ||||||
|  | 		HalfCycles time_since_sn76489_update_; | ||||||
|  | 		HalfCycles time_until_interrupt_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | using namespace Coleco::Vision; | ||||||
|  |  | ||||||
|  | Machine *Machine::ColecoVision() { | ||||||
|  | 	return new ConcreteMachine; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::~Machine() {} | ||||||
							
								
								
									
										24
									
								
								Machines/ColecoVision/ColecoVision.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Machines/ColecoVision/ColecoVision.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // | ||||||
|  | //  ColecoVision.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/02/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ColecoVision_hpp | ||||||
|  | #define ColecoVision_hpp | ||||||
|  |  | ||||||
|  | namespace Coleco { | ||||||
|  | namespace Vision { | ||||||
|  |  | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		virtual ~Machine(); | ||||||
|  | 		static Machine *ColecoVision(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ColecoVision_hpp */ | ||||||
| @@ -252,9 +252,19 @@ class Joystick: public Inputs::Joystick { | |||||||
| 			user_port_via_port_handler_(user_port_via_port_handler), | 			user_port_via_port_handler_(user_port_via_port_handler), | ||||||
| 			keyboard_via_port_handler_(keyboard_via_port_handler) {} | 			keyboard_via_port_handler_(keyboard_via_port_handler) {} | ||||||
|  |  | ||||||
| 		void set_digital_input(DigitalInput digital_input, bool is_active) override { | 		std::vector<DigitalInput> get_inputs() override { | ||||||
|  | 			return { | ||||||
|  | 				DigitalInput(DigitalInput::Up), | ||||||
|  | 				DigitalInput(DigitalInput::Down), | ||||||
|  | 				DigitalInput(DigitalInput::Left), | ||||||
|  | 				DigitalInput(DigitalInput::Right), | ||||||
|  | 				DigitalInput(DigitalInput::Fire) | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_digital_input(const DigitalInput &digital_input, bool is_active) override { | ||||||
| 			JoystickInput mapped_input; | 			JoystickInput mapped_input; | ||||||
| 			switch (digital_input) { | 			switch(digital_input.type) { | ||||||
| 				default: return; | 				default: return; | ||||||
| 				case DigitalInput::Up: mapped_input = Up;		break; | 				case DigitalInput::Up: mapped_input = Up;		break; | ||||||
| 				case DigitalInput::Down: mapped_input = Down;	break; | 				case DigitalInput::Down: mapped_input = Down;	break; | ||||||
| @@ -312,9 +322,6 @@ class ConcreteMachine: | |||||||
| 			delete[] rom_; | 			delete[] rom_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data) { |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		// Obtains the system ROMs. | ||||||
| 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | 		bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override { | ||||||
| 			auto roms = roms_with_names( | 			auto roms = roms_with_names( | ||||||
| @@ -346,19 +353,19 @@ class ConcreteMachine: | |||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target &target) override final { | ||||||
| 			if(target.loading_command.length()) { | 			if(target.loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target.loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			switch(target.vic20.memory_model) { | 			switch(target.vic20.memory_model) { | ||||||
| 				case StaticAnalyser::Vic20MemoryModel::Unexpanded: | 				case Analyser::Static::Vic20MemoryModel::Unexpanded: | ||||||
| 					set_memory_size(Default); | 					set_memory_size(Default); | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::Vic20MemoryModel::EightKB: | 				case Analyser::Static::Vic20MemoryModel::EightKB: | ||||||
| 					set_memory_size(ThreeKB); | 					set_memory_size(ThreeKB); | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB: | 				case Analyser::Static::Vic20MemoryModel::ThirtyTwoKB: | ||||||
| 					set_memory_size(ThirtyTwoKB); | 					set_memory_size(ThirtyTwoKB); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| @@ -377,7 +384,7 @@ class ConcreteMachine: | |||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_->set_tape(media.tapes.front()); | 				tape_->set_tape(media.tapes.front()); | ||||||
| 			} | 			} | ||||||
| @@ -396,6 +403,8 @@ class ConcreteMachine: | |||||||
| 				write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); | 				write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  |  | ||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); | 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -516,10 +525,6 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_use_fast_tape_hack(bool activate) { |  | ||||||
| 			use_fast_tape_hack_ = activate; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | 		forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { | ||||||
| 			// run the phase-1 part of this cycle, in which the VIC accesses memory | 			// run the phase-1 part of this cycle, in which the VIC accesses memory | ||||||
| @@ -539,7 +544,7 @@ class ConcreteMachine: | |||||||
| 				// PC hits the start of the loop that just waits for an interesting tape interrupt to have | 				// PC hits the start of the loop that just waits for an interesting tape interrupt to have | ||||||
| 				// occurred then skip both 6522s and the tape ahead to the next interrupt without any further | 				// occurred then skip both 6522s and the tape ahead to the next interrupt without any further | ||||||
| 				// CPU or 6560 costs. | 				// CPU or 6560 costs. | ||||||
| 				if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU::MOS6502::BusOperation::ReadOpcode) { | 				if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { | ||||||
| 					if(address == 0xf7b2) { | 					if(address == 0xf7b2) { | ||||||
| 						// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. | 						// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. | ||||||
| 						// So cancel that via a double NOP and fill in the next header programmatically. | 						// So cancel that via a double NOP and fill in the next header programmatically. | ||||||
| @@ -662,8 +667,8 @@ class ConcreteMachine: | |||||||
| 			keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input()); | 			keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
| @@ -674,7 +679,8 @@ class ConcreteMachine: | |||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| 			bool quickload; | 			bool quickload; | ||||||
| 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
| 				set_use_fast_tape_hack(quickload); | 				allow_fast_tape_hack_ = quickload; | ||||||
|  | 				set_use_fast_tape(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -739,8 +745,12 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// Tape | 		// Tape | ||||||
| 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | 		std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_; | ||||||
| 		bool use_fast_tape_hack_; | 		bool use_fast_tape_hack_ = false; | ||||||
|  | 		bool allow_fast_tape_hack_ = false; | ||||||
| 		bool is_running_at_zero_cost_ = false; | 		bool is_running_at_zero_cost_ = false; | ||||||
|  | 		void set_use_fast_tape() { | ||||||
|  | 			use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_->has_tape(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// Disk | 		// Disk | ||||||
| 		std::shared_ptr<::Commodore::C1540::Machine> c1540_; | 		std::shared_ptr<::Commodore::C1540::Machine> c1540_; | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #ifndef ConfigurationTarget_hpp | #ifndef ConfigurationTarget_hpp | ||||||
| #define ConfigurationTarget_hpp | #define ConfigurationTarget_hpp | ||||||
|  |  | ||||||
| #include "../StaticAnalyser/StaticAnalyser.hpp" | #include "../Analyser/Static/StaticAnalyser.hpp" | ||||||
| #include "../Configurable/Configurable.hpp" | #include "../Configurable/Configurable.hpp" | ||||||
|  |  | ||||||
| #include <string> | #include <string> | ||||||
| @@ -17,20 +17,20 @@ | |||||||
| namespace ConfigurationTarget { | namespace ConfigurationTarget { | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target | 	A ConfigurationTarget::Machine is anything that can accept a Analyser::Static::Target | ||||||
| 	and configure itself appropriately, or accept a list of media subsequently to insert. | 	and configure itself appropriately, or accept a list of media subsequently to insert. | ||||||
| */ | */ | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		/// Instructs the machine to configure itself as described by @c target and insert the included media. | 		/// Instructs the machine to configure itself as described by @c target and insert the included media. | ||||||
| 		virtual void configure_as_target(const StaticAnalyser::Target &target) = 0; | 		virtual void configure_as_target(const Analyser::Static::Target &target) = 0; | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Requests that the machine insert @c media as a modification to current state | 			Requests that the machine insert @c media as a modification to current state | ||||||
|  |  | ||||||
| 			@returns @c true if any media was inserted; @c false otherwise. | 			@returns @c true if any media was inserted; @c false otherwise. | ||||||
| 		*/ | 		*/ | ||||||
| 		virtual bool insert_media(const StaticAnalyser::Media &media) = 0; | 		virtual bool insert_media(const Analyser::Static::Media &media) = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								Machines/DynamicMachine.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Machines/DynamicMachine.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | // | ||||||
|  | //  DynamicMachine.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef DynamicMachine_h | ||||||
|  | #define DynamicMachine_h | ||||||
|  |  | ||||||
|  | #include "../Configurable/Configurable.hpp" | ||||||
|  | #include "ConfigurationTarget.hpp" | ||||||
|  | #include "CRTMachine.hpp" | ||||||
|  | #include "JoystickMachine.hpp" | ||||||
|  | #include "KeyboardMachine.hpp" | ||||||
|  | #include "Utility/Typer.hpp" | ||||||
|  |  | ||||||
|  | namespace Machine { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides the structure for owning a machine and dynamically casting it as desired without knowledge of | ||||||
|  | 	the machine's parent class or, therefore, the need to establish a common one. | ||||||
|  | */ | ||||||
|  | struct DynamicMachine { | ||||||
|  | 	virtual ~DynamicMachine() {} | ||||||
|  | 	virtual ConfigurationTarget::Machine *configuration_target() = 0; | ||||||
|  | 	virtual CRTMachine::Machine *crt_machine() = 0; | ||||||
|  | 	virtual JoystickMachine::Machine *joystick_machine() = 0; | ||||||
|  | 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | ||||||
|  | 	virtual Configurable::Device *configurable_device() = 0; | ||||||
|  |  | ||||||
|  | 	/*! | ||||||
|  | 		Provides a raw pointer to the underlying machine if and only if this dynamic machine really is | ||||||
|  | 		only a single machine. | ||||||
|  |  | ||||||
|  | 		Very unsafe. Very temporary. | ||||||
|  |  | ||||||
|  | 		TODO: eliminate in favour of introspection for machine-specific inputs. This is here temporarily | ||||||
|  | 		only to permit continuity of certain features in the Mac port that have not yet made their way | ||||||
|  | 		to the SDL/console port. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void *raw_pointer() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* DynamicMachine_h */ | ||||||
| @@ -66,7 +66,13 @@ class ConcreteMachine: | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			std::memcpy(target, &data[0], std::min(static_cast<std::size_t>(16384), data.size())); | 			// Copy in, with mirroring. | ||||||
|  | 			std::size_t rom_ptr = 0; | ||||||
|  | 			while(rom_ptr < 16384) { | ||||||
|  | 				std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size()); | ||||||
|  | 				std::memcpy(&target[rom_ptr], data.data(), size_to_copy); | ||||||
|  | 				rom_ptr += size_to_copy; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 		// Obtains the system ROMs. | ||||||
| @@ -109,11 +115,7 @@ class ConcreteMachine: | |||||||
| 			if(is_holding_shift_) set_key_state(KeyShift, true); | 			if(is_holding_shift_) set_key_state(KeyShift, true); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_use_fast_tape_hack(bool activate) { | 		void configure_as_target(const Analyser::Static::Target &target) override final { | ||||||
| 			use_fast_tape_hack_ = activate; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final { |  | ||||||
| 			if(target.loading_command.length()) { | 			if(target.loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target.loading_command); | ||||||
| 			} | 			} | ||||||
| @@ -137,7 +139,7 @@ class ConcreteMachine: | |||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_.set_tape(media.tapes.front()); | 				tape_.set_tape(media.tapes.front()); | ||||||
| 			} | 			} | ||||||
| @@ -152,6 +154,7 @@ class ConcreteMachine: | |||||||
| 				slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15); | 				slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape_hack(); | ||||||
| 			return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty(); | 			return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -277,7 +280,6 @@ class ConcreteMachine: | |||||||
| 							if(isReadOperation(operation)) { | 							if(isReadOperation(operation)) { | ||||||
| 								if( | 								if( | ||||||
| 									use_fast_tape_hack_ && | 									use_fast_tape_hack_ && | ||||||
| 									tape_.has_tape() && |  | ||||||
| 									(operation == CPU::MOS6502::BusOperation::ReadOpcode) && | 									(operation == CPU::MOS6502::BusOperation::ReadOpcode) && | ||||||
| 									( | 									( | ||||||
| 										(address == 0xf4e5) || (address == 0xf4e6) ||	// double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 | 										(address == 0xf4e5) || (address == 0xf4e6) ||	// double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 | ||||||
| @@ -419,8 +421,8 @@ class ConcreteMachine: | |||||||
| 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
| @@ -431,7 +433,8 @@ class ConcreteMachine: | |||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| 			bool quickload; | 			bool quickload; | ||||||
| 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
| 				set_use_fast_tape_hack(quickload); | 				allow_fast_tape_hack_ = quickload; | ||||||
|  | 				set_use_fast_tape_hack(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Configurable::Display display; | 			Configurable::Display display; | ||||||
| @@ -520,6 +523,10 @@ class ConcreteMachine: | |||||||
| 		// Tape | 		// Tape | ||||||
| 		Tape tape_; | 		Tape tape_; | ||||||
| 		bool use_fast_tape_hack_ = false; | 		bool use_fast_tape_hack_ = false; | ||||||
|  | 		bool allow_fast_tape_hack_ = false; | ||||||
|  | 		void set_use_fast_tape_hack() { | ||||||
|  | 			use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_.has_tape(); | ||||||
|  | 		} | ||||||
| 		bool fast_load_is_in_data_ = false; | 		bool fast_load_is_in_data_ = false; | ||||||
|  |  | ||||||
| 		// Disk | 		// Disk | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ Machine::Machine() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) { | ||||||
| 	uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key); | 	uint16_t mapped_key = get_keyboard_mapper()->mapped_key_for_key(key); | ||||||
| 	if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed); | 	if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -30,3 +30,7 @@ Inputs::Keyboard &Machine::get_keyboard() { | |||||||
|  |  | ||||||
| void Machine::type_string(const std::string &) { | void Machine::type_string(const std::string &) { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Machine::KeyboardMapper *Machine::get_keyboard_mapper() { | ||||||
|  | 	return nullptr; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -61,12 +61,11 @@ class Machine: public Inputs::Keyboard::Delegate { | |||||||
| 		*/ | 		*/ | ||||||
| 		static const uint16_t KeyNotMapped = 0xfffe; | 		static const uint16_t KeyNotMapped = 0xfffe; | ||||||
|  |  | ||||||
| 	protected: |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Allows individual machines to provide the mapping between host keys | 			Allows individual machines to provide the mapping between host keys | ||||||
| 			as per Inputs::Keyboard and their native scheme. | 			as per Inputs::Keyboard and their native scheme. | ||||||
| 		*/ | 		*/ | ||||||
| 		virtual KeyboardMapper &get_keyboard_mapper() = 0; | 		virtual KeyboardMapper *get_keyboard_mapper(); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; | 		void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; | ||||||
|   | |||||||
| @@ -19,18 +19,31 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler { | |||||||
| 		ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | 		ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
| 			map_(map), slot_(slot) {} | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
| 		void write(uint16_t address, uint8_t value) { | 		void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override { | ||||||
|  | //			printf("A16 %04x ", address); | ||||||
| 			switch(address >> 11) { | 			switch(address >> 11) { | ||||||
| 				default: break; | 				default: | ||||||
|  | 					if(pc_is_outside_bios) confidence_counter_.add_miss(); | ||||||
|  | 				break; | ||||||
| 				case 0xc: | 				case 0xc: | ||||||
| 					map_.map(slot_, value * 8192, 0x4000, 0x4000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x4000, 0x4000, 0x4000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xe: | 				case 0xe: | ||||||
| 					map_.map(slot_, value * 8192, 0x8000, 0x4000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x4000, 0x8000, 0x4000); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		virtual void print_type() override { | ||||||
|  | 			printf("A16"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		MSX::MemoryMap &map_; | 		MSX::MemoryMap &map_; | ||||||
| 		int slot_; | 		int slot_; | ||||||
|   | |||||||
| @@ -19,24 +19,43 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler { | |||||||
| 		ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | 		ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
| 			map_(map), slot_(slot) {} | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
| 		void write(uint16_t address, uint8_t value) { | 		void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override { | ||||||
|  | //			printf("A8 %04x ", address); | ||||||
| 			switch(address >> 11) { | 			switch(address >> 11) { | ||||||
| 				default: break; | 				default: | ||||||
|  | 					if(pc_is_outside_bios) confidence_counter_.add_miss(); | ||||||
|  | 				break; | ||||||
| 				case 0xc: | 				case 0xc: | ||||||
| 					map_.map(slot_, value * 8192, 0x4000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x4000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xd: | 				case 0xd: | ||||||
| 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x6000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xe: | 				case 0xe: | ||||||
| 					map_.map(slot_, value * 8192, 0x8000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x8000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0xf: | 				case 0xf: | ||||||
| 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0xa000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		virtual void print_type() override { | ||||||
|  | 			printf("A8"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		MSX::MemoryMap &map_; | 		MSX::MemoryMap &map_; | ||||||
| 		int slot_; | 		int slot_; | ||||||
|   | |||||||
| @@ -19,21 +19,36 @@ class KonamiROMSlotHandler: public ROMSlotHandler { | |||||||
| 		KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) : | 		KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) : | ||||||
| 			map_(map), slot_(slot) {} | 			map_(map), slot_(slot) {} | ||||||
|  |  | ||||||
| 		void write(uint16_t address, uint8_t value) { | 		void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override { | ||||||
|  | //			printf("K %04x[%c]\n", address, pc_is_outside_bios ? '.' : '+'); | ||||||
| 			switch(address >> 13) { | 			switch(address >> 13) { | ||||||
| 				default: break; | 				default: | ||||||
|  | 					if(pc_is_outside_bios) confidence_counter_.add_miss(); | ||||||
|  | 				break; | ||||||
| 				case 3: | 				case 3: | ||||||
| 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x6000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 4: | 				case 4: | ||||||
| 					map_.map(slot_, value * 8192, 0x8000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x8000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 5: | 				case 5: | ||||||
| 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0xa000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		virtual void print_type() override { | ||||||
|  | 			printf("K"); | ||||||
|  | 		} | ||||||
| 	private: | 	private: | ||||||
| 		MSX::MemoryMap &map_; | 		MSX::MemoryMap &map_; | ||||||
| 		int slot_; | 		int slot_; | ||||||
|   | |||||||
| @@ -20,40 +20,66 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { | |||||||
| 		KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) : | 		KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) : | ||||||
| 			map_(map), slot_(slot), scc_(scc) {} | 			map_(map), slot_(slot), scc_(scc) {} | ||||||
|  |  | ||||||
| 		void write(uint16_t address, uint8_t value) override { | 		void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override { | ||||||
|  | //			printf("KSCC %04x ", address); | ||||||
| 			switch(address >> 11) { | 			switch(address >> 11) { | ||||||
| 				default: break; | 				default: | ||||||
|  | 					if(pc_is_outside_bios) confidence_counter_.add_miss(); | ||||||
|  | 				break; | ||||||
| 				case 0x0a: | 				case 0x0a: | ||||||
| 					map_.map(slot_, value * 8192, 0x4000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x4000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0x0e: | 				case 0x0e: | ||||||
| 					map_.map(slot_, value * 8192, 0x6000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0x6000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 				case 0x12: | 				case 0x12: | ||||||
|  | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0x9000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
| 					if((value&0x3f) == 0x3f) { | 					if((value&0x3f) == 0x3f) { | ||||||
| 						scc_is_visible_ = true; | 						scc_is_visible_ = true; | ||||||
| 						map_.unmap(slot_, 0x8000, 0x2000); | 						map_.unmap(slot_, 0x8000, 0x2000); | ||||||
| 					} else { | 					} else { | ||||||
| 						scc_is_visible_ = false; | 						scc_is_visible_ = false; | ||||||
| 						map_.map(slot_, value * 8192, 0x8000, 0x2000); | 						map_.map(slot_, value * 0x2000, 0x8000, 0x2000); | ||||||
| 					} | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case 0x13: | 				case 0x13: | ||||||
| 					if(scc_is_visible_) scc_.write(address, value); | 					if(scc_is_visible_) { | ||||||
|  | 						if(pc_is_outside_bios) confidence_counter_.add_hit(); | ||||||
|  | 						scc_.write(address, value); | ||||||
|  | 					} else { | ||||||
|  | 						if(pc_is_outside_bios) confidence_counter_.add_miss(); | ||||||
|  | 					} | ||||||
| 				break; | 				break; | ||||||
| 				case 0x16: | 				case 0x16: | ||||||
| 					map_.map(slot_, value * 8192, 0xa000, 0x2000); | 					if(pc_is_outside_bios) { | ||||||
|  | 						if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); | ||||||
|  | 					} | ||||||
|  | 					map_.map(slot_, value * 0x2000, 0xa000, 0x2000); | ||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		uint8_t read(uint16_t address) override { | 		uint8_t read(uint16_t address) override { | ||||||
| 			if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) { | 			if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) { | ||||||
|  | 				confidence_counter_.add_hit(); | ||||||
| 				return scc_.read(address); | 				return scc_.read(address); | ||||||
| 			} | 			} | ||||||
|  | 			confidence_counter_.add_miss(); | ||||||
| 			return 0xff; | 			return 0xff; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		virtual void print_type() override { | ||||||
|  | 			printf("KSCC"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		MSX::MemoryMap &map_; | 		MSX::MemoryMap &map_; | ||||||
| 		int slot_; | 		int slot_; | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ DiskROM::DiskROM(const std::vector<uint8_t> &rom) : | |||||||
| 	set_is_double_density(true); | 	set_is_double_density(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiskROM::write(uint16_t address, uint8_t value) { | void DiskROM::write(uint16_t address, uint8_t value, bool pc_is_outside_bios) { | ||||||
| 	switch(address) { | 	switch(address) { | ||||||
| 		case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb: | 		case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb: | ||||||
| 			set_register(address, value); | 			set_register(address, value); | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ class DiskROM: public ROMSlotHandler, public WD::WD1770 { | |||||||
| 	public: | 	public: | ||||||
| 		DiskROM(const std::vector<uint8_t> &rom); | 		DiskROM(const std::vector<uint8_t> &rom); | ||||||
|  |  | ||||||
| 		void write(uint16_t address, uint8_t value) override; | 		void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) override; | ||||||
| 		uint8_t read(uint16_t address) override; | 		uint8_t read(uint16_t address) override; | ||||||
| 		void run_for(HalfCycles half_cycles) override; | 		void run_for(HalfCycles half_cycles) override; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ | |||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
| #include "ROMSlotHandler.hpp" | #include "ROMSlotHandler.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/MSX/Cartridge.hpp" | ||||||
| #include "Cartridges/ASCII8kb.hpp" | #include "Cartridges/ASCII8kb.hpp" | ||||||
| #include "Cartridges/ASCII16kb.hpp" | #include "Cartridges/ASCII16kb.hpp" | ||||||
| #include "Cartridges/Konami.hpp" | #include "Cartridges/Konami.hpp" | ||||||
| @@ -37,6 +38,7 @@ | |||||||
| #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" | ||||||
|  |  | ||||||
| #include "../../Configurable/StandardOptions.hpp" | #include "../../Configurable/StandardOptions.hpp" | ||||||
|  | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
|  |  | ||||||
| namespace MSX { | namespace MSX { | ||||||
|  |  | ||||||
| @@ -115,7 +117,8 @@ class ConcreteMachine: | |||||||
| 	public ConfigurationTarget::Machine, | 	public ConfigurationTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public MemoryMap { | 	public MemoryMap, | ||||||
|  | 	public Sleeper::SleepObserver { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(): | 		ConcreteMachine(): | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| @@ -134,6 +137,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			ay_.set_port_handler(&ay_port_handler_); | 			ay_.set_port_handler(&ay_port_handler_); | ||||||
| 			speaker_.set_input_rate(3579545.0f / 2.0f); | 			speaker_.set_input_rate(3579545.0f / 2.0f); | ||||||
|  | 			tape_player_.set_sleep_observer(this); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| @@ -156,7 +160,21 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override { | 		float get_confidence() override { | ||||||
|  | 			if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f; | ||||||
|  | 			if(memory_slots_[1].handler) { | ||||||
|  | 				return memory_slots_[1].handler->get_confidence(); | ||||||
|  | 			} | ||||||
|  | 			return 0.5f; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void print_type() override { | ||||||
|  | 			if(memory_slots_[1].handler) { | ||||||
|  | 				memory_slots_[1].handler->print_type(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void configure_as_target(const Analyser::Static::Target &target) override { | ||||||
| 			// Add a disk cartridge if any disks were supplied. | 			// Add a disk cartridge if any disks were supplied. | ||||||
| 			if(!target.media.disks.empty()) { | 			if(!target.media.disks.empty()) { | ||||||
| 				map(2, 0, 0x4000, 0x2000); | 				map(2, 0, 0x4000, 0x2000); | ||||||
| @@ -171,30 +189,32 @@ class ConcreteMachine: | |||||||
| 			if(target.loading_command.length()) { | 			if(target.loading_command.length()) { | ||||||
| 				type_string(target.loading_command); | 				type_string(target.loading_command); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Attach the hardware necessary for a game cartridge, if any. |  | ||||||
| 			switch(target.msx.cartridge_type) { |  | ||||||
| 				default: break; |  | ||||||
| 				case StaticAnalyser::MSXCartridgeType::Konami: |  | ||||||
| 					memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); |  | ||||||
| 				break; |  | ||||||
| 				case StaticAnalyser::MSXCartridgeType::KonamiWithSCC: |  | ||||||
| 					memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); |  | ||||||
| 				break; |  | ||||||
| 				case StaticAnalyser::MSXCartridgeType::ASCII8kb: |  | ||||||
| 					memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); |  | ||||||
| 				break; |  | ||||||
| 				case StaticAnalyser::MSXCartridgeType::ASCII16kb: |  | ||||||
| 					memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			if(!media.cartridges.empty()) { | 			if(!media.cartridges.empty()) { | ||||||
| 				const auto &segment = media.cartridges.front()->get_segments().front(); | 				const auto &segment = media.cartridges.front()->get_segments().front(); | ||||||
| 				memory_slots_[1].source = segment.data; | 				memory_slots_[1].source = segment.data; | ||||||
| 				map(1, 0, static_cast<uint16_t>(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); | 				map(1, 0, static_cast<uint16_t>(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); | ||||||
|  |  | ||||||
|  | 				auto msx_cartridge = dynamic_cast<Analyser::Static::MSX::Cartridge *>(media.cartridges.front().get()); | ||||||
|  | 				if(msx_cartridge) { | ||||||
|  | 					switch(msx_cartridge->type) { | ||||||
|  | 						default: break; | ||||||
|  | 						case Analyser::Static::MSX::Cartridge::Konami: | ||||||
|  | 							memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); | ||||||
|  | 						break; | ||||||
|  | 						case Analyser::Static::MSX::Cartridge::KonamiWithSCC: | ||||||
|  | 							memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); | ||||||
|  | 						break; | ||||||
|  | 						case Analyser::Static::MSX::Cartridge::ASCII8kb: | ||||||
|  | 							memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); | ||||||
|  | 						break; | ||||||
|  | 						case Analyser::Static::MSX::Cartridge::ASCII16kb: | ||||||
|  | 							memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| @@ -211,6 +231,8 @@ class ConcreteMachine: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  |  | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -259,18 +281,11 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: Z80::BusHandler | 		// MARK: Z80::BusHandler | ||||||
| 		HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			if(time_until_interrupt_ > 0) { |  | ||||||
| 				time_until_interrupt_ -= cycle.length; |  | ||||||
| 				if(time_until_interrupt_ <= HalfCycles(0)) { |  | ||||||
| 					z80_.set_interrupt_line(true, time_until_interrupt_); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | 			uint16_t address = cycle.address ? *cycle.address : 0x0000; | ||||||
| 			switch(cycle.operation) { | 			switch(cycle.operation) { | ||||||
| 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
| 					if(use_fast_tape_ && tape_player_.has_tape()) { | 					if(use_fast_tape_) { | ||||||
| 						if(address == 0x1a63) { | 						if(address == 0x1a63) { | ||||||
| 							// TAPION | 							// TAPION | ||||||
|  |  | ||||||
| @@ -324,6 +339,14 @@ class ConcreteMachine: | |||||||
| 							break; | 							break; | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					if(!address) { | ||||||
|  | 						pc_zero_accesses_++; | ||||||
|  | 					} | ||||||
|  | 					if(read_pointers_[address >> 13] == unpopulated_) { | ||||||
|  | 						performed_unmapped_access_ = true; | ||||||
|  | 					} | ||||||
|  | 					pc_address_ = address;	// This is retained so as to be able to name the source of an access to cartridge handlers. | ||||||
| 				case CPU::Z80::PartialMachineCycle::Read: | 				case CPU::Z80::PartialMachineCycle::Read: | ||||||
| 					if(read_pointers_[address >> 13]) { | 					if(read_pointers_[address >> 13]) { | ||||||
| 						*cycle.value = read_pointers_[address >> 13][address & 8191]; | 						*cycle.value = read_pointers_[address >> 13][address & 8191]; | ||||||
| @@ -341,7 +364,7 @@ class ConcreteMachine: | |||||||
| 					if(memory_slots_[slot_hit].handler) { | 					if(memory_slots_[slot_hit].handler) { | ||||||
| 						update_audio(); | 						update_audio(); | ||||||
| 						memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); | 						memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); | ||||||
| 						memory_slots_[slot_hit].handler->write(address, *cycle.value); | 						memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); | ||||||
| 					} | 					} | ||||||
| 				} break; | 				} break; | ||||||
|  |  | ||||||
| @@ -433,18 +456,26 @@ class ConcreteMachine: | |||||||
| 				default: break; | 				default: break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Update the tape. (TODO: allow for sleeping) | 			if(!tape_player_is_sleeping_) | ||||||
| 			tape_player_.run_for(cycle.length.as_int()); | 				tape_player_.run_for(cycle.length.as_int()); | ||||||
|  |  | ||||||
| 			// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, | 			// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, | ||||||
| 			// but otherwise runs without pause. | 			// but otherwise runs without pause. | ||||||
| 			HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); | 			const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); | ||||||
| 			time_since_vdp_update_ += cycle.length + addition; | 			const HalfCycles total_length = addition + cycle.length; | ||||||
| 			time_since_ay_update_ += cycle.length + addition; |  | ||||||
| 			memory_slots_[0].cycles_since_update  += cycle.length + addition; | 			if(time_until_interrupt_ > 0) { | ||||||
| 			memory_slots_[1].cycles_since_update  += cycle.length + addition; | 				time_until_interrupt_ -= total_length; | ||||||
| 			memory_slots_[2].cycles_since_update  += cycle.length + addition; | 				if(time_until_interrupt_ <= HalfCycles(0)) { | ||||||
| 			memory_slots_[3].cycles_since_update  += cycle.length + addition; | 					z80_.set_interrupt_line(true, time_until_interrupt_); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			time_since_vdp_update_ += total_length; | ||||||
|  | 			time_since_ay_update_ += total_length; | ||||||
|  | 			memory_slots_[0].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[1].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[2].cycles_since_update += total_length; | ||||||
|  | 			memory_slots_[3].cycles_since_update += total_length; | ||||||
| 			return addition; | 			return addition; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -505,8 +536,8 @@ class ConcreteMachine: | |||||||
| 			if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask; | 			if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
| @@ -517,7 +548,8 @@ class ConcreteMachine: | |||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| 			bool quickload; | 			bool quickload; | ||||||
| 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
| 				use_fast_tape_ = quickload; | 				allow_fast_tape_ = quickload; | ||||||
|  | 				set_use_fast_tape(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Configurable::Display display; | 			Configurable::Display display; | ||||||
| @@ -540,6 +572,12 @@ class ConcreteMachine: | |||||||
| 			return selection_set; | 			return selection_set; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Sleeper | ||||||
|  | 		void set_component_is_sleeping(void *component, bool is_sleeping) override { | ||||||
|  | 			tape_player_is_sleeping_ = tape_player_.is_sleeping(); | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_audio() { | 		void update_audio() { | ||||||
| 			speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); | 			speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); | ||||||
| @@ -600,7 +638,12 @@ class ConcreteMachine: | |||||||
| 		Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC>> speaker_; | 		Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC>> speaker_; | ||||||
|  |  | ||||||
| 		Storage::Tape::BinaryTapePlayer tape_player_; | 		Storage::Tape::BinaryTapePlayer tape_player_; | ||||||
|  | 		bool tape_player_is_sleeping_ = false; | ||||||
|  | 		bool allow_fast_tape_ = false; | ||||||
| 		bool use_fast_tape_ = false; | 		bool use_fast_tape_ = false; | ||||||
|  | 		void set_use_fast_tape() { | ||||||
|  | 			use_fast_tape_ = !tape_player_is_sleeping_ && allow_fast_tape_ && tape_player_.has_tape(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		i8255PortHandler i8255_port_handler_; | 		i8255PortHandler i8255_port_handler_; | ||||||
| 		AYPortHandler ay_port_handler_; | 		AYPortHandler ay_port_handler_; | ||||||
| @@ -637,6 +680,10 @@ class ConcreteMachine: | |||||||
| 		std::string input_text_; | 		std::string input_text_; | ||||||
|  |  | ||||||
| 		MSX::KeyboardMapper keyboard_mapper_; | 		MSX::KeyboardMapper keyboard_mapper_; | ||||||
|  |  | ||||||
|  | 		int pc_zero_accesses_ = 0; | ||||||
|  | 		bool performed_unmapped_access_ = false; | ||||||
|  | 		uint16_t pc_address_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #define ROMSlotHandler_hpp | #define ROMSlotHandler_hpp | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../Analyser/Dynamic/ConfidenceCounter.hpp" | ||||||
|  |  | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| @@ -46,7 +47,7 @@ class ROMSlotHandler { | |||||||
| 		virtual void run_for(HalfCycles half_cycles) {} | 		virtual void run_for(HalfCycles half_cycles) {} | ||||||
|  |  | ||||||
| 		/*! Announces an attempt to write @c value to @c address. */ | 		/*! Announces an attempt to write @c value to @c address. */ | ||||||
| 		virtual void write(uint16_t address, uint8_t value) = 0; | 		virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0; | ||||||
|  |  | ||||||
| 		/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ | 		/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ | ||||||
| 		virtual uint8_t read(uint16_t address) { return 0xff; } | 		virtual uint8_t read(uint16_t address) { return 0xff; } | ||||||
| @@ -57,12 +58,22 @@ class ROMSlotHandler { | |||||||
| 			/// Empty causes all out-of-bounds accesses to read a vacant bus. | 			/// Empty causes all out-of-bounds accesses to read a vacant bus. | ||||||
| 			Empty | 			Empty | ||||||
| 		}; | 		}; | ||||||
| 		/*! |  | ||||||
| 			Returns the wrapping strategy to apply to mapping requests from this ROM slot. | 		/*! @returns The wrapping strategy to apply to mapping requests from this ROM slot. */ | ||||||
| 		*/ |  | ||||||
| 		virtual WrappingStrategy wrapping_strategy() const { | 		virtual WrappingStrategy wrapping_strategy() const { | ||||||
| 			return WrappingStrategy::Repeat; | 			return WrappingStrategy::Repeat; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/*! @returns The probability that this handler is correct for the data it owns. */ | ||||||
|  | 		float get_confidence() { | ||||||
|  | 			return confidence_counter_.get_confidence(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		virtual void print_type() { | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	protected: | ||||||
|  | 		Analyser::Dynamic::ConfidenceCounter confidence_counter_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -254,7 +254,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy ConfigurationTarget::Machine | 		// to satisfy ConfigurationTarget::Machine | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target &target) override final { | ||||||
| 			if(target.oric.has_microdisc) { | 			if(target.oric.has_microdisc) { | ||||||
| 				microdisc_is_enabled_ = true; | 				microdisc_is_enabled_ = true; | ||||||
| 				microdisc_did_change_paging_flags(µdisc_); | 				microdisc_did_change_paging_flags(µdisc_); | ||||||
| @@ -284,7 +284,7 @@ class ConcreteMachine: | |||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(media.tapes.size()) { | 			if(media.tapes.size()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| 			} | 			} | ||||||
| @@ -432,8 +432,8 @@ class ConcreteMachine: | |||||||
| 			set_interrupt_line(); | 			set_interrupt_line(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
|   | |||||||
| @@ -15,11 +15,13 @@ | |||||||
|  |  | ||||||
| namespace ROMMachine { | namespace ROMMachine { | ||||||
|  |  | ||||||
|  | typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher; | ||||||
|  |  | ||||||
| struct Machine { | struct Machine { | ||||||
| 	/*! | 	/*! | ||||||
| 		Provides the machine with a way to obtain such ROMs as it needs. | 		Provides the machine with a way to obtain such ROMs as it needs. | ||||||
| 	*/ | 	*/ | ||||||
| 	virtual bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &rom_with_name) { return true; } | 	virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,51 +10,107 @@ | |||||||
|  |  | ||||||
| #include "../AmstradCPC/AmstradCPC.hpp" | #include "../AmstradCPC/AmstradCPC.hpp" | ||||||
| #include "../Atari2600/Atari2600.hpp" | #include "../Atari2600/Atari2600.hpp" | ||||||
|  | #include "../ColecoVision/ColecoVision.hpp" | ||||||
| #include "../Commodore/Vic-20/Vic20.hpp" | #include "../Commodore/Vic-20/Vic20.hpp" | ||||||
| #include "../Electron/Electron.hpp" | #include "../Electron/Electron.hpp" | ||||||
| #include "../MSX/MSX.hpp" | #include "../MSX/MSX.hpp" | ||||||
| #include "../Oric/Oric.hpp" | #include "../Oric/Oric.hpp" | ||||||
| #include "../ZX8081/ZX8081.hpp" | #include "../ZX8081/ZX8081.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Dynamic/MultiMachine/MultiMachine.hpp" | ||||||
| #include "TypedDynamicMachine.hpp" | #include "TypedDynamicMachine.hpp" | ||||||
|  |  | ||||||
| ::Machine::DynamicMachine *::Machine::MachineForTarget(const StaticAnalyser::Target &target) { | namespace { | ||||||
| 	switch(target.machine) { |  | ||||||
| 		case StaticAnalyser::Target::AmstradCPC:	return new TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); |  | ||||||
| 		case StaticAnalyser::Target::Atari2600:		return new TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); |  | ||||||
| 		case StaticAnalyser::Target::Electron:		return new TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); |  | ||||||
| 		case StaticAnalyser::Target::MSX:			return new TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); |  | ||||||
| 		case StaticAnalyser::Target::Oric:			return new TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric()); |  | ||||||
| 		case StaticAnalyser::Target::Vic20:			return new TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20()); |  | ||||||
| 		case StaticAnalyser::Target::ZX8081:		return new TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target)); |  | ||||||
|  |  | ||||||
| 		default:	return nullptr; | ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { | ||||||
|  | 	error = Machine::Error::None; | ||||||
|  | 	::Machine::DynamicMachine *machine = nullptr; | ||||||
|  | 	switch(target.machine) { | ||||||
|  | 		case Analyser::Machine::AmstradCPC:		machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());				break; | ||||||
|  | 		case Analyser::Machine::Atari2600:		machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());				break; | ||||||
|  | 		case Analyser::Machine::ColecoVision:	machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision());	break; | ||||||
|  | 		case Analyser::Machine::Electron:		machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());					break; | ||||||
|  | 		case Analyser::Machine::MSX:			machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());									break; | ||||||
|  | 		case Analyser::Machine::Oric:			machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric());								break; | ||||||
|  | 		case Analyser::Machine::Vic20:			machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());		break; | ||||||
|  | 		case Analyser::Machine::ZX8081:			machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));					break; | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			error = Machine::Error::UnknownMachine; | ||||||
|  | 		return nullptr; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine. | ||||||
|  | 	CRTMachine::Machine *crt_machine = machine->crt_machine(); | ||||||
|  | 	if(crt_machine) { | ||||||
|  | 		if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) { | ||||||
|  | 			delete machine; | ||||||
|  | 			error = Machine::Error::MissingROM; | ||||||
|  | 			return nullptr; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); | ||||||
|  | 	if(configuration_target) { | ||||||
|  | 		machine->configuration_target()->configure_as_target(target); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return machine; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Machine::ShortNameForTargetMachine(const StaticAnalyser::Target::Machine machine) { | } | ||||||
|  |  | ||||||
|  | ::Machine::DynamicMachine *::Machine::MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) { | ||||||
|  | 	// Zero targets implies no machine. | ||||||
|  | 	if(targets.empty()) { | ||||||
|  | 		error = Error::NoTargets; | ||||||
|  | 		return nullptr; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If there's more than one target, get all the machines and combine them into a multimachine. | ||||||
|  | 	if(targets.size() > 1) { | ||||||
|  | 		std::vector<std::unique_ptr<Machine::DynamicMachine>> machines; | ||||||
|  | 		for(const auto &target: targets) { | ||||||
|  | 			machines.emplace_back(MachineForTarget(*target, rom_fetcher, error)); | ||||||
|  |  | ||||||
|  | 			// Exit early if any errors have occurred. | ||||||
|  | 			if(error != Error::None) { | ||||||
|  | 				return nullptr; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return new Analyser::Dynamic::MultiMachine(std::move(machines)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// There's definitely exactly one target. | ||||||
|  | 	return MachineForTarget(*targets.front(), rom_fetcher, error); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) { | ||||||
| 	switch(machine) { | 	switch(machine) { | ||||||
| 		case StaticAnalyser::Target::AmstradCPC:	return "AmstradCPC"; | 		case Analyser::Machine::AmstradCPC:		return "AmstradCPC"; | ||||||
| 		case StaticAnalyser::Target::Atari2600:		return "Atari2600"; | 		case Analyser::Machine::Atari2600:		return "Atari2600"; | ||||||
| 		case StaticAnalyser::Target::Electron:		return "Electron"; | 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | ||||||
| 		case StaticAnalyser::Target::MSX:			return "MSX"; | 		case Analyser::Machine::Electron:		return "Electron"; | ||||||
| 		case StaticAnalyser::Target::Oric:			return "Oric"; | 		case Analyser::Machine::MSX:			return "MSX"; | ||||||
| 		case StaticAnalyser::Target::Vic20:			return "Vic20"; | 		case Analyser::Machine::Oric:			return "Oric"; | ||||||
| 		case StaticAnalyser::Target::ZX8081:		return "ZX8081"; | 		case Analyser::Machine::Vic20:			return "Vic20"; | ||||||
|  | 		case Analyser::Machine::ZX8081:			return "ZX8081"; | ||||||
|  |  | ||||||
| 		default:	return ""; | 		default:	return ""; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine machine) { | std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { | ||||||
| 	switch(machine) { | 	switch(machine) { | ||||||
| 		case StaticAnalyser::Target::AmstradCPC:	return "Amstrad CPC"; | 		case Analyser::Machine::AmstradCPC:		return "Amstrad CPC"; | ||||||
| 		case StaticAnalyser::Target::Atari2600:		return "Atari 2600"; | 		case Analyser::Machine::Atari2600:		return "Atari 2600"; | ||||||
| 		case StaticAnalyser::Target::Electron:		return "Acorn Electron"; | 		case Analyser::Machine::ColecoVision:	return "ColecoVision"; | ||||||
| 		case StaticAnalyser::Target::MSX:			return "MSX"; | 		case Analyser::Machine::Electron:		return "Acorn Electron"; | ||||||
| 		case StaticAnalyser::Target::Oric:			return "Oric"; | 		case Analyser::Machine::MSX:			return "MSX"; | ||||||
| 		case StaticAnalyser::Target::Vic20:			return "Vic 20"; | 		case Analyser::Machine::Oric:			return "Oric"; | ||||||
| 		case StaticAnalyser::Target::ZX8081:		return "ZX80/81"; | 		case Analyser::Machine::Vic20:			return "Vic 20"; | ||||||
|  | 		case Analyser::Machine::ZX8081:			return "ZX80/81"; | ||||||
|  |  | ||||||
| 		default:	return ""; | 		default:	return ""; | ||||||
| 	} | 	} | ||||||
| @@ -63,11 +119,11 @@ std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine ma | |||||||
| std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() { | std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() { | ||||||
| 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | 	std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; | ||||||
|  |  | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Electron), Electron::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::MSX), MSX::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Oric), Oric::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Vic20), Commodore::Vic20::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::ZX8081), ZX8081::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ZX8081), ZX8081::get_options())); | ||||||
|  |  | ||||||
| 	return options; | 	return options; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,52 +9,42 @@ | |||||||
| #ifndef MachineForTarget_hpp | #ifndef MachineForTarget_hpp | ||||||
| #define MachineForTarget_hpp | #define MachineForTarget_hpp | ||||||
|  |  | ||||||
| #include "../../StaticAnalyser/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../DynamicMachine.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" |  | ||||||
| #include "../JoystickMachine.hpp" |  | ||||||
| #include "../KeyboardMachine.hpp" |  | ||||||
| #include "Typer.hpp" |  | ||||||
|  |  | ||||||
| #include <map> | #include <map> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace Machine { | namespace Machine { | ||||||
|  |  | ||||||
| /*! | enum class Error { | ||||||
| 	Provides the structure for owning a machine and dynamically casting it as desired without knowledge of | 	None, | ||||||
| 	the machine's parent class or, therefore, the need to establish a common one. | 	UnknownMachine, | ||||||
| */ | 	MissingROM, | ||||||
| struct DynamicMachine { | 	NoTargets | ||||||
| 	virtual ConfigurationTarget::Machine *configuration_target() = 0; |  | ||||||
| 	virtual CRTMachine::Machine *crt_machine() = 0; |  | ||||||
| 	virtual JoystickMachine::Machine *joystick_machine() = 0; |  | ||||||
| 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; |  | ||||||
| 	virtual Configurable::Device *configurable_device() = 0; |  | ||||||
| 	virtual Utility::TypeRecipient *type_recipient() = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Allocates an instance of DynamicMachine holding a machine that can | 	Allocates an instance of DynamicMachine holding a machine that can | ||||||
| 	receive the supplied target. The machine has been allocated on the heap. | 	receive the supplied static analyser result. The machine has been allocated | ||||||
| 	It is the caller's responsibility to delete the class when finished. | 	on the heap. It is the caller's responsibility to delete the class when finished. | ||||||
| */ | */ | ||||||
| DynamicMachine *MachineForTarget(const StaticAnalyser::Target &target); | DynamicMachine *MachineForTargets(const std::vector<std::unique_ptr<Analyser::Static::Target>> &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Returns a short string name for the machine identified by the target, | 	Returns a short string name for the machine identified by the target, | ||||||
| 	which is guaranteed not to have any spaces or other potentially | 	which is guaranteed not to have any spaces or other potentially | ||||||
| 	filesystem-bothering contents. | 	filesystem-bothering contents. | ||||||
| */ | */ | ||||||
| std::string ShortNameForTargetMachine(const StaticAnalyser::Target::Machine target); | std::string ShortNameForTargetMachine(const Analyser::Machine target); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Returns a long string name for the machine identified by the target, | 	Returns a long string name for the machine identified by the target, | ||||||
| 	usable for presentation to a human. | 	usable for presentation to a human. | ||||||
| */ | */ | ||||||
| std::string LongNameForTargetMachine(const StaticAnalyser::Target::Machine target); | std::string LongNameForTargetMachine(const Analyser::Machine target); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Returns a map from machine name to the list of options that machine | 	Returns a map from machine name to the list of options that machine | ||||||
|   | |||||||
| @@ -45,8 +45,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine | |||||||
| 			return get<Configurable::Device>(); | 			return get<Configurable::Device>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Utility::TypeRecipient *type_recipient() override { | 		void *raw_pointer() override { | ||||||
| 			return get<Utility::TypeRecipient>(); | 			return get(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -158,7 +158,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
|  |  | ||||||
| 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | 				case CPU::Z80::PartialMachineCycle::ReadOpcode: | ||||||
| 					// Check for use of the fast tape hack. | 					// Check for use of the fast tape hack. | ||||||
| 					if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) { | 					if(use_fast_tape_hack_ && address == tape_trap_address_) { | ||||||
| 						uint64_t prior_offset = tape_player_.get_tape()->get_offset(); | 						uint64_t prior_offset = tape_player_.get_tape()->get_offset(); | ||||||
| 						int next_byte = parser_.get_next_byte(tape_player_.get_tape()); | 						int next_byte = parser_.get_next_byte(tape_player_.get_tape()); | ||||||
| 						if(next_byte != -1) { | 						if(next_byte != -1) { | ||||||
| @@ -238,7 +238,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const StaticAnalyser::Target &target) override final { | 		void configure_as_target(const Analyser::Static::Target &target) override final { | ||||||
| 			is_zx81_ = target.zx8081.isZX81; | 			is_zx81_ = target.zx8081.isZX81; | ||||||
| 			if(is_zx81_) { | 			if(is_zx81_) { | ||||||
| 				rom_ = zx81_rom_; | 				rom_ = zx81_rom_; | ||||||
| @@ -260,17 +260,17 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | ||||||
|  |  | ||||||
| 			switch(target.zx8081.memory_model) { | 			switch(target.zx8081.memory_model) { | ||||||
| 				case StaticAnalyser::ZX8081MemoryModel::Unexpanded: | 				case Analyser::Static::ZX8081MemoryModel::Unexpanded: | ||||||
| 					ram_.resize(1024); | 					ram_.resize(1024); | ||||||
| 					ram_base_ = 16384; | 					ram_base_ = 16384; | ||||||
| 					ram_mask_ = 1023; | 					ram_mask_ = 1023; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::ZX8081MemoryModel::SixteenKB: | 				case Analyser::Static::ZX8081MemoryModel::SixteenKB: | ||||||
| 					ram_.resize(16384); | 					ram_.resize(16384); | ||||||
| 					ram_base_ = 16384; | 					ram_base_ = 16384; | ||||||
| 					ram_mask_ = 16383; | 					ram_mask_ = 16383; | ||||||
| 				break; | 				break; | ||||||
| 				case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB: | 				case Analyser::Static::ZX8081MemoryModel::SixtyFourKB: | ||||||
| 					ram_.resize(65536); | 					ram_.resize(65536); | ||||||
| 					ram_base_ = 8192; | 					ram_base_ = 8192; | ||||||
| 					ram_mask_ = 65535; | 					ram_mask_ = 65535; | ||||||
| @@ -285,11 +285,12 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const StaticAnalyser::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			if(!media.tapes.empty()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape(); | ||||||
| 			return !media.tapes.empty(); | 			return !media.tapes.empty(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -331,9 +332,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Tape control | 		// MARK: - Tape control | ||||||
| 		void set_use_fast_tape_hack(bool activate) { |  | ||||||
| 			use_fast_tape_hack_ = activate; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_use_automatic_tape_motor_control(bool enabled) { | 		void set_use_automatic_tape_motor_control(bool enabled) { | ||||||
| 			use_automatic_tape_motor_control_ = enabled; | 			use_automatic_tape_motor_control_ = enabled; | ||||||
| @@ -341,16 +339,21 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				tape_player_.set_motor_control(false); | 				tape_player_.set_motor_control(false); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_tape_is_playing(bool is_playing) override final { | 		void set_tape_is_playing(bool is_playing) override final { | ||||||
| 			tape_player_.set_motor_control(is_playing); | 			tape_player_.set_motor_control(is_playing); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		bool get_tape_is_playing() override final { | ||||||
|  | 			return tape_player_.get_motor_control(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Typer timing | 		// MARK: - Typer timing | ||||||
| 		HalfCycles get_typer_delay() override final { return Cycles(7000000); } | 		HalfCycles get_typer_delay() override final { return Cycles(7000000); } | ||||||
| 		HalfCycles get_typer_frequency() override final { return Cycles(390000); } | 		HalfCycles get_typer_frequency() override final { return Cycles(390000); } | ||||||
|  |  | ||||||
| 		KeyboardMapper &get_keyboard_mapper() override { | 		KeyboardMapper *get_keyboard_mapper() override { | ||||||
| 			return keyboard_mapper_; | 			return &keyboard_mapper_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
| @@ -361,7 +364,8 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
| 			bool quickload; | 			bool quickload; | ||||||
| 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { | ||||||
| 				set_use_fast_tape_hack(quickload); | 				allow_fast_tape_hack_ = quickload; | ||||||
|  | 				set_use_fast_tape(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			bool autotapemotor; | 			bool autotapemotor; | ||||||
| @@ -418,6 +422,10 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		bool has_latched_video_byte_ = false; | 		bool has_latched_video_byte_ = false; | ||||||
|  |  | ||||||
| 		bool use_fast_tape_hack_ = false; | 		bool use_fast_tape_hack_ = false; | ||||||
|  | 		bool allow_fast_tape_hack_ = false; | ||||||
|  | 		void set_use_fast_tape() { | ||||||
|  | 			use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape(); | ||||||
|  | 		} | ||||||
| 		bool use_automatic_tape_motor_control_; | 		bool use_automatic_tape_motor_control_; | ||||||
| 		HalfCycles tape_advance_delay_ = 0; | 		HalfCycles tape_advance_delay_ = 0; | ||||||
|  |  | ||||||
| @@ -442,7 +450,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| using namespace ZX8081; | using namespace ZX8081; | ||||||
|  |  | ||||||
| // See header; constructs and returns an instance of the ZX80 or 81. | // See header; constructs and returns an instance of the ZX80 or 81. | ||||||
| Machine *Machine::ZX8081(const StaticAnalyser::Target &target_hint) { | Machine *Machine::ZX8081(const Analyser::Static::Target &target_hint) { | ||||||
| 	// Instantiate the correct type of machine. | 	// Instantiate the correct type of machine. | ||||||
| 	if(target_hint.zx8081.isZX81) | 	if(target_hint.zx8081.isZX81) | ||||||
| 		return new ZX8081::ConcreteMachine<true>(); | 		return new ZX8081::ConcreteMachine<true>(); | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user