mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			182 Commits
		
	
	
		
			2018-07-05
			...
			2018-09-09
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ab02f82470 | ||
|  | 1e3318816c | ||
|  | 3a3dec92c7 | ||
|  | 5a5fc1ae1a | ||
|  | 8d79a1e381 | ||
|  | d70f5da94e | ||
|  | 05d4274019 | ||
|  | afeec09902 | ||
|  | 0526ac2ee2 | ||
|  | 6725ee2190 | ||
|  | 8b661fb90f | ||
|  | dab7d3db1b | ||
|  | 1cba3d48d9 | ||
|  | d53b38ec7e | ||
|  | 5d0f47eda2 | ||
|  | 2e04c4442c | ||
|  | f639cdc8ad | ||
|  | 71ec7624ca | ||
|  | 0599d9602e | ||
|  | 234bef2a88 | ||
|  | adb574e1cd | ||
|  | 1f491e764e | ||
|  | 114a43a662 | ||
|  | 5547c39c91 | ||
|  | 97a89aaf4d | ||
|  | 61e46399dc | ||
|  | e802f6ecc2 | ||
|  | 4209f0e044 | ||
|  | 33576aa2c4 | ||
|  | 17bf1a64bf | ||
|  | f8d46f8f3d | ||
|  | 8787d85e64 | ||
|  | 7f0f17f435 | ||
|  | 0e7f54f375 | ||
|  | b3bdfa9f46 | ||
|  | 592ec69d36 | ||
|  | 60e00ddd02 | ||
|  | 6806193dc2 | ||
|  | c35dca783f | ||
|  | 901e0d65b9 | ||
|  | ddf45a0010 | ||
|  | 1eca4463b3 | ||
|  | be01203cc1 | ||
|  | 4d1d19a464 | ||
|  | 760817eb3b | ||
|  | cb47575860 | ||
|  | 434d184503 | ||
|  | 7374c665e8 | ||
|  | 10c930a59d | ||
|  | 60ab6f0c2a | ||
|  | a13eb351da | ||
|  | 4b91910fab | ||
|  | f46d52364c | ||
|  | 878c63dcd2 | ||
|  | 261fb3d4f8 | ||
|  | b63e0cff72 | ||
|  | 5d6e479338 | ||
|  | 90094529a5 | ||
|  | aed4c0539e | ||
|  | 8b50ab2593 | ||
|  | 95164b79c9 | ||
|  | 6f838fe190 | ||
|  | bb680b40d8 | ||
|  | e3f6da6994 | ||
|  | e46bde35f5 | ||
|  | 32338bea4d | ||
|  | 5c881bd19d | ||
|  | 1a44ef0469 | ||
|  | ebce9a2e51 | ||
|  | 633af4d404 | ||
|  | 76a73c835c | ||
|  | c1d1c451ef | ||
|  | 3be30d8c71 | ||
|  | d4c1244485 | ||
|  | c61b9dca17 | ||
|  | 39bf682016 | ||
|  | 60ac9b49ea | ||
|  | a8bb18e2cf | ||
|  | 1852786609 | ||
|  | 31df8c7e91 | ||
|  | 832939f5b7 | ||
|  | c2d9e1ec81 | ||
|  | 673b915ee8 | ||
|  | 032a62dfff | ||
|  | f2d78182a3 | ||
|  | de68e70246 | ||
|  | e07447eb9a | ||
|  | 5cdeb58571 | ||
|  | ce14cc8677 | ||
|  | bcd0479074 | ||
|  | d72dd8c4ff | ||
|  | f7ce86fef8 | ||
|  | 55f2fccf5e | ||
|  | c939a274be | ||
|  | 101fb5d7bf | ||
|  | 3c51e335c3 | ||
|  | 33ea90678c | ||
|  | 11ae2c64ba | ||
|  | 26624d7652 | ||
|  | 85fb4773b0 | ||
|  | 099d66804e | ||
|  | 086596c28e | ||
|  | 3aeb4213fe | ||
|  | 558b96bc05 | ||
|  | e97cc40a2c | ||
|  | 94503ed771 | ||
|  | c4f86cc324 | ||
|  | 70c4d6b9b3 | ||
|  | 78c7137427 | ||
|  | 74a2f717b3 | ||
|  | 98bb5bd9f1 | ||
|  | c91eaaf8da | ||
|  | a36f37d240 | ||
|  | c773d3501a | ||
|  | 5810f9b3f9 | ||
|  | 3f56683342 | ||
|  | 16ccbdefd6 | ||
|  | a533d09fe7 | ||
|  | e9aaa5bbdf | ||
|  | ecb26e3281 | ||
|  | 5aa0b17720 | ||
|  | 632b37ecec | ||
|  | c905de2e40 | ||
|  | bc2afe69e1 | ||
|  | 894998b163 | ||
|  | 51192d8397 | ||
|  | 3c33ccd730 | ||
|  | 3e35109d63 | ||
|  | 99c770eab4 | ||
|  | 34aa78b7ce | ||
|  | 8cca9c2055 | ||
|  | 85ce21c79f | ||
|  | d19d949b9c | ||
|  | 1cb3713b84 | ||
|  | 689850d698 | ||
|  | c572a52049 | ||
|  | 41765e00c4 | ||
|  | 080aa0acc5 | ||
|  | 5e7c46a72a | ||
|  | 5f2b9b2d5a | ||
|  | 5c4506a9db | ||
|  | 55a6431fb3 | ||
|  | ede2696a77 | ||
|  | 59b9e39022 | ||
|  | 6b2970f2f2 | ||
|  | 6a73fe7d65 | ||
|  | 1362906f94 | ||
|  | 8f4042c4bb | ||
|  | c05b6397b0 | ||
|  | 8d18808efe | ||
|  | 09950d9414 | ||
|  | badbbdf155 | ||
|  | 2832792fed | ||
|  | efa45b9504 | ||
|  | 523749edf8 | ||
|  | 5a0499e8a7 | ||
|  | 258c8b5900 | ||
|  | 24b861f056 | ||
|  | 29f7f4d432 | ||
|  | 21080a1149 | ||
|  | 1d068fd09b | ||
|  | 92065813ef | ||
|  | 3e9ef6b8cb | ||
|  | c9451a5382 | ||
|  | 2be3b027db | ||
|  | e339d169c5 | ||
|  | 87001f86ee | ||
|  | 58484e8f37 | ||
|  | 94f68f9d55 | ||
|  | 3f6944de54 | ||
|  | 00cb4d26b3 | ||
|  | 774d8668bf | ||
|  | 8503589828 | ||
|  | 0f95ef2059 | ||
|  | efd812cf22 | ||
|  | 736e14c83e | ||
|  | 57f161e64c | ||
|  | 0897210969 | ||
|  | 7e58a44771 | ||
|  | e8f847d288 | ||
|  | a0f817108e | ||
|  | 3862fdb44c | 
							
								
								
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | [*] | ||||||
|  | charset = utf-8 | ||||||
|  | indent_style = tab | ||||||
|  | indent_size = 4 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  |  | ||||||
| @@ -24,13 +24,13 @@ namespace Activity { | |||||||
| class Observer { | class Observer { | ||||||
| 	public: | 	public: | ||||||
| 		/// Announces to the receiver that there is an LED of name @c name. | 		/// Announces to the receiver that there is an LED of name @c name. | ||||||
| 		virtual void register_led(const std::string &name) = 0; | 		virtual void register_led(const std::string &name) {} | ||||||
|  |  | ||||||
| 		/// Announces to the receiver that there is a drive of name @c name. | 		/// Announces to the receiver that there is a drive of name @c name. | ||||||
| 		virtual void register_drive(const std::string &name) = 0; | 		virtual void register_drive(const std::string &name) {} | ||||||
|  |  | ||||||
| 		/// Informs the receiver of the new state of the LED with name @c name. | 		/// Informs the receiver of the new state of the LED with name @c name. | ||||||
| 		virtual void set_led_status(const std::string &name, bool lit) = 0; | 		virtual void set_led_status(const std::string &name, bool lit) {} | ||||||
|  |  | ||||||
| 		enum class DriveEvent { | 		enum class DriveEvent { | ||||||
| 			StepNormal, | 			StepNormal, | ||||||
| @@ -39,11 +39,10 @@ class Observer { | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | 		/// Informs the receiver that the named event just occurred for the drive with name @c name. | ||||||
| 		virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0; | 		virtual void announce_drive_event(const std::string &name, DriveEvent event) {} | ||||||
|  |  | ||||||
| 		/// Informs the receiver of the motor-on status of the drive with name @c name. | 		/// Informs the receiver of the motor-on status of the drive with name @c name. | ||||||
| 		virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0; | 		virtual void set_drive_motor_status(const std::string &name, bool is_on) {} | ||||||
|  |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| // |  | ||||||
| //  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; |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| // |  | ||||||
| //  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,26 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
|  | using namespace Analyser::Dynamic; | ||||||
|  |  | ||||||
|  | MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) { | ||||||
|  | 	for(const auto &machine: machines) { | ||||||
|  | 		MediaTarget::Machine *media_target = machine->media_target(); | ||||||
|  | 		if(media_target) targets_.push_back(media_target); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MultiMediaTarget::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,41 @@ | |||||||
|  | // | ||||||
|  | //  MultiMediaTarget.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 29/01/2018. | ||||||
|  | //  Copyright 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MultiMediaTarget_hpp | ||||||
|  | #define MultiMediaTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../../../../Machines/MediaTarget.hpp" | ||||||
|  | #include "../../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Analyser { | ||||||
|  | namespace Dynamic { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a class that multiplexes the media 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 MultiMediaTarget: public MediaTarget::Machine { | ||||||
|  | 	public: | ||||||
|  | 		MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines); | ||||||
|  |  | ||||||
|  | 		// Below is the standard MediaTarget::Machine interface; see there for documentation. | ||||||
|  | 		bool insert_media(const Analyser::Static::Media &media) override; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<MediaTarget::Machine *> targets_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MultiMediaTarget_hpp */ | ||||||
| @@ -15,10 +15,10 @@ using namespace Analyser::Dynamic; | |||||||
| MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) : | ||||||
| 	machines_(std::move(machines)), | 	machines_(std::move(machines)), | ||||||
| 	configurable_(machines_), | 	configurable_(machines_), | ||||||
| 	configuration_target_(machines_), |  | ||||||
| 	crt_machine_(machines_, machines_mutex_), | 	crt_machine_(machines_, machines_mutex_), | ||||||
| 	joystick_machine_(machines), | 	joystick_machine_(machines), | ||||||
| 	keyboard_machine_(machines_) { | 	keyboard_machine_(machines_), | ||||||
|  | 	media_target_(machines_) { | ||||||
| 	crt_machine_.set_delegate(this); | 	crt_machine_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -26,11 +26,11 @@ Activity::Source *MultiMachine::activity_source() { | |||||||
| 	return nullptr; // TODO | 	return nullptr; // TODO | ||||||
| } | } | ||||||
|  |  | ||||||
| ConfigurationTarget::Machine *MultiMachine::configuration_target() { | MediaTarget::Machine *MultiMachine::media_target() { | ||||||
| 	if(has_picked_) { | 	if(has_picked_) { | ||||||
| 		return machines_.front()->configuration_target(); | 		return machines_.front()->media_target(); | ||||||
| 	} else { | 	} else { | ||||||
| 		return &configuration_target_; | 		return &media_target_; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,10 +12,10 @@ | |||||||
| #include "../../../Machines/DynamicMachine.hpp" | #include "../../../Machines/DynamicMachine.hpp" | ||||||
|  |  | ||||||
| #include "Implementation/MultiConfigurable.hpp" | #include "Implementation/MultiConfigurable.hpp" | ||||||
| #include "Implementation/MultiConfigurationTarget.hpp" |  | ||||||
| #include "Implementation/MultiCRTMachine.hpp" | #include "Implementation/MultiCRTMachine.hpp" | ||||||
| #include "Implementation/MultiJoystickMachine.hpp" | #include "Implementation/MultiJoystickMachine.hpp" | ||||||
| #include "Implementation/MultiKeyboardMachine.hpp" | #include "Implementation/MultiKeyboardMachine.hpp" | ||||||
|  | #include "Implementation/MultiMediaTarget.hpp" | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| @@ -51,11 +51,11 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | 		MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines); | ||||||
|  |  | ||||||
| 		Activity::Source *activity_source() override; | 		Activity::Source *activity_source() override; | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override; | 		Configurable::Device *configurable_device() override; | ||||||
| 		CRTMachine::Machine *crt_machine() override; | 		CRTMachine::Machine *crt_machine() override; | ||||||
| 		JoystickMachine::Machine *joystick_machine() override; | 		JoystickMachine::Machine *joystick_machine() override; | ||||||
| 		KeyboardMachine::Machine *keyboard_machine() override; | 		KeyboardMachine::Machine *keyboard_machine() override; | ||||||
| 		Configurable::Device *configurable_device() override; | 		MediaTarget::Machine *media_target() override; | ||||||
| 		void *raw_pointer() override; | 		void *raw_pointer() override; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| @@ -65,10 +65,10 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De | |||||||
| 		std::mutex machines_mutex_; | 		std::mutex machines_mutex_; | ||||||
|  |  | ||||||
| 		MultiConfigurable configurable_; | 		MultiConfigurable configurable_; | ||||||
| 		MultiConfigurationTarget configuration_target_; |  | ||||||
| 		MultiCRTMachine crt_machine_; | 		MultiCRTMachine crt_machine_; | ||||||
| 		MultiJoystickMachine joystick_machine_; | 		MultiJoystickMachine joystick_machine_; | ||||||
| 		MultiKeyboardMachine keyboard_machine_; | 		MultiKeyboardMachine keyboard_machine_; | ||||||
|  | 		MultiMediaTarget media_target_; | ||||||
|  |  | ||||||
| 		void pick_first(); | 		void pick_first(); | ||||||
| 		bool has_picked_ = false; | 		bool has_picked_ = false; | ||||||
|   | |||||||
| @@ -69,13 +69,13 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me | |||||||
| 	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.empty()) { | ||||||
| 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | 		std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front(); | ||||||
| 		std::vector<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 | ||||||
| 		if(files.size()) { | 		if(!files.empty()) { | ||||||
| 			bool is_basic = true; | 			bool is_basic = true; | ||||||
|  |  | ||||||
| 			// protected files are always for *RUNning only | 			// protected files are always for *RUNning only | ||||||
| @@ -103,7 +103,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(media.disks.size() > 0) { | 	if(!media.disks.empty()) { | ||||||
| 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | 		std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front(); | ||||||
| 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | 		std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue; | ||||||
| 		dfs_catalogue = GetDFSCatalogue(disk); | 		dfs_catalogue = GetDFSCatalogue(disk); | ||||||
|   | |||||||
| @@ -18,7 +18,9 @@ namespace AppleII { | |||||||
| struct Target: public ::Analyser::Static::Target { | struct Target: public ::Analyser::Static::Target { | ||||||
| 	enum class Model { | 	enum class Model { | ||||||
| 		II, | 		II, | ||||||
| 		IIplus | 		IIplus, | ||||||
|  | 		IIe, | ||||||
|  | 		EnhancedIIe | ||||||
| 	}; | 	}; | ||||||
| 	enum class DiskController { | 	enum class DiskController { | ||||||
| 		None, | 		None, | ||||||
| @@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target { | |||||||
| 		ThirteenSector | 		ThirteenSector | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Model model = Model::IIplus; | 	Model model = Model::IIe; | ||||||
| 	DiskController disk_controller = DiskController::None; | 	DiskController disk_controller = DiskController::None; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,13 +17,8 @@ static std::vector<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 8, 12, 16, 24 or 32 kb in size |  | ||||||
| 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | 		const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); | ||||||
| 		const std::size_t data_size = segment.data.size(); | 		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 | 		// the two bytes that will be first must be 0xaa and 0x55, either way around | ||||||
| 		auto *start = &segment.data[0]; | 		auto *start = &segment.data[0]; | ||||||
| @@ -34,20 +29,25 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> | |||||||
| 		if(start[0] == start[1]) 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. | 		// 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); | 		// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if | ||||||
| 		} else { | 		// this image is within a short distance of 32kb. | ||||||
| 			// Size down to a multiple of 8kb and apply the start address. |  | ||||||
| 		std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | 		std::vector<Storage::Cartridge::Cartridge::Segment> output_segments; | ||||||
|  |  | ||||||
|  | 		size_t target_size; | ||||||
|  | 		if(data_size >= 32*1024 && data_size < 32*1024 + 512) { | ||||||
|  | 			target_size = 32 * 1024; | ||||||
|  | 		} else { | ||||||
|  | 			target_size = data_size + ((8192 - (data_size & 8191)) & 8191); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		std::vector<uint8_t> truncated_data; | 		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 = segment.data; | ||||||
| 			truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); | 		truncated_data.resize(target_size); | ||||||
| 		output_segments.emplace_back(0x8000, truncated_data); | 		output_segments.emplace_back(0x8000, truncated_data); | ||||||
|  |  | ||||||
| 		coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | 		coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return coleco_cartridges; | 	return coleco_cartridges; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -111,7 +111,7 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med | |||||||
| 	for(auto &tape : media.tapes) { | 	for(auto &tape : media.tapes) { | ||||||
| 		std::vector<File> tape_files = GetFiles(tape); | 		std::vector<File> tape_files = GetFiles(tape); | ||||||
| 		tape->reset(); | 		tape->reset(); | ||||||
| 		if(tape_files.size()) { | 		if(!tape_files.empty()) { | ||||||
| 			for(const auto &file : tape_files) { | 			for(const 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}; | ||||||
|   | |||||||
							
								
								
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								ClockReceiver/ClockDeferrer.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | // | ||||||
|  | //  ClockDeferrer.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 23/08/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ClockDeferrer_h | ||||||
|  | #define ClockDeferrer_h | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A ClockDeferrer maintains a list of ordered actions and the times at which | ||||||
|  | 	they should happen, and divides a total execution period up into the portions | ||||||
|  | 	that occur between those actions, triggering each action when it is reached. | ||||||
|  | */ | ||||||
|  | template <typename TimeUnit> class ClockDeferrer { | ||||||
|  | 	public: | ||||||
|  | 		/// Constructs a ClockDeferrer that will call target(period) in between deferred actions. | ||||||
|  | 		ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Schedules @c action to occur in @c delay units of time. | ||||||
|  |  | ||||||
|  | 			Actions must be scheduled in the order they will occur. It is undefined behaviour | ||||||
|  | 			to schedule them out of order. | ||||||
|  | 		*/ | ||||||
|  | 		void defer(TimeUnit delay, const std::function<void(void)> &action) { | ||||||
|  | 			pending_actions_.emplace_back(delay, action); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Runs for @c length units of time. | ||||||
|  |  | ||||||
|  | 			The constructor-supplied target will be called with one or more periods that add up to @c length; | ||||||
|  | 			any scheduled actions will be called between periods. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(TimeUnit length) { | ||||||
|  | 			// If there are no pending actions, just run for the entire length. | ||||||
|  | 			// This should be the normal branch. | ||||||
|  | 			if(pending_actions_.empty()) { | ||||||
|  | 				target_(length); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Divide the time to run according to the pending actions. | ||||||
|  | 			while(length > TimeUnit(0)) { | ||||||
|  | 				TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay); | ||||||
|  | 				target_(next_period); | ||||||
|  | 				length -= next_period; | ||||||
|  |  | ||||||
|  | 				off_t performances = 0; | ||||||
|  | 				for(auto &action: pending_actions_) { | ||||||
|  | 					action.delay -= next_period; | ||||||
|  | 					if(!action.delay) { | ||||||
|  | 						action.action(); | ||||||
|  | 						++performances; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if(performances) { | ||||||
|  | 					pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::function<void(TimeUnit)> target_; | ||||||
|  |  | ||||||
|  | 		// The list of deferred actions. | ||||||
|  | 		struct DeferredAction { | ||||||
|  | 			TimeUnit delay; | ||||||
|  | 			std::function<void(void)> action; | ||||||
|  |  | ||||||
|  | 			DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {} | ||||||
|  | 		}; | ||||||
|  | 		std::vector<DeferredAction> pending_actions_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif /* ClockDeferrer_h */ | ||||||
| @@ -496,17 +496,25 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// Paint sprites and check for collisions. | 							// Paint sprites and check for collisions, but only if at least one sprite is active | ||||||
|  | 							// on this line. | ||||||
| 							if(sprite_set.active_sprite_slot) { | 							if(sprite_set.active_sprite_slot) { | ||||||
| 								int sprite_pixels_left = pixels_left; | 								int sprite_pixels_left = pixels_left; | ||||||
| 								const int shift_advance = sprites_magnified_ ? 1 : 2; | 								const int shift_advance = sprites_magnified_ ? 1 : 2; | ||||||
|  |  | ||||||
| 								const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | 								static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; | ||||||
| 								const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | 								static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; | ||||||
|  |  | ||||||
| 								while(sprite_pixels_left--) { | 								while(sprite_pixels_left--) { | ||||||
|  | 									// sprite_colour is the colour that's going to reach the display after sprite logic has been | ||||||
|  | 									// applied; by default assume that nothing is going to be drawn. | ||||||
| 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | 									uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; | ||||||
|  |  | ||||||
|  | 									// The sprite_mask is used to keep track of whether two sprites have both sought to output | ||||||
|  | 									// a pixel at the same location, and to feed that into the status register's sprite | ||||||
|  | 									// collision bit. | ||||||
| 									int sprite_mask = 0; | 									int sprite_mask = 0; | ||||||
|  |  | ||||||
| 									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]; | ||||||
| @@ -517,15 +525,24 @@ void TMS9918::run_for(const HalfCycles cycles) { | |||||||
| 										} else if(sprite.shift_position < 32) { | 										} else if(sprite.shift_position < 32) { | ||||||
| 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | 											int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); | ||||||
| 											mask = (mask >> 7) & 1; | 											mask = (mask >> 7) & 1; | ||||||
|  |  | ||||||
|  | 											// Ignore the right half of whatever was collected if sprites are not in 16x16 mode. | ||||||
|  | 											if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { | ||||||
|  | 												// If any previous sprite has been painted in this column and this sprite | ||||||
|  | 												// has this pixel set, set the sprite collision status bit. | ||||||
| 												status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | 												status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; | ||||||
| 												sprite_mask |= mask; | 												sprite_mask |= mask; | ||||||
| 											sprite.shift_position += shift_advance; |  | ||||||
|  |  | ||||||
|  | 												// Check that the sprite colour is not transparent | ||||||
| 												mask &= colour_masks[sprite.info[3]&15]; | 												mask &= colour_masks[sprite.info[3]&15]; | ||||||
| 												sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | 												sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); | ||||||
| 											} | 											} | ||||||
|  |  | ||||||
|  | 											sprite.shift_position += shift_advance; | ||||||
|  | 										} | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
|  | 									// Output whichever sprite colour was on top. | ||||||
| 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | 									pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; | ||||||
| 									output_column_++; | 									output_column_++; | ||||||
| 								} | 								} | ||||||
|   | |||||||
| @@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) { | |||||||
| 	drives_[active_drive_].set_motor_on(motor_is_enabled_); | 	drives_[active_drive_].set_motor_on(motor_is_enabled_); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk. | ||||||
|  |  | ||||||
| void DiskII::run_for(const Cycles cycles) { | void DiskII::run_for(const Cycles cycles) { | ||||||
| 	if(preferred_clocking() == ClockingHint::Preference::None) return; | 	if(preferred_clocking() == ClockingHint::Preference::None) return; | ||||||
|  |  | ||||||
| 	int integer_cycles = cycles.as_int(); | 	int integer_cycles = cycles.as_int(); | ||||||
| 	while(integer_cycles--) { | 	while(integer_cycles--) { | ||||||
| 		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); | 		const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); | ||||||
| 		inputs_ |= input_flux; | 		if(flux_duration_) { | ||||||
|  | 			--flux_duration_; | ||||||
|  | 			if(!flux_duration_) inputs_ |= input_flux; | ||||||
|  | 		} | ||||||
| 		state_ = state_machine_[static_cast<std::size_t>(address)]; | 		state_ = state_machine_[static_cast<std::size_t>(address)]; | ||||||
| 		switch(state_ & 0xf) { | 		switch(state_ & 0xf) { | ||||||
| 			default:	shift_register_ = 0;													break;	// clear | 			default:	shift_register_ = 0;													break;	// clear | ||||||
| @@ -115,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) { | |||||||
| 		if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); | 		if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Per comp.sys.apple2.programmer there is a delay between the controller | ||||||
|  | 	// motor switch being flipped and the drive motor actually switching off. | ||||||
|  | 	// This models that, accepting overrun as a risk. | ||||||
|  | 	if(motor_off_time_ >= 0) { | ||||||
|  | 		motor_off_time_ -= cycles.as_int(); | ||||||
|  | 		if(motor_off_time_ < 0) { | ||||||
|  | 			set_control(Control::Motor, false); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	decide_clocking_preference(); | 	decide_clocking_preference(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -200,6 +214,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv | |||||||
| void DiskII::process_event(const Storage::Disk::Track::Event &event) { | void DiskII::process_event(const Storage::Disk::Track::Event &event) { | ||||||
| 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | 	if(event.type == Storage::Disk::Track::Event::FluxTransition) { | ||||||
| 		inputs_ &= ~input_flux; | 		inputs_ &= ~input_flux; | ||||||
|  | 		flux_duration_ = 2;	// Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. | ||||||
| 		decide_clocking_preference(); | 		decide_clocking_preference(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -232,9 +247,12 @@ int DiskII::read_address(int address) { | |||||||
|  |  | ||||||
| 		case 0x8: | 		case 0x8: | ||||||
| 			shift_register_ = 0; | 			shift_register_ = 0; | ||||||
| 			set_control(Control::Motor, false); | 			motor_off_time_ = clock_rate_; | ||||||
|  | 		break; | ||||||
|  | 		case 0x9: | ||||||
|  | 			set_control(Control::Motor, true); | ||||||
|  | 			motor_off_time_ = -1; | ||||||
| 		break; | 		break; | ||||||
| 		case 0x9:	set_control(Control::Motor, true);	break; |  | ||||||
|  |  | ||||||
| 		case 0xa:	select_drive(0);					break; | 		case 0xa:	select_drive(0);					break; | ||||||
| 		case 0xb:	select_drive(1);					break; | 		case 0xb:	select_drive(1);					break; | ||||||
|   | |||||||
| @@ -109,6 +109,7 @@ class DiskII: | |||||||
|  |  | ||||||
| 		int stepper_mask_ = 0; | 		int stepper_mask_ = 0; | ||||||
| 		int stepper_position_ = 0; | 		int stepper_position_ = 0; | ||||||
|  | 		int motor_off_time_ = -1; | ||||||
|  |  | ||||||
| 		bool is_write_protected(); | 		bool is_write_protected(); | ||||||
| 		std::array<uint8_t, 256> state_machine_; | 		std::array<uint8_t, 256> state_machine_; | ||||||
| @@ -121,6 +122,7 @@ class DiskII: | |||||||
| 		ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; | 		ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; | ||||||
|  |  | ||||||
| 		uint8_t data_input_ = 0; | 		uint8_t data_input_ = 0; | ||||||
|  | 		int flux_duration_ = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -128,6 +128,24 @@ class Joystick { | |||||||
| 					set_input(input, 0.5f); | 					set_input(input, 0.5f); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Gets the number of input fire buttons. | ||||||
|  |  | ||||||
|  | 			This is cached by default, but it's virtual so overridable. | ||||||
|  | 		*/ | ||||||
|  | 		virtual int get_number_of_fire_buttons() { | ||||||
|  | 			if(number_of_buttons_ >= 0) return number_of_buttons_; | ||||||
|  |  | ||||||
|  | 			number_of_buttons_ = 0; | ||||||
|  | 			for(const auto &input: get_inputs()) { | ||||||
|  | 				if(input.type == Input::Type::Fire) ++number_of_buttons_; | ||||||
|  | 			} | ||||||
|  | 			return number_of_buttons_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		int number_of_buttons_ = -1; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| @@ -168,10 +186,10 @@ class ConcreteJoystick: public Joystick { | |||||||
| 			using Type = Joystick::Input::Type; | 			using Type = Joystick::Input::Type; | ||||||
| 			switch(input.type) { | 			switch(input.type) { | ||||||
| 				default: 			did_set_input(input, is_active ? 1.0f : 0.0f);													break; | 				default: 			did_set_input(input, is_active ? 1.0f : 0.0f);													break; | ||||||
| 				case Type::Left:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.25f : 0.5f);		break; | 				case Type::Left:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f);		break; | ||||||
| 				case Type::Right:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.75f : 0.5f);		break; | 				case Type::Right:	did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f);		break; | ||||||
| 				case Type::Up:		did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.25f : 0.5f);		break; | 				case Type::Up:		did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f);		break; | ||||||
| 				case Type::Down:	did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.75f : 0.5f);		break; | 				case Type::Down:	did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f);		break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ | |||||||
| #include "../Utility/Typer.hpp" | #include "../Utility/Typer.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
| @@ -44,13 +44,6 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| enum ROMType: int { |  | ||||||
| 	OS464 = 0,	BASIC464, |  | ||||||
| 	OS664,		BASIC664, |  | ||||||
| 	OS6128,		BASIC6128, |  | ||||||
| 	AMSDOS |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | 	Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output | ||||||
| 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | 	is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period | ||||||
| @@ -761,9 +754,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler { | |||||||
| /*! | /*! | ||||||
| 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | 	The actual Amstrad CPC implementation; tying the 8255, 6845 and AY to the Z80. | ||||||
| */ | */ | ||||||
| class ConcreteMachine: | template <bool has_fdc> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| @@ -773,7 +766,7 @@ class ConcreteMachine: | |||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			crtc_bus_handler_(ram_, interrupt_timer_), | 			crtc_bus_handler_(ram_, interrupt_timer_), | ||||||
| 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | 			crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), | ||||||
| @@ -792,7 +785,59 @@ class ConcreteMachine: | |||||||
| 			fdc_.set_clocking_hint_observer(this); | 			fdc_.set_clocking_hint_observer(this); | ||||||
| 			tape_player_.set_clocking_hint_observer(this); | 			tape_player_.set_clocking_hint_observer(this); | ||||||
|  |  | ||||||
|  | 			// install the keyboard state class as the AY port handler | ||||||
| 			ay_.ay().set_port_handler(&key_state_); | 			ay_.ay().set_port_handler(&key_state_); | ||||||
|  |  | ||||||
|  | 			// construct the list of necessary ROMs | ||||||
|  | 			std::vector<std::string> required_roms = {"amsdos.rom"}; | ||||||
|  | 			std::string model_number; | ||||||
|  | 			switch(target.model) { | ||||||
|  | 				default: | ||||||
|  | 					model_number = "6128"; | ||||||
|  | 					has_128k_ = true; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: | ||||||
|  | 					model_number = "464"; | ||||||
|  | 					has_128k_ = false; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: | ||||||
|  | 					model_number = "664"; | ||||||
|  | 					has_128k_ = false; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			required_roms.push_back("os" + model_number + ".rom"); | ||||||
|  | 			required_roms.push_back("basic" + model_number + ".rom"); | ||||||
|  |  | ||||||
|  | 			// fetch and verify the ROMs | ||||||
|  | 			const auto roms = rom_fetcher("AmstradCPC", required_roms); | ||||||
|  |  | ||||||
|  | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
|  | 				auto &data = roms[index]; | ||||||
|  | 				if(!data) throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				roms_[static_cast<int>(index)] = std::move(*data); | ||||||
|  | 				roms_[static_cast<int>(index)].resize(16384); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Establish default memory map | ||||||
|  | 			upper_rom_is_paged_ = true; | ||||||
|  | 			upper_rom_ = ROMType::BASIC; | ||||||
|  |  | ||||||
|  | 			write_pointers_[0] = &ram_[0]; | ||||||
|  | 			write_pointers_[1] = &ram_[16384]; | ||||||
|  | 			write_pointers_[2] = &ram_[32768]; | ||||||
|  | 			write_pointers_[3] = &ram_[49152]; | ||||||
|  |  | ||||||
|  | 			read_pointers_[0] = roms_[ROMType::OS].data(); | ||||||
|  | 			read_pointers_[1] = write_pointers_[1]; | ||||||
|  | 			read_pointers_[2] = write_pointers_[2]; | ||||||
|  | 			read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
|  |  | ||||||
|  | 			// Type whatever is required. | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The entry point for performing a partial Z80 machine cycle. | 		/// The entry point for performing a partial Z80 machine cycle. | ||||||
| @@ -847,8 +892,8 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an upper ROM selection | 					// Check for an upper ROM selection | ||||||
| 					if(has_fdc_ && !(address&0x2000)) { | 					if(has_fdc && !(address&0x2000)) { | ||||||
| 						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : rom_model_ + 1; | 						upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; | ||||||
| 						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | 						if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -867,13 +912,13 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an FDC access | 					// Check for an FDC access | ||||||
| 					if(has_fdc_ && (address & 0x580) == 0x100) { | 					if(has_fdc && (address & 0x580) == 0x100) { | ||||||
| 						flush_fdc(); | 						flush_fdc(); | ||||||
| 						fdc_.set_register(address & 1, *cycle.value); | 						fdc_.set_register(address & 1, *cycle.value); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for a disk motor access | 					// Check for a disk motor access | ||||||
| 					if(has_fdc_ && !(address & 0x580)) { | 					if(has_fdc && !(address & 0x580)) { | ||||||
| 						flush_fdc(); | 						flush_fdc(); | ||||||
| 						fdc_.set_motor_on(!!(*cycle.value)); | 						fdc_.set_motor_on(!!(*cycle.value)); | ||||||
| 					} | 					} | ||||||
| @@ -888,7 +933,7 @@ class ConcreteMachine: | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Check for an FDC access | 					// Check for an FDC access | ||||||
| 					if(has_fdc_ && (address & 0x580) == 0x100) { | 					if(has_fdc && (address & 0x580) == 0x100) { | ||||||
| 						flush_fdc(); | 						flush_fdc(); | ||||||
| 						*cycle.value &= fdc_.get_register(address & 1); | 						*cycle.value &= fdc_.get_register(address & 1); | ||||||
| 					} | 					} | ||||||
| @@ -961,50 +1006,6 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// The ConfigurationTarget entry point; should configure this meachine as described by @c target. |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final  { |  | ||||||
| 			auto *const cpc_target = dynamic_cast<const Analyser::Static::AmstradCPC::Target *>(target); |  | ||||||
|  |  | ||||||
| 			switch(cpc_target->model) { |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC464: |  | ||||||
| 					rom_model_ = ROMType::OS464; |  | ||||||
| 					has_128k_ = false; |  | ||||||
| 					has_fdc_ = false; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC664: |  | ||||||
| 					rom_model_ = ROMType::OS664; |  | ||||||
| 					has_128k_ = false; |  | ||||||
| 					has_fdc_ = true; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::AmstradCPC::Target::Model::CPC6128: |  | ||||||
| 					rom_model_ = ROMType::OS6128; |  | ||||||
| 					has_128k_ = true; |  | ||||||
| 					has_fdc_ = true; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Establish default memory map |  | ||||||
| 			upper_rom_is_paged_ = true; |  | ||||||
| 			upper_rom_ = rom_model_ + 1; |  | ||||||
|  |  | ||||||
| 			write_pointers_[0] = &ram_[0]; |  | ||||||
| 			write_pointers_[1] = &ram_[16384]; |  | ||||||
| 			write_pointers_[2] = &ram_[32768]; |  | ||||||
| 			write_pointers_[3] = &ram_[49152]; |  | ||||||
|  |  | ||||||
| 			read_pointers_[0] = roms_[rom_model_].data(); |  | ||||||
| 			read_pointers_[1] = write_pointers_[1]; |  | ||||||
| 			read_pointers_[2] = write_pointers_[2]; |  | ||||||
| 			read_pointers_[3] = roms_[upper_rom_].data(); |  | ||||||
|  |  | ||||||
| 			// Type whatever is required. |  | ||||||
| 			if(!cpc_target->loading_command.empty()) { |  | ||||||
| 				type_string(cpc_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::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()) { | ||||||
| @@ -1019,28 +1020,7 @@ class ConcreteMachine: | |||||||
| 				if(c == 4) break; | 				if(c == 4) break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc_); | 			return !media.tapes.empty() || (!media.disks.empty() && has_fdc); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"AmstradCPC", |  | ||||||
| 				{ |  | ||||||
| 					"os464.rom",	"basic464.rom", |  | ||||||
| 					"os664.rom",	"basic664.rom", |  | ||||||
| 					"os6128.rom",	"basic6128.rom", |  | ||||||
| 					"amsdos.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				auto &data = roms[index]; |  | ||||||
| 				if(!data) return false; |  | ||||||
| 				roms_[static_cast<int>(index)] = std::move(*data); |  | ||||||
| 				roms_[static_cast<int>(index)].resize(16384); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final { | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final { | ||||||
| @@ -1078,7 +1058,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		// MARK: - Activity Source | 		// MARK: - Activity Source | ||||||
| 		void set_activity_observer(Activity::Observer *observer) override { | 		void set_activity_observer(Activity::Observer *observer) override { | ||||||
| 			if(has_fdc_) fdc_.set_activity_observer(observer); | 			if(has_fdc) fdc_.set_activity_observer(observer); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Configuration options. | 		// MARK: - Configuration options. | ||||||
| @@ -1117,7 +1097,7 @@ class ConcreteMachine: | |||||||
| 				case 1: crtc_bus_handler_.set_colour(value & 0x1f);		break; | 				case 1: crtc_bus_handler_.set_colour(value & 0x1f);		break; | ||||||
| 				case 2: | 				case 2: | ||||||
| 					// Perform ROM paging. | 					// Perform ROM paging. | ||||||
| 					read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[rom_model_].data(); | 					read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data(); | ||||||
|  |  | ||||||
| 					upper_rom_is_paged_ = !(value & 8); | 					upper_rom_is_paged_ = !(value & 8); | ||||||
| 					read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; | 					read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; | ||||||
| @@ -1169,7 +1149,7 @@ class ConcreteMachine: | |||||||
| 		HalfCycles time_since_fdc_update_; | 		HalfCycles time_since_fdc_update_; | ||||||
| 		void flush_fdc() { | 		void flush_fdc() { | ||||||
| 			// Clock the FDC, if connected, using a lazy scale by two | 			// Clock the FDC, if connected, using a lazy scale by two | ||||||
| 			if(has_fdc_ && !fdc_is_sleeping_) { | 			if(has_fdc && !fdc_is_sleeping_) { | ||||||
| 				fdc_.run_for(Cycles(time_since_fdc_update_.as_int())); | 				fdc_.run_for(Cycles(time_since_fdc_update_.as_int())); | ||||||
| 			} | 			} | ||||||
| 			time_since_fdc_update_ = HalfCycles(0); | 			time_since_fdc_update_ = HalfCycles(0); | ||||||
| @@ -1184,13 +1164,16 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		uint8_t ram_[128 * 1024]; | 		uint8_t ram_[128 * 1024]; | ||||||
|  |  | ||||||
| 		std::vector<uint8_t> roms_[7]; | 		bool fdc_is_sleeping_; | ||||||
| 		int rom_model_; |  | ||||||
| 		bool has_fdc_, fdc_is_sleeping_; |  | ||||||
| 		bool tape_player_is_sleeping_; | 		bool tape_player_is_sleeping_; | ||||||
| 		bool has_128k_; | 		bool has_128k_; | ||||||
|  |  | ||||||
|  | 		enum ROMType: int { | ||||||
|  | 			AMSDOS = 0, OS = 1, BASIC = 2 | ||||||
|  | 		}; | ||||||
|  | 		std::vector<uint8_t> roms_[3]; | ||||||
| 		bool upper_rom_is_paged_; | 		bool upper_rom_is_paged_; | ||||||
| 		int upper_rom_; | 		ROMType upper_rom_; | ||||||
|  |  | ||||||
| 		uint8_t *ram_pages_[4]; | 		uint8_t *ram_pages_[4]; | ||||||
| 		uint8_t *read_pointers_[4]; | 		uint8_t *read_pointers_[4]; | ||||||
| @@ -1205,8 +1188,13 @@ class ConcreteMachine: | |||||||
| using namespace AmstradCPC; | using namespace AmstradCPC; | ||||||
|  |  | ||||||
| // See header; constructs and returns an instance of the Amstrad CPC. | // See header; constructs and returns an instance of the Amstrad CPC. | ||||||
| Machine *Machine::AmstradCPC() { | Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new AmstradCPC::ConcreteMachine; | 	using Target = Analyser::Static::AmstradCPC::Target; | ||||||
|  | 	const Target *const cpc_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	switch(cpc_target->model) { | ||||||
|  | 		default: 					return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher); | ||||||
|  | 		case Target::Model::CPC464:	return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,8 +10,9 @@ | |||||||
| #define AmstradCPC_hpp | #define AmstradCPC_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| @@ -28,7 +29,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Amstrad CPC. | 		/// Creates and returns an Amstrad CPC. | ||||||
| 		static Machine *AmstradCPC(); | 		static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #include "AppleII.hpp" | #include "AppleII.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
| @@ -20,6 +20,7 @@ | |||||||
| #include "../../Components/AudioToggle/AudioToggle.hpp" | #include "../../Components/AudioToggle/AudioToggle.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| #include "Card.hpp" | #include "Card.hpp" | ||||||
| #include "DiskIICard.hpp" | #include "DiskIICard.hpp" | ||||||
| @@ -27,27 +28,19 @@ | |||||||
|  |  | ||||||
| #include "../../Analyser/Static/AppleII/Target.hpp" | #include "../../Analyser/Static/AppleII/Target.hpp" | ||||||
| #include "../../ClockReceiver/ForceInline.hpp" | #include "../../ClockReceiver/ForceInline.hpp" | ||||||
| #include "../../Configurable/Configurable.hpp" |  | ||||||
| #include "../../Storage/Disk/Track/TrackSerialiser.hpp" |  | ||||||
| #include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp" |  | ||||||
|  |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
| #include <memory> | #include <memory> | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() { |  | ||||||
| 	std::vector<std::unique_ptr<Configurable::Option>> options; |  | ||||||
| 	options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload")); |  | ||||||
| 	return options; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| class ConcreteMachine: | #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) | ||||||
|  |  | ||||||
|  | template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, |  | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| 	public Inputs::Keyboard, | 	public Inputs::Keyboard, | ||||||
| 	public AppleII::Machine, | 	public AppleII::Machine, | ||||||
| @@ -57,19 +50,20 @@ class ConcreteMachine: | |||||||
| 	private: | 	private: | ||||||
| 		struct VideoBusHandler : public AppleII::Video::BusHandler { | 		struct VideoBusHandler : public AppleII::Video::BusHandler { | ||||||
| 			public: | 			public: | ||||||
| 				VideoBusHandler(uint8_t *ram) : ram_(ram) {} | 				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} | ||||||
|  |  | ||||||
| 				uint8_t perform_read(uint16_t address) { | 				void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { | ||||||
| 					return ram_[address]; | 					memcpy(base_target, &ram_[address], count); | ||||||
|  | 					memcpy(auxiliary_target, &aux_ram_[address], count); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			private: | 			private: | ||||||
| 				uint8_t *ram_; | 				uint8_t *ram_, *aux_ram_; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
| 		VideoBusHandler video_bus_handler_; | 		VideoBusHandler video_bus_handler_; | ||||||
| 		std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_; | 		std::unique_ptr<AppleII::Video::Video<VideoBusHandler, is_iie()>> video_; | ||||||
| 		int cycles_into_current_line_ = 0; | 		int cycles_into_current_line_ = 0; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
|  |  | ||||||
| @@ -89,17 +83,24 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		uint8_t ram_[65536], aux_ram_[65536]; | 		uint8_t ram_[65536], aux_ram_[65536]; | ||||||
| 		std::vector<uint8_t> apple2_rom_, apple2plus_rom_, rom_; | 		std::vector<uint8_t> rom_; | ||||||
| 		std::vector<uint8_t> character_rom_; | 		std::vector<uint8_t> character_rom_; | ||||||
| 		uint8_t keyboard_input_ = 0x00; | 		uint8_t keyboard_input_ = 0x00; | ||||||
|  | 		bool key_is_down_ = false; | ||||||
|  |  | ||||||
|  | 		uint8_t get_keyboard_input() { | ||||||
|  | 			if(string_serialiser_) { | ||||||
|  | 				return string_serialiser_->head() | 0x80; | ||||||
|  | 			} else { | ||||||
|  | 				return keyboard_input_; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | 		Concurrency::DeferringAsyncTaskQueue audio_queue_; | ||||||
| 		Audio::Toggle audio_toggle_; | 		Audio::Toggle audio_toggle_; | ||||||
| 		Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_; | 		Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_; | ||||||
| 		Cycles cycles_since_audio_update_; | 		Cycles cycles_since_audio_update_; | ||||||
|  |  | ||||||
| 		ROMMachine::ROMFetcher rom_fetcher_; |  | ||||||
|  |  | ||||||
| 		// MARK: - Cards | 		// MARK: - Cards | ||||||
| 		std::array<std::unique_ptr<AppleII::Card>, 7> cards_; | 		std::array<std::unique_ptr<AppleII::Card>, 7> cards_; | ||||||
| 		Cycles cycles_since_card_update_; | 		Cycles cycles_since_card_update_; | ||||||
| @@ -138,11 +139,44 @@ class ConcreteMachine: | |||||||
| 			return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get()); | 			return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Memory Map | 		// MARK: - Memory Map. | ||||||
| 		struct MemoryBlock { |  | ||||||
| 			uint8_t *read_pointer = nullptr; | 		/* | ||||||
| 			uint8_t *write_pointer = nullptr; | 			The Apple II's paging mechanisms are byzantine to say the least. Painful is | ||||||
| 		} memory_blocks_[4];	// The IO page isn't included. | 			another appropriate adjective. | ||||||
|  |  | ||||||
|  | 			On a II and II+ there are five distinct zones of memory: | ||||||
|  |  | ||||||
|  | 			0000 to c000	:	the main block of RAM | ||||||
|  | 			c000 to d000	:	the IO area, including card ROMs | ||||||
|  | 			d000 to e000	:	the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card | ||||||
|  | 			e000 onward		:	the rest of ROM, also potentially replaced with RAM by a language card | ||||||
|  |  | ||||||
|  | 			On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: | ||||||
|  |  | ||||||
|  | 			0000 to 0200 	:	can be paged independently of the rest of RAM, other than part of the language card area which pages with it | ||||||
|  | 			0400 to 0800	:	the text screen, can be configured to write to auxiliary RAM | ||||||
|  | 			2000 to 4000	:	the graphics screen, which can be configured to write to auxiliary RAM | ||||||
|  | 			c100 to d000	:	can be used to page an additional 3.75kb of ROM, replacing the IO area | ||||||
|  | 			c300 to c400	:	can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest | ||||||
|  | 			c800 to d000	:	can contain ROM separately from the region below c800 | ||||||
|  |  | ||||||
|  | 			If dealt with as individual blocks in the inner loop, that would therefore imply mapping | ||||||
|  | 			an address to one of 13 potential pageable zones. So I've gone reductive and surrendered | ||||||
|  | 			to paging every 6502 page of memory independently. It makes the paging events more expensive, | ||||||
|  | 			but hopefully more clear. | ||||||
|  | 		*/ | ||||||
|  | 		uint8_t *read_pages_[256];	// each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory | ||||||
|  | 		uint8_t *write_pages_[256];	// as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. | ||||||
|  | 		void page(int start, int end, uint8_t *read, uint8_t *write) { | ||||||
|  | 			for(int position = start; position < end; ++position) { | ||||||
|  | 				read_pages_[position] = read; | ||||||
|  | 				if(read) read += 256; | ||||||
|  |  | ||||||
|  | 				write_pages_[position] = write; | ||||||
|  | 				if(write) write += 256; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// MARK: - The language card. | 		// MARK: - The language card. | ||||||
| 		struct { | 		struct { | ||||||
| @@ -153,28 +187,70 @@ class ConcreteMachine: | |||||||
| 		} language_card_; | 		} language_card_; | ||||||
| 		bool has_language_card_ = true; | 		bool has_language_card_ = true; | ||||||
| 		void set_language_card_paging() { | 		void set_language_card_paging() { | ||||||
| 			if(has_language_card_ && !language_card_.write) { | 			uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; | ||||||
| 				memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; | 			uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data(); | ||||||
| 				memory_blocks_[3].write_pointer = &ram_[56*1024]; |  | ||||||
| 			} else { | 			page(0xd0, 0xe0, | ||||||
| 				memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr; | 				language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom, | ||||||
|  | 				language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]); | ||||||
|  |  | ||||||
|  | 			page(0xe0, 0x100, | ||||||
|  | 				language_card_.read ? &ram[0xe000] : &rom[0x1000], | ||||||
|  | 				language_card_.write ? nullptr : &ram[0xe000]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 			if(has_language_card_ && language_card_.read) { | 		// MARK - The IIe's ROM controls. | ||||||
| 				memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)]; | 		bool internal_CX_rom_ = false; | ||||||
| 				memory_blocks_[3].read_pointer = &ram_[56*1024]; | 		bool slot_C3_rom_ = false; | ||||||
|  | 		bool internal_c8_rom_ = false; | ||||||
|  |  | ||||||
|  | 		void set_card_paging() { | ||||||
|  | 			page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr); | ||||||
|  |  | ||||||
|  | 			if(!internal_CX_rom_) { | ||||||
|  | 				if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK - The IIe's auxiliary RAM controls. | ||||||
|  | 		bool alternative_zero_page_ = false; | ||||||
|  | 		void set_zero_page_paging() { | ||||||
|  | 			if(alternative_zero_page_) { | ||||||
|  | 				read_pages_[0] = aux_ram_; | ||||||
| 			} else { | 			} else { | ||||||
| 				memory_blocks_[2].read_pointer = rom_.data(); | 				read_pages_[0] = ram_; | ||||||
| 				memory_blocks_[3].read_pointer = rom_.data() + 0x1000; | 			} | ||||||
|  | 			read_pages_[1] = read_pages_[0] + 256; | ||||||
|  | 			write_pages_[0] = read_pages_[0]; | ||||||
|  | 			write_pages_[1] = read_pages_[1]; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		bool read_auxiliary_memory_ = false; | ||||||
|  | 		bool write_auxiliary_memory_ = false; | ||||||
|  | 		void set_main_paging() { | ||||||
|  | 			page(0x02, 0xc0, | ||||||
|  | 				read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200], | ||||||
|  | 				write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]); | ||||||
|  |  | ||||||
|  | 			if(video_ && video_->get_80_store()) { | ||||||
|  | 				bool use_aux_ram = video_->get_page2(); | ||||||
|  | 				page(0x04, 0x08, | ||||||
|  | 					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400], | ||||||
|  | 					use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]); | ||||||
|  |  | ||||||
|  | 				if(video_->get_high_resolution()) { | ||||||
|  | 					page(0x20, 0x40, | ||||||
|  | 						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000], | ||||||
|  | 						use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK - typing | 		// MARK - typing | ||||||
| 		std::unique_ptr<Utility::StringSerialiser> string_serialiser_; | 		std::unique_ptr<Utility::StringSerialiser> string_serialiser_; | ||||||
|  |  | ||||||
| 		// MARK - quick loading |  | ||||||
| 		bool should_load_quickly_ = false; |  | ||||||
|  |  | ||||||
| 		// MARK - joysticks | 		// MARK - joysticks | ||||||
| 		class Joystick: public Inputs::ConcreteJoystick { | 		class Joystick: public Inputs::ConcreteJoystick { | ||||||
| 			public: | 			public: | ||||||
| @@ -222,13 +298,17 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
| 		bool analogue_channel_is_discharged(size_t channel) { | 		bool analogue_channel_is_discharged(size_t channel) { | ||||||
| 			return static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1] < analogue_charge_ + analogue_biases_[channel]; | 			return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel]; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// The IIe has three keys that are wired directly to the same input as the joystick buttons. | ||||||
|  | 		bool open_apple_is_pressed_ = false; | ||||||
|  | 		bool closed_apple_is_pressed_ = false; | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(): | 		ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | ||||||
| 			m6502_(*this), | 			m6502_(*this), | ||||||
| 		 	video_bus_handler_(ram_), | 		 	video_bus_handler_(ram_, aux_ram_), | ||||||
| 		 	audio_toggle_(audio_queue_), | 		 	audio_toggle_(audio_queue_), | ||||||
| 		 	speaker_(audio_toggle_) { | 		 	speaker_(audio_toggle_) { | ||||||
| 		 	// The system's master clock rate. | 		 	// The system's master clock rate. | ||||||
| @@ -250,10 +330,66 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// Also, start with randomised memory contents. | 			// Also, start with randomised memory contents. | ||||||
| 			Memory::Fuzz(ram_, sizeof(ram_)); | 			Memory::Fuzz(ram_, sizeof(ram_)); | ||||||
|  | 			Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); | ||||||
|  |  | ||||||
| 			// Add a couple of joysticks. | 			// Add a couple of joysticks. | ||||||
| 		 	joysticks_.emplace_back(new Joystick); | 		 	joysticks_.emplace_back(new Joystick); | ||||||
| 		 	joysticks_.emplace_back(new Joystick); | 		 	joysticks_.emplace_back(new Joystick); | ||||||
|  |  | ||||||
|  | 			// Pick the required ROMs. | ||||||
|  | 			using Target = Analyser::Static::AppleII::Target; | ||||||
|  | 			std::vector<std::string> rom_names; | ||||||
|  | 			size_t rom_size = 12*1024; | ||||||
|  | 			switch(target.model) { | ||||||
|  | 				default: | ||||||
|  | 					rom_names.push_back("apple2-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2o.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::IIplus: | ||||||
|  | 					rom_names.push_back("apple2-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::IIe: | ||||||
|  | 					rom_size += 3840; | ||||||
|  | 					rom_names.push_back("apple2eu-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2eu.rom"); | ||||||
|  | 				break; | ||||||
|  | 				case Target::Model::EnhancedIIe: | ||||||
|  | 					rom_size += 3840; | ||||||
|  | 					rom_names.push_back("apple2e-character.rom"); | ||||||
|  | 					rom_names.push_back("apple2e.rom"); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("AppleII", rom_names); | ||||||
|  |  | ||||||
|  | 			if(!roms[0] || !roms[1]) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			rom_ = std::move(*roms[1]); | ||||||
|  | 			if(rom_.size() > rom_size) { | ||||||
|  | 				rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			character_rom_ = std::move(*roms[0]); | ||||||
|  |  | ||||||
|  | 			if(target.disk_controller != Target::DiskController::None) { | ||||||
|  | 				// Apple recommended slot 6 for the (first) Disk II. | ||||||
|  | 				install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Set up the default memory blocks. On a II or II+ these values will never change. | ||||||
|  | 			// On a IIe they'll be affected by selection of auxiliary RAM. | ||||||
|  | 			set_main_paging(); | ||||||
|  | 			set_zero_page_paging(); | ||||||
|  |  | ||||||
|  | 			// Set the whole card area to initially backed by nothing. | ||||||
|  | 			page(0xc0, 0xd0, nullptr, nullptr); | ||||||
|  |  | ||||||
|  | 			// Set proper values for the language card/ROM area. | ||||||
|  | 			set_language_card_paging(); | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -261,7 +397,7 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| 			video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_)); | 			video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie()>(video_bus_handler_)); | ||||||
| 			video_->set_character_rom(character_rom_); | 			video_->set_character_rom(character_rom_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -295,108 +431,21 @@ class ConcreteMachine: | |||||||
| 				++ stretched_cycles_since_card_update_; | 				++ stretched_cycles_since_card_update_; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 				There are five distinct zones of memory on an Apple II: |  | ||||||
|  |  | ||||||
| 				0000 to 0200	:	the zero and stack pages, which can be paged independently on a IIe |  | ||||||
| 				0200 to c000	:	the main block of RAM, which can be paged on a IIe |  | ||||||
| 				c000 to d000	:	the IO area, including card ROMs |  | ||||||
| 				d000 to e000	:	the low ROM area, which can contain indepdently-paged RAM with a language card |  | ||||||
| 				e000 onward		:	the rest of ROM, also potentially replaced with RAM by a language card |  | ||||||
| 			*/ |  | ||||||
| 			uint16_t accessed_address = address; |  | ||||||
| 			MemoryBlock *block = nullptr; |  | ||||||
| 			if(address < 0x200) block = &memory_blocks_[0]; |  | ||||||
| 			else if(address < 0xc000) { |  | ||||||
| 				if(address < 0x6000 && !isReadOperation(operation)) update_video(); |  | ||||||
| 				block = &memory_blocks_[1]; |  | ||||||
| 				accessed_address -= 0x200; |  | ||||||
| 			} |  | ||||||
| 			else if(address < 0xd000) block = nullptr; |  | ||||||
| 			else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; } |  | ||||||
| 			else { block = &memory_blocks_[3]; accessed_address -= 0xe000; } |  | ||||||
|  |  | ||||||
| 			bool has_updated_cards = false; | 			bool has_updated_cards = false; | ||||||
| 			if(block) { | 			if(read_pages_[address >> 8]) { | ||||||
| 				if(isReadOperation(operation)) *value = block->read_pointer[accessed_address]; | 				if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; | ||||||
| 				else if(block->write_pointer) block->write_pointer[accessed_address] = *value; | 				else { | ||||||
|  | 					if(address >= 0x200 && address < 0x6000) update_video(); | ||||||
| 				if(should_load_quickly_) { | 					if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; | ||||||
| 					// Check for a prima facie entry into RWTS. |  | ||||||
| 					if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) { |  | ||||||
| 						// Grab the IO control block address for inspection. |  | ||||||
| 						uint16_t io_control_block_address = |  | ||||||
| 							static_cast<uint16_t>( |  | ||||||
| 								(m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) | |  | ||||||
| 								m6502_.get_value_of_register(CPU::MOS6502::Register::Y) |  | ||||||
| 							); |  | ||||||
|  |  | ||||||
| 						// Verify that this is table type one, for execution on card six, |  | ||||||
| 						// against drive 1 or 2, and that the command is either a seek or a sector read. |  | ||||||
| 						if( |  | ||||||
| 							ram_[io_control_block_address+0x00] == 0x01 && |  | ||||||
| 							ram_[io_control_block_address+0x01] == 0x60 && |  | ||||||
| 							ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 && |  | ||||||
| 							ram_[io_control_block_address+0x0c] < 2 |  | ||||||
| 						) { |  | ||||||
| 							const uint8_t iob_track = ram_[io_control_block_address+4]; |  | ||||||
| 							const uint8_t iob_sector = ram_[io_control_block_address+5]; |  | ||||||
| 							const uint8_t iob_drive = ram_[io_control_block_address+2] - 1; |  | ||||||
|  |  | ||||||
| 							// Get the track identified and store the new head position. |  | ||||||
| 							auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track)); |  | ||||||
|  |  | ||||||
| 							// DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2 |  | ||||||
| 							// is also kept in that Disk II card's screen hole. |  | ||||||
| 							ram_[0x478] = iob_track; |  | ||||||
| 							if(ram_[io_control_block_address+0x02] == 1) { |  | ||||||
| 								ram_[0x47e] = iob_track; |  | ||||||
| 							} else { |  | ||||||
| 								ram_[0x4fe] = iob_track; |  | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 							// Check whether this is a read, not merely a seek. | 				if(is_iie() && address >= 0xc300 && address < 0xd000) { | ||||||
| 							if(ram_[io_control_block_address+0x0c] == 1) { | 					bool internal_c8_rom = internal_c8_rom_; | ||||||
| 								// Apple the DOS 3.3 formula to map the requested logical sector to a physical sector. | 					internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_; | ||||||
| 								const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15); | 					internal_c8_rom &= (address != 0xcfff); | ||||||
|  | 					if(internal_c8_rom != internal_c8_rom_) { | ||||||
| 								// Parse the entire track. TODO: cache these. | 						internal_c8_rom_ = internal_c8_rom; | ||||||
| 								auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment( | 						set_card_paging(); | ||||||
| 									Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000))); |  | ||||||
|  |  | ||||||
| 								bool found_sector = false; |  | ||||||
| 								for(const auto &pair: sector_map) { |  | ||||||
| 									if(pair.second.address.sector == physical_sector) { |  | ||||||
| 										found_sector = true; |  | ||||||
|  |  | ||||||
| 										// Copy the sector contents to their destination. |  | ||||||
| 										uint16_t target = static_cast<uint16_t>( |  | ||||||
| 											ram_[io_control_block_address+8] | |  | ||||||
| 											(ram_[io_control_block_address+9] << 8) |  | ||||||
| 										); |  | ||||||
|  |  | ||||||
| 										for(size_t c = 0; c < 256; ++c) { |  | ||||||
| 											ram_[target] = pair.second.data[c]; |  | ||||||
| 											++target; |  | ||||||
| 										} |  | ||||||
|  |  | ||||||
| 										// Set no error encountered. |  | ||||||
| 										ram_[io_control_block_address + 0xd] = 0; |  | ||||||
| 										break; |  | ||||||
| 									} |  | ||||||
| 								} |  | ||||||
|  |  | ||||||
| 								if(found_sector) { |  | ||||||
| 									// Set no error in the flags register too, and RTS. |  | ||||||
| 									m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1); |  | ||||||
| 									*value = 0x60; |  | ||||||
| 								} |  | ||||||
| 							} else { |  | ||||||
| 								// No error encountered; RTS. |  | ||||||
| 								m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1); |  | ||||||
| 								*value = 0x60; |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| @@ -425,21 +474,27 @@ class ConcreteMachine: | |||||||
| 								default: break; | 								default: break; | ||||||
|  |  | ||||||
| 								case 0xc000: | 								case 0xc000: | ||||||
| 									if(string_serialiser_) { | 									*value = get_keyboard_input(); | ||||||
| 										*value = string_serialiser_->head() | 0x80; | 								break; | ||||||
| 									} else { | 								case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007: | ||||||
| 										*value = keyboard_input_; | 								case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f: | ||||||
| 									} | 									*value = (*value & 0x80) | (get_keyboard_input() & 0x7f); | ||||||
| 								break; | 								break; | ||||||
|  |  | ||||||
| 								case 0xc061:	// Switch input 0. | 								case 0xc061:	// Switch input 0. | ||||||
| 									*value &= 0x7f; | 									*value &= 0x7f; | ||||||
| 									if(static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2]) | 									if( | ||||||
|  | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] || | ||||||
|  | 										(is_iie() && open_apple_is_pressed_) | ||||||
|  | 									) | ||||||
| 										*value |= 0x80; | 										*value |= 0x80; | ||||||
| 								break; | 								break; | ||||||
| 								case 0xc062:	// Switch input 1. | 								case 0xc062:	// Switch input 1. | ||||||
| 									*value &= 0x7f; | 									*value &= 0x7f; | ||||||
| 									if(static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1]) | 									if( | ||||||
|  | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] || | ||||||
|  | 										(is_iie() && closed_apple_is_pressed_) | ||||||
|  | 									) | ||||||
| 										*value |= 0x80; | 										*value |= 0x80; | ||||||
| 								break; | 								break; | ||||||
| 								case 0xc063:	// Switch input 2. | 								case 0xc063:	// Switch input 2. | ||||||
| @@ -454,13 +509,93 @@ class ConcreteMachine: | |||||||
| 								case 0xc067: {	// Analogue input 3. | 								case 0xc067: {	// Analogue input 3. | ||||||
| 									const size_t input = address - 0xc064; | 									const size_t input = address - 0xc064; | ||||||
| 									*value &= 0x7f; | 									*value &= 0x7f; | ||||||
| 									if(analogue_channel_is_discharged(input)) { | 									if(!analogue_channel_is_discharged(input)) { | ||||||
| 										*value |= 0x80; | 										*value |= 0x80; | ||||||
| 									} | 									} | ||||||
| 								} break; | 								} break; | ||||||
|  |  | ||||||
|  | 								// The IIe-only state reads follow... | ||||||
|  | #define IIeSwitchRead(s)	*value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); | ||||||
|  | 								case 0xc011:	IIeSwitchRead(language_card_.bank1);										break; | ||||||
|  | 								case 0xc012:	IIeSwitchRead(language_card_.read);											break; | ||||||
|  | 								case 0xc013:	IIeSwitchRead(read_auxiliary_memory_);										break; | ||||||
|  | 								case 0xc014:	IIeSwitchRead(write_auxiliary_memory_);										break; | ||||||
|  | 								case 0xc015:	IIeSwitchRead(internal_CX_rom_);											break; | ||||||
|  | 								case 0xc016:	IIeSwitchRead(alternative_zero_page_);										break; | ||||||
|  | 								case 0xc017:	IIeSwitchRead(slot_C3_rom_);												break; | ||||||
|  | 								case 0xc018:	IIeSwitchRead(video_->get_80_store());										break; | ||||||
|  | 								case 0xc019:	IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_));	break; | ||||||
|  | 								case 0xc01a:	IIeSwitchRead(video_->get_text());											break; | ||||||
|  | 								case 0xc01b:	IIeSwitchRead(video_->get_mixed());											break; | ||||||
|  | 								case 0xc01c:	IIeSwitchRead(video_->get_page2());											break; | ||||||
|  | 								case 0xc01d:	IIeSwitchRead(video_->get_high_resolution());								break; | ||||||
|  | 								case 0xc01e:	IIeSwitchRead(video_->get_alternative_character_set());						break; | ||||||
|  | 								case 0xc01f:	IIeSwitchRead(video_->get_80_columns());									break; | ||||||
|  | #undef IIeSwitchRead | ||||||
|  |  | ||||||
|  | 								case 0xc07f: | ||||||
|  | 									if(is_iie()) *value = (*value & 0x7f) | (video_->get_annunciator_3() ? 0x80 : 0x00); | ||||||
|  | 								break; | ||||||
| 							} | 							} | ||||||
| 						} else { | 						} else { | ||||||
| 							// Write-only switches. | 							// Write-only switches. All IIe as currently implemented. | ||||||
|  | 							if(is_iie()) { | ||||||
|  | 								switch(address) { | ||||||
|  | 									default: break; | ||||||
|  |  | ||||||
|  | 									case 0xc000: | ||||||
|  | 									case 0xc001: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_80_store(!!(address&1)); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc002: | ||||||
|  | 									case 0xc003: | ||||||
|  | 										read_auxiliary_memory_ = !!(address&1); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc004: | ||||||
|  | 									case 0xc005: | ||||||
|  | 										write_auxiliary_memory_ = !!(address&1); | ||||||
|  | 										set_main_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc006: | ||||||
|  | 									case 0xc007: | ||||||
|  | 										internal_CX_rom_ = !!(address&1); | ||||||
|  | 										set_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc008: | ||||||
|  | 									case 0xc009: | ||||||
|  | 										// The alternative zero page setting affects both bank 0 and any RAM | ||||||
|  | 										// that's paged as though it were on a language card. | ||||||
|  | 										alternative_zero_page_ = !!(address&1); | ||||||
|  | 										set_zero_page_paging(); | ||||||
|  | 										set_language_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00a: | ||||||
|  | 									case 0xc00b: | ||||||
|  | 										slot_C3_rom_ = !!(address&1); | ||||||
|  | 										set_card_paging(); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00c: | ||||||
|  | 									case 0xc00d: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_80_columns(!!(address&1)); | ||||||
|  | 									break; | ||||||
|  |  | ||||||
|  | 									case 0xc00e: | ||||||
|  | 									case 0xc00f: | ||||||
|  | 										update_video(); | ||||||
|  | 										video_->set_alternative_character_set(!!(address&1)); | ||||||
|  | 									break; | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
| @@ -476,15 +611,34 @@ class ConcreteMachine: | |||||||
| 						analogue_charge_ = 0.0f; | 						analogue_charge_ = 0.0f; | ||||||
| 					} break; | 					} break; | ||||||
|  |  | ||||||
| 					/* Read-write switches. */ | 					/* Switches triggered by reading or writing. */ | ||||||
| 					case 0xc050:	update_video();		video_->set_graphics_mode();	break; | 					case 0xc050: | ||||||
| 					case 0xc051:	update_video();		video_->set_text_mode();		break; | 					case 0xc051: | ||||||
| 					case 0xc052:	update_video();		video_->set_mixed_mode(false);	break; | 						update_video(); | ||||||
| 					case 0xc053:	update_video();		video_->set_mixed_mode(true);	break; | 						video_->set_text(!!(address&1)); | ||||||
| 					case 0xc054:	update_video();		video_->set_video_page(0);		break; | 					break; | ||||||
| 					case 0xc055:	update_video();		video_->set_video_page(1);		break; | 					case 0xc052:	update_video();		video_->set_mixed(false);			break; | ||||||
| 					case 0xc056:	update_video();		video_->set_low_resolution();	break; | 					case 0xc053:	update_video();		video_->set_mixed(true);			break; | ||||||
| 					case 0xc057:	update_video();		video_->set_high_resolution();	break; | 					case 0xc054: | ||||||
|  | 					case 0xc055: | ||||||
|  | 						update_video(); | ||||||
|  | 						video_->set_page2(!!(address&1)); | ||||||
|  | 						set_main_paging(); | ||||||
|  | 					break; | ||||||
|  | 					case 0xc056: | ||||||
|  | 					case 0xc057: | ||||||
|  | 						update_video(); | ||||||
|  | 						video_->set_high_resolution(!!(address&1)); | ||||||
|  | 						set_main_paging(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case 0xc05e: | ||||||
|  | 					case 0xc05f: | ||||||
|  | 						if(is_iie()) { | ||||||
|  | 							update_video(); | ||||||
|  | 							video_->set_annunciator_3(!(address&1)); | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 					case 0xc010: | 					case 0xc010: | ||||||
| 						keyboard_input_ &= 0x7f; | 						keyboard_input_ &= 0x7f; | ||||||
| @@ -492,9 +646,15 @@ class ConcreteMachine: | |||||||
| 							if(!string_serialiser_->advance()) | 							if(!string_serialiser_->advance()) | ||||||
| 								string_serialiser_.reset(); | 								string_serialiser_.reset(); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
|  | 						// On the IIe, reading C010 returns additional key info. | ||||||
|  | 						if(is_iie() && isReadOperation(operation)) { | ||||||
|  | 							*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f); | ||||||
|  | 						} | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
| 					case 0xc030: | 					case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037: | ||||||
|  | 					case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f: | ||||||
| 						update_audio(); | 						update_audio(); | ||||||
| 						audio_toggle_.set_output(!audio_toggle_.get_output()); | 						audio_toggle_.set_output(!audio_toggle_.get_output()); | ||||||
| 					break; | 					break; | ||||||
| @@ -523,6 +683,7 @@ class ConcreteMachine: | |||||||
| 						// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." | 						// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." | ||||||
| 						language_card_.pre_write = isReadOperation(operation) ? (address&1) : false; | 						language_card_.pre_write = isReadOperation(operation) ? (address&1) : false; | ||||||
|  |  | ||||||
|  | 						// Apply whatever the net effect of all that is to the memory map. | ||||||
| 						set_language_card_paging(); | 						set_language_card_paging(); | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| @@ -531,7 +692,7 @@ class ConcreteMachine: | |||||||
| 					Communication with cards follows. | 					Communication with cards follows. | ||||||
| 				*/ | 				*/ | ||||||
|  |  | ||||||
| 				if(address >= 0xc090 && address < 0xc800) { | 				if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) { | ||||||
| 					// If this is a card access, figure out which card is at play before determining | 					// If this is a card access, figure out which card is at play before determining | ||||||
| 					// the totality of who needs messaging. | 					// the totality of who needs messaging. | ||||||
| 					size_t card_number = 0; | 					size_t card_number = 0; | ||||||
| @@ -556,7 +717,7 @@ class ConcreteMachine: | |||||||
| 					// If the selected card is a just-in-time card, update the just-in-time cards, | 					// If the selected card is a just-in-time card, update the just-in-time cards, | ||||||
| 					// and then message it specifically. | 					// and then message it specifically. | ||||||
| 					const bool is_read = isReadOperation(operation); | 					const bool is_read = isReadOperation(operation); | ||||||
| 					AppleII::Card *const target = cards_[card_number].get(); | 					AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get(); | ||||||
| 					if(target && !is_every_cycle_card(target)) { | 					if(target && !is_every_cycle_card(target)) { | ||||||
| 						update_just_in_time_cards(); | 						update_just_in_time_cards(); | ||||||
| 						target->perform_bus_operation(select, is_read, address, value); | 						target->perform_bus_operation(select, is_read, address, value); | ||||||
| @@ -596,49 +757,50 @@ class ConcreteMachine: | |||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"AppleII", |  | ||||||
| 				{ |  | ||||||
| 					"apple2o.rom", |  | ||||||
| 					"apple2.rom", |  | ||||||
| 					"apple2-character.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			if(!roms[0] || !roms[1] || !roms[2]) return false; |  | ||||||
|  |  | ||||||
| 			apple2_rom_ = std::move(*roms[0]); |  | ||||||
| 			apple2plus_rom_ = std::move(*roms[1]); |  | ||||||
|  |  | ||||||
| 			character_rom_ = std::move(*roms[2]); |  | ||||||
|  |  | ||||||
| 			rom_fetcher_ = roms_with_names; |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void run_for(const Cycles cycles) override { | 		void run_for(const Cycles cycles) override { | ||||||
| 			m6502_.run_for(cycles); | 			m6502_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		void reset_all_keys() override { | ||||||
|  | 			open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_key_pressed(Key key, char value, bool is_pressed) override { | 		void set_key_pressed(Key key, char value, bool is_pressed) override { | ||||||
| 			if(key == Key::F12) { | 			switch(key) { | ||||||
|  | 				default: break; | ||||||
|  | 				case Key::F12: | ||||||
| 					m6502_.set_reset_line(is_pressed); | 					m6502_.set_reset_line(is_pressed); | ||||||
| 				return; | 				return; | ||||||
|  | 				case Key::LeftOption: | ||||||
|  | 					open_apple_is_pressed_ = is_pressed; | ||||||
|  | 				return; | ||||||
|  | 				case Key::RightOption: | ||||||
|  | 					closed_apple_is_pressed_ = is_pressed; | ||||||
|  | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(is_pressed) { |  | ||||||
| 			// If no ASCII value is supplied, look for a few special cases. | 			// If no ASCII value is supplied, look for a few special cases. | ||||||
| 			if(!value) { | 			if(!value) { | ||||||
| 				switch(key) { | 				switch(key) { | ||||||
| 						case Key::Left:		value = 8;	break; | 					case Key::Left:			value = 0x08;	break; | ||||||
| 						case Key::Right:	value = 21;	break; | 					case Key::Right:		value = 0x15;	break; | ||||||
| 						case Key::Down:		value = 10;	break; | 					case Key::Down:			value = 0x0a;	break; | ||||||
| 						default: break; | 					case Key::Up:			value = 0x0b;	break; | ||||||
|  | 					case Key::BackSpace:	value = 0x7f;	break; | ||||||
|  | 					default: return; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 				keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80); | 			// Prior to the IIe, the keyboard could produce uppercase only. | ||||||
|  | 			if(!is_iie()) value = static_cast<char>(toupper(value)); | ||||||
|  |  | ||||||
|  | 			if(is_pressed) { | ||||||
|  | 				keyboard_input_ = static_cast<uint8_t>(value | 0x80); | ||||||
|  | 				key_is_down_ = true; | ||||||
|  | 			} else { | ||||||
|  | 				if((keyboard_input_ & 0x7f) == value) { | ||||||
|  | 					key_is_down_ = false; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -650,29 +812,7 @@ class ConcreteMachine: | |||||||
| 			string_serialiser_.reset(new Utility::StringSerialiser(string, true)); | 			string_serialiser_.reset(new Utility::StringSerialiser(string, true)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: ConfigurationTarget | 		// MARK: MediaTarget | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			using Target = Analyser::Static::AppleII::Target; |  | ||||||
| 			auto *const apple_target = dynamic_cast<const Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(apple_target->disk_controller != Target::DiskController::None) { |  | ||||||
| 				// Apple recommended slot 6 for the (first) Disk II. |  | ||||||
| 				install_card(6, new AppleII::DiskIICard(rom_fetcher_, apple_target->disk_controller == Target::DiskController::SixteenSector)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			rom_ = (apple_target->model == Target::Model::II) ? apple2_rom_ : apple2plus_rom_; |  | ||||||
| 			if(rom_.size() > 12*1024) { |  | ||||||
| 				rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Set up the default memory blocks. |  | ||||||
| 			memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_; |  | ||||||
| 			memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200]; |  | ||||||
| 			set_language_card_paging(); |  | ||||||
|  |  | ||||||
| 			insert_media(apple_target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			if(!media.disks.empty()) { | 			if(!media.disks.empty()) { | ||||||
| 				auto diskii = diskii_card(); | 				auto diskii = diskii_card(); | ||||||
| @@ -688,30 +828,6 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// MARK: Options |  | ||||||
| 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { |  | ||||||
| 			return AppleII::get_options(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { |  | ||||||
| 			bool quickload; |  | ||||||
| 			if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { |  | ||||||
| 				should_load_quickly_ = quickload; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet get_accurate_selections() override { |  | ||||||
| 			Configurable::SelectionSet selection_set; |  | ||||||
| 			Configurable::append_quick_load_tape_selection(selection_set, false); |  | ||||||
| 			return selection_set; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		Configurable::SelectionSet get_user_friendly_selections() override { |  | ||||||
| 			Configurable::SelectionSet selection_set; |  | ||||||
| 			Configurable::append_quick_load_tape_selection(selection_set, true); |  | ||||||
| 			return selection_set; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// MARK: JoystickMachine | 		// MARK: JoystickMachine | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
| 			return joysticks_; | 			return joysticks_; | ||||||
| @@ -722,9 +838,16 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace AppleII; | using namespace AppleII; | ||||||
|  |  | ||||||
| Machine *Machine::AppleII() { | Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	using Target = Analyser::Static::AppleII::Target; | ||||||
|  | 	const Target *const appleii_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	switch(appleii_target->model) { | ||||||
|  | 		default: return nullptr; | ||||||
|  | 		case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher); | ||||||
|  | 		case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,21 +10,20 @@ | |||||||
| #define AppleII_hpp | #define AppleII_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace AppleII { | namespace AppleII { | ||||||
|  |  | ||||||
| /// @returns The options available for an Apple II. |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); |  | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an AppleII. | 		/// Creates and returns an AppleII. | ||||||
| 		static Machine *AppleII(); | 		static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| using namespace AppleII; | using namespace AppleII; | ||||||
|  |  | ||||||
| DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { | DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { | ||||||
| 	auto roms = rom_fetcher( | 	const auto roms = rom_fetcher( | ||||||
| 		"DiskII", | 		"DiskII", | ||||||
| 		{ | 		{ | ||||||
| 			is_16_sector ? "boot-16.rom" : "boot-13.rom", | 			is_16_sector ? "boot-16.rom" : "boot-13.rom", | ||||||
|   | |||||||
| @@ -10,51 +10,330 @@ | |||||||
|  |  | ||||||
| using namespace AppleII::Video; | using namespace AppleII::Video; | ||||||
|  |  | ||||||
| VideoBase::VideoBase() : | VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) : | ||||||
| 	crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { | 	crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)), | ||||||
|  | 	is_iie_(is_iie), | ||||||
|  | 	deferrer_(std::move(target)) { | ||||||
|  |  | ||||||
| 	// Set a composite sampling function that assumes one byte per pixel input, and | 	// Set a composite sampling function that assumes one byte per pixel input, and | ||||||
| 	// accepts any non-zero value as being fully on, zero being fully off. | 	// accepts any non-zero value as being fully on, zero being fully off. | ||||||
| 	crt_->set_composite_sampling_function( | 	crt_->set_composite_sampling_function( | ||||||
| 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | 		"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" | ||||||
| 		"{" | 		"{" | ||||||
| 			"return texture(sampler, coordinate).r;" | 			"return clamp(texture(sampler, coordinate).r, 0.0, 0.7);" | ||||||
| 		"}"); | 		"}"); | ||||||
|  |  | ||||||
| 	// Show only the centre 75% of the TV frame. | 	// Show only the centre 75% of the TV frame. | ||||||
| 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | 	crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); | ||||||
| 	crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.122f, 0.77f, 0.77f)); | 	crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f)); | ||||||
| 	crt_->set_immediate_default_phase(0.0f); | 	crt_->set_immediate_default_phase(0.0f); | ||||||
|  |  | ||||||
|  | 	character_zones[0].xor_mask = 0; | ||||||
|  | 	character_zones[0].address_mask = 0x3f; | ||||||
|  | 	character_zones[1].xor_mask = 0; | ||||||
|  | 	character_zones[1].address_mask = 0x3f; | ||||||
|  | 	character_zones[2].xor_mask = 0; | ||||||
|  | 	character_zones[2].address_mask = 0x3f; | ||||||
|  | 	character_zones[3].xor_mask = 0; | ||||||
|  | 	character_zones[3].address_mask = 0x3f; | ||||||
|  |  | ||||||
|  | 	if(is_iie) { | ||||||
|  | 		character_zones[0].xor_mask = | ||||||
|  | 		character_zones[2].xor_mask = | ||||||
|  | 		character_zones[3].xor_mask = 0xff; | ||||||
|  | 		character_zones[2].address_mask = | ||||||
|  | 		character_zones[3].address_mask = 0xff; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Outputs::CRT::CRT *VideoBase::get_crt() { | Outputs::CRT::CRT *VideoBase::get_crt() { | ||||||
| 	return crt_.get(); | 	return crt_.get(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_graphics_mode() { | /* | ||||||
| 	use_graphics_mode_ = true; | 	Rote setters and getters. | ||||||
|  | */ | ||||||
|  | void VideoBase::set_alternative_character_set(bool alternative_character_set) { | ||||||
|  | 	set_alternative_character_set_ = alternative_character_set; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		alternative_character_set_ = alternative_character_set; | ||||||
|  | 		if(alternative_character_set) { | ||||||
|  | 			character_zones[1].address_mask = 0xff; | ||||||
|  | 			character_zones[1].xor_mask = 0; | ||||||
|  | 		} else { | ||||||
|  | 			character_zones[1].address_mask = 0x3f; | ||||||
|  | 			character_zones[1].xor_mask = flash_mask(); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_text_mode() { | bool VideoBase::get_alternative_character_set() { | ||||||
| 	use_graphics_mode_ = false; | 	return set_alternative_character_set_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_mixed_mode(bool mixed_mode) { | void VideoBase::set_80_columns(bool columns_80) { | ||||||
| 	mixed_mode_ = mixed_mode; | 	set_columns_80_ = columns_80; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		columns_80_ = columns_80; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_video_page(int page) { | bool VideoBase::get_80_columns() { | ||||||
| 	video_page_ = page; | 	return set_columns_80_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_low_resolution() { | void VideoBase::set_80_store(bool store_80) { | ||||||
| 	graphics_mode_ = GraphicsMode::LowRes; | 	set_store_80_ = store_80_ = store_80; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_high_resolution() { | bool VideoBase::get_80_store() { | ||||||
| 	graphics_mode_ = GraphicsMode::HighRes; | 	return set_store_80_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_page2(bool page2) { | ||||||
|  | 	set_page2_ = page2_ = page2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_page2() { | ||||||
|  | 	return set_page2_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_text(bool text) { | ||||||
|  | 	set_text_ = text; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		text_ = text; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_text() { | ||||||
|  | 	return set_text_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_mixed(bool mixed) { | ||||||
|  | 	set_mixed_ = mixed; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		mixed_ = mixed; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_mixed() { | ||||||
|  | 	return set_mixed_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_high_resolution(bool high_resolution) { | ||||||
|  | 	set_high_resolution_ = high_resolution; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		high_resolution_ = high_resolution; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_high_resolution() { | ||||||
|  | 	return set_high_resolution_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::set_annunciator_3(bool annunciator_3) { | ||||||
|  | 	set_annunciator_3_ = annunciator_3; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		annunciator_3_ = annunciator_3; | ||||||
|  | 		high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VideoBase::get_annunciator_3() { | ||||||
|  | 	return set_annunciator_3_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { | void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { | ||||||
| 	character_rom_ = character_rom; | 	character_rom_ = character_rom; | ||||||
|  |  | ||||||
|  | 	// Flip all character contents based on the second line of the $ graphic. | ||||||
|  | 	if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) { | ||||||
|  | 		for(auto &graphic : character_rom_) { | ||||||
|  | 			graphic = | ||||||
|  | 				((graphic & 0x01) ? 0x40 : 0x00) | | ||||||
|  | 				((graphic & 0x02) ? 0x20 : 0x00) | | ||||||
|  | 				((graphic & 0x04) ? 0x10 : 0x00) | | ||||||
|  | 				((graphic & 0x08) ? 0x08 : 0x00) | | ||||||
|  | 				((graphic & 0x10) ? 0x04 : 0x00) | | ||||||
|  | 				((graphic & 0x20) ? 0x02 : 0x00) | | ||||||
|  | 				((graphic & 0x40) ? 0x01 : 0x00); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		const int character = source[c] & character_zones[source[c] >> 6].address_mask; | ||||||
|  | 		const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask; | ||||||
|  | 		const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row; | ||||||
|  | 		const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask; | ||||||
|  |  | ||||||
|  | 		// The character ROM is output MSB to LSB rather than LSB to MSB. | ||||||
|  | 		target[0] = target[1] = character_pattern & 0x40; | ||||||
|  | 		target[2] = target[3] = character_pattern & 0x20; | ||||||
|  | 		target[4] = target[5] = character_pattern & 0x10; | ||||||
|  | 		target[6] = target[7] = character_pattern & 0x08; | ||||||
|  | 		target[8] = target[9] = character_pattern & 0x04; | ||||||
|  | 		target[10] = target[11] = character_pattern & 0x02; | ||||||
|  | 		target[12] = target[13] = character_pattern & 0x01; | ||||||
|  | 		graphics_carry_ = character_pattern & 0x01; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		const std::size_t character_addresses[2] = { | ||||||
|  | 			static_cast<std::size_t>( | ||||||
|  | 				(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3 | ||||||
|  | 			) + pixel_row, | ||||||
|  | 			static_cast<std::size_t>( | ||||||
|  | 				(source[c] & character_zones[source[c] >> 6].address_mask) << 3 | ||||||
|  | 			) + pixel_row | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const uint8_t character_patterns[2] = { | ||||||
|  | 			static_cast<uint8_t>( | ||||||
|  | 				character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask | ||||||
|  | 			), | ||||||
|  | 			static_cast<uint8_t>( | ||||||
|  | 				character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask | ||||||
|  | 			) | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// The character ROM is output MSB to LSB rather than LSB to MSB. | ||||||
|  | 		target[0] = character_patterns[0] & 0x40; | ||||||
|  | 		target[1] = character_patterns[0] & 0x20; | ||||||
|  | 		target[2] = character_patterns[0] & 0x10; | ||||||
|  | 		target[3] = character_patterns[0] & 0x08; | ||||||
|  | 		target[4] = character_patterns[0] & 0x04; | ||||||
|  | 		target[5] = character_patterns[0] & 0x02; | ||||||
|  | 		target[6] = character_patterns[0] & 0x01; | ||||||
|  | 		target[7] = character_patterns[1] & 0x40; | ||||||
|  | 		target[8] = character_patterns[1] & 0x20; | ||||||
|  | 		target[9] = character_patterns[1] & 0x10; | ||||||
|  | 		target[10] = character_patterns[1] & 0x08; | ||||||
|  | 		target[11] = character_patterns[1] & 0x04; | ||||||
|  | 		target[12] = character_patterns[1] & 0x02; | ||||||
|  | 		target[13] = character_patterns[1] & 0x01; | ||||||
|  | 		graphics_carry_ = character_patterns[1] & 0x01; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this | ||||||
|  | 		// 14-sample output window is starting at the beginning of a colour cycle or halfway through. | ||||||
|  | 		if((column + static_cast<int>(c))&1) { | ||||||
|  | 			target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8; | ||||||
|  | 			target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 8; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2; | ||||||
|  | 			target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 2; | ||||||
|  | 		} | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// Fat low-resolution mode appears not to do anything to try to make odd and | ||||||
|  | 		// even columns compatible. | ||||||
|  | 		target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1; | ||||||
|  | 		target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 		target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4; | ||||||
|  | 		target[6] = target[7] = (source[c] >> row_shift) & 8; | ||||||
|  | 		graphics_carry_ = (source[c] >> row_shift) & 4; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const { | ||||||
|  | 	const int row_shift = row&4; | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		if((column + static_cast<int>(c))&1) { | ||||||
|  | 			target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2; | ||||||
|  | 			target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4; | ||||||
|  | 			target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8; | ||||||
|  | 			target[3] = (auxiliary_source[c] >> row_shift) & 1; | ||||||
|  |  | ||||||
|  | 			target[8] = target[12] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[9] = target[13] = (source[c] >> row_shift) & 8; | ||||||
|  | 			target[10] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[7] = target[11] = (source[c] >> row_shift) & 2; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 8; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8; | ||||||
|  | 			target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1; | ||||||
|  | 			target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2; | ||||||
|  | 			target[3] = (auxiliary_source[c] >> row_shift) & 4; | ||||||
|  |  | ||||||
|  | 			target[8] = target[12] = (source[c] >> row_shift) & 1; | ||||||
|  | 			target[9] = target[13] = (source[c] >> row_shift) & 2; | ||||||
|  | 			target[10] = (source[c] >> row_shift) & 4; | ||||||
|  | 			target[7] = target[11] = (source[c] >> row_shift) & 8; | ||||||
|  | 			graphics_carry_ = (source[c] >> row_shift) & 2; | ||||||
|  | 		} | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel. | ||||||
|  | 		// If there is a delay, the previous output level is held to bridge the gap. | ||||||
|  | 		// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that | ||||||
|  | 		// high_resolution_mask_ models. | ||||||
|  | 		if(source[c] & high_resolution_mask_ & 0x80) { | ||||||
|  | 			target[0] = graphics_carry_; | ||||||
|  | 			target[1] = target[2] = source[c] & 0x01; | ||||||
|  | 			target[3] = target[4] = source[c] & 0x02; | ||||||
|  | 			target[5] = target[6] = source[c] & 0x04; | ||||||
|  | 			target[7] = target[8] = source[c] & 0x08; | ||||||
|  | 			target[9] = target[10] = source[c] & 0x10; | ||||||
|  | 			target[11] = target[12] = source[c] & 0x20; | ||||||
|  | 			target[13] = source[c] & 0x40; | ||||||
|  | 		} else { | ||||||
|  | 			target[0] = target[1] = source[c] & 0x01; | ||||||
|  | 			target[2] = target[3] = source[c] & 0x02; | ||||||
|  | 			target[4] = target[5] = source[c] & 0x04; | ||||||
|  | 			target[6] = target[7] = source[c] & 0x08; | ||||||
|  | 			target[8] = target[9] = source[c] & 0x10; | ||||||
|  | 			target[10] = target[11] = source[c] & 0x20; | ||||||
|  | 			target[12] = target[13] = source[c] & 0x40; | ||||||
|  | 		} | ||||||
|  | 		graphics_carry_ = source[c] & 0x40; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const { | ||||||
|  | 	for(size_t c = 0; c < length; ++c) { | ||||||
|  | 		target[0] = auxiliary_source[c] & 0x01; | ||||||
|  | 		target[1] = auxiliary_source[c] & 0x02; | ||||||
|  | 		target[2] = auxiliary_source[c] & 0x04; | ||||||
|  | 		target[3] = auxiliary_source[c] & 0x08; | ||||||
|  | 		target[4] = auxiliary_source[c] & 0x10; | ||||||
|  | 		target[5] = auxiliary_source[c] & 0x20; | ||||||
|  | 		target[6] = auxiliary_source[c] & 0x40; | ||||||
|  | 		target[7] = source[c] & 0x01; | ||||||
|  | 		target[8] = source[c] & 0x02; | ||||||
|  | 		target[9] = source[c] & 0x04; | ||||||
|  | 		target[10] = source[c] & 0x08; | ||||||
|  | 		target[11] = source[c] & 0x10; | ||||||
|  | 		target[12] = source[c] & 0x20; | ||||||
|  | 		target[13] = source[c] & 0x40; | ||||||
|  |  | ||||||
|  | 		graphics_carry_ = auxiliary_source[c] & 0x40; | ||||||
|  | 		target += 14; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,9 @@ | |||||||
|  |  | ||||||
| #include "../../Outputs/CRT/CRT.hpp" | #include "../../Outputs/CRT/CRT.hpp" | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../ClockReceiver/ClockDeferrer.hpp" | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace AppleII { | namespace AppleII { | ||||||
| @@ -19,25 +21,126 @@ namespace Video { | |||||||
|  |  | ||||||
| class BusHandler { | class BusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		uint8_t perform_read(uint16_t address) { | 		/*! | ||||||
| 			return 0xff; | 			Requests fetching of the @c count bytes starting from @c address. | ||||||
|  |  | ||||||
|  | 			The handler should write the values from base memory to @c base_target, and those | ||||||
|  | 			from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory, | ||||||
|  | 			it needn't write anything to auxiliary_target. | ||||||
|  | 		*/ | ||||||
|  | 		void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class VideoBase { | class VideoBase { | ||||||
| 	public: | 	public: | ||||||
| 		VideoBase(); | 		VideoBase(bool is_iie, std::function<void(Cycles)> &&target); | ||||||
|  |  | ||||||
| 		/// @returns The CRT this video feed is feeding. | 		/// @returns The CRT this video feed is feeding. | ||||||
| 		Outputs::CRT::CRT *get_crt(); | 		Outputs::CRT::CRT *get_crt(); | ||||||
|  |  | ||||||
| 		// Inputs for the various soft switches. | 		/* | ||||||
| 		void set_graphics_mode(); | 			Descriptions for the setters below are taken verbatim from | ||||||
| 		void set_text_mode(); | 			the Apple IIe Technical Reference. Addresses are the conventional | ||||||
| 		void set_mixed_mode(bool); | 			locations within the Apple II memory map. Only those which affect | ||||||
| 		void set_video_page(int); | 			video output are implemented here. | ||||||
| 		void set_low_resolution(); |  | ||||||
| 		void set_high_resolution(); | 			Those registers which don't exist on a II/II+ are marked. | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for ALTCHAR ($C00E/$C00F; triggers on write only): | ||||||
|  |  | ||||||
|  | 			* Off: display text using primary character set. | ||||||
|  | 			* On: display text using alternate character set. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_alternative_character_set(bool); | ||||||
|  | 		bool get_alternative_character_set(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for 80COL ($C00C/$C00D; triggers on write only). | ||||||
|  |  | ||||||
|  | 			* Off: display 40 columns. | ||||||
|  | 			* On: display 80 columns. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_80_columns(bool); | ||||||
|  | 		bool get_80_columns(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for 80STORE ($C000/$C001; triggers on write only). | ||||||
|  |  | ||||||
|  | 			* Off: cause PAGE2 to select auxiliary RAM. | ||||||
|  | 			* On: cause PAGE2 to switch main RAM areas. | ||||||
|  |  | ||||||
|  | 			Doesn't exist on a II/II+. | ||||||
|  | 		*/ | ||||||
|  | 		void set_80_store(bool); | ||||||
|  | 		bool get_80_store(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for PAGE2 ($C054/$C055; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: select Page 1. | ||||||
|  | 			* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. | ||||||
|  |  | ||||||
|  | 			80STORE doesn't exist on a II/II+; therefore this always selects | ||||||
|  | 			either Page 1 or Page 2 on those machines. | ||||||
|  | 		*/ | ||||||
|  | 		void set_page2(bool); | ||||||
|  | 		bool get_page2(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for TEXT ($C050/$C051; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: display graphics or, if MIXED on, mixed. | ||||||
|  | 			* On: display text. | ||||||
|  | 		*/ | ||||||
|  | 		void set_text(bool); | ||||||
|  | 		bool get_text(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for MIXED ($C052/$C053; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: display only text or only graphics. | ||||||
|  | 			* On: if TEXT off, display text and graphics. | ||||||
|  | 		*/ | ||||||
|  | 		void set_mixed(bool); | ||||||
|  | 		bool get_mixed(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for HIRES ($C056/$C057; triggers on read or write). | ||||||
|  |  | ||||||
|  | 			* Off: if TEXT off, display low-resolution graphics. | ||||||
|  | 			* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. | ||||||
|  |  | ||||||
|  | 			DHIRES doesn't exist on a II/II+; therefore this always selects | ||||||
|  | 			either high- or low-resolution graphics on those machines. | ||||||
|  |  | ||||||
|  | 			Despite Apple's documentation, the IIe also supports double low-resolution | ||||||
|  | 			graphics, which are the 80-column analogue to ordinary low-resolution 40-column | ||||||
|  | 			low-resolution graphics. | ||||||
|  | 		*/ | ||||||
|  | 		void set_high_resolution(bool); | ||||||
|  | 		bool get_high_resolution(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Setter for annunciator 3. | ||||||
|  |  | ||||||
|  | 			* On: turn on annunciator 3. | ||||||
|  | 			* Off: turn off annunciator 3. | ||||||
|  |  | ||||||
|  | 			This exists on both the II/II+ and the IIe, but has no effect on | ||||||
|  | 			video on the older machines. It's intended to be used on the IIe | ||||||
|  | 			to confirm double-high resolution mode but has side effects in | ||||||
|  | 			selecting mixed mode output and discarding high-resolution | ||||||
|  | 			delay bits. | ||||||
|  | 		*/ | ||||||
|  | 		void set_annunciator_3(bool); | ||||||
|  | 		bool get_annunciator_3(); | ||||||
|  |  | ||||||
| 		// Setup for text mode. | 		// Setup for text mode. | ||||||
| 		void set_character_rom(const std::vector<uint8_t> &); | 		void set_character_rom(const std::vector<uint8_t> &); | ||||||
| @@ -45,231 +148,121 @@ class VideoBase { | |||||||
| 	protected: | 	protected: | ||||||
| 		std::unique_ptr<Outputs::CRT::CRT> crt_; | 		std::unique_ptr<Outputs::CRT::CRT> crt_; | ||||||
|  |  | ||||||
|  | 		// State affecting output video stream generation. | ||||||
| 		uint8_t *pixel_pointer_ = nullptr; | 		uint8_t *pixel_pointer_ = nullptr; | ||||||
| 		int pixel_pointer_column_ = 0; |  | ||||||
| 		bool pixels_are_high_density_ = false; |  | ||||||
|  |  | ||||||
| 		int video_page_ = 0; | 		// State affecting logical state. | ||||||
| 		int row_ = 0, column_ = 0, flash_ = 0; | 		int row_ = 0, column_ = 0, flash_ = 0; | ||||||
|  | 		uint8_t flash_mask() { | ||||||
|  | 			return static_cast<uint8_t>((flash_ / flash_length) * 0xff); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Enumerates all Apple II and IIe display modes. | ||||||
|  | 		enum class GraphicsMode { | ||||||
|  | 			Text = 0, | ||||||
|  | 			DoubleText, | ||||||
|  | 			HighRes, | ||||||
|  | 			DoubleHighRes, | ||||||
|  | 			LowRes, | ||||||
|  | 			DoubleLowRes, | ||||||
|  | 			FatLowRes | ||||||
|  | 		}; | ||||||
|  | 		bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; } | ||||||
|  | 		bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); } | ||||||
|  |  | ||||||
|  | 		// Various soft-switch values. | ||||||
|  | 		bool alternative_character_set_ = false, set_alternative_character_set_ = false; | ||||||
|  | 		bool columns_80_ = false, set_columns_80_ = false; | ||||||
|  | 		bool store_80_ = false, set_store_80_ = false; | ||||||
|  | 		bool page2_ = false, set_page2_ = false; | ||||||
|  | 		bool text_ = true, set_text_ = true; | ||||||
|  | 		bool mixed_ = false, set_mixed_ = false; | ||||||
|  | 		bool high_resolution_ = false, set_high_resolution_ = false; | ||||||
|  | 		bool annunciator_3_ = false, set_annunciator_3_ = false; | ||||||
|  |  | ||||||
|  | 		// Graphics carry is the final level output in a fetch window; | ||||||
|  | 		// it carries on into the next if it's high resolution with | ||||||
|  | 		// the delay bit set. | ||||||
|  | 		mutable uint8_t graphics_carry_ = 0; | ||||||
|  | 		bool was_double_ = false; | ||||||
|  | 		uint8_t high_resolution_mask_ = 0xff; | ||||||
|  |  | ||||||
|  | 		// This holds a copy of the character ROM. The regular character | ||||||
|  | 		// set is assumed to be in the first 64*8 bytes; the alternative | ||||||
|  | 		// is in the 128*8 bytes after that. | ||||||
| 		std::vector<uint8_t> character_rom_; | 		std::vector<uint8_t> character_rom_; | ||||||
|  |  | ||||||
| 		enum class GraphicsMode { | 		// Memory is fetched ahead of time into this array; | ||||||
| 			LowRes, | 		// this permits the correct delay between fetching | ||||||
| 			HighRes, | 		// without having to worry about a rolling buffer. | ||||||
| 			Text | 		std::array<uint8_t, 40> base_stream_; | ||||||
| 		} graphics_mode_ = GraphicsMode::LowRes; | 		std::array<uint8_t, 40> auxiliary_stream_; | ||||||
| 		bool use_graphics_mode_ = false; |  | ||||||
| 		bool mixed_mode_ = false; | 		bool is_iie_ = false; | ||||||
| 		uint8_t graphics_carry_ = 0; | 		static const int flash_length = 8406; | ||||||
|  |  | ||||||
|  | 		// Describes the current text mode mapping from in-memory character index | ||||||
|  | 		// to output character. | ||||||
|  | 		struct CharacterMapping { | ||||||
|  | 			uint8_t address_mask; | ||||||
|  | 			uint8_t xor_mask; | ||||||
|  | 		}; | ||||||
|  | 		CharacterMapping character_zones[4]; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column text to @c target, using @c length bytes from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source. | ||||||
|  | 		*/ | ||||||
|  | 		void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source. | ||||||
|  |  | ||||||
|  | 			Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M | ||||||
|  | 			clock rather than the 14M. | ||||||
|  | 		*/ | ||||||
|  | 		void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; | ||||||
|  |  | ||||||
|  | 		// Maintain a ClockDeferrer for delayed mode switches. | ||||||
|  | 		ClockDeferrer<Cycles> deferrer_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| template <class BusHandler> class Video: public VideoBase { | template <class BusHandler, bool is_iie> class Video: public VideoBase { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs an instance of the video feed; a CRT is also created. | 		/// Constructs an instance of the video feed; a CRT is also created. | ||||||
| 		Video(BusHandler &bus_handler) : | 		Video(BusHandler &bus_handler) : | ||||||
| 			VideoBase(), | 			VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }), | ||||||
| 			bus_handler_(bus_handler) {} | 			bus_handler_(bus_handler) {} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Advances time by @c cycles; expects to be fed by the CPU clock. | 			Runs video for @c cycles. | ||||||
| 			Implicitly adds an extra half a colour clock at the end of every |  | ||||||
| 			line. |  | ||||||
| 		*/ | 		*/ | ||||||
| 		void run_for(const Cycles cycles) { | 		void run_for(Cycles cycles) { | ||||||
| 			/* | 			deferrer_.run_for(cycles); | ||||||
| 				Addressing scheme used throughout is that column 0 is the first column with pixels in it; |  | ||||||
| 				row 0 is the first row with pixels in it. |  | ||||||
|  |  | ||||||
| 				A frame is oriented around 65 cycles across, 262 lines down. |  | ||||||
| 			*/ |  | ||||||
| 			static const int first_sync_line = 220;		// A complete guess. Information needed. |  | ||||||
| 			static const int first_sync_column = 49;	// Also a guess. |  | ||||||
| 			static const int sync_length = 4;			// One of the two likely candidates. |  | ||||||
|  |  | ||||||
| 			int int_cycles = cycles.as_int(); |  | ||||||
| 			while(int_cycles) { |  | ||||||
| 				const int cycles_this_line = std::min(65 - column_, int_cycles); |  | ||||||
| 				const int ending_column = column_ + cycles_this_line; |  | ||||||
|  |  | ||||||
| 				if(row_ >= first_sync_line && row_ < first_sync_line + 3) { |  | ||||||
| 					// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising |  | ||||||
| 					// pulses (and hencce keep hsync approximately where it should be during vsync). |  | ||||||
| 					const int blank_start = std::max(first_sync_column - sync_length, column_); |  | ||||||
| 					const int blank_end = std::min(first_sync_column, ending_column); |  | ||||||
| 					if(blank_end > blank_start) { |  | ||||||
| 						if(blank_start > column_) { |  | ||||||
| 							crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14); |  | ||||||
| 						} |  | ||||||
| 						crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14); |  | ||||||
| 						if(blank_end < ending_column) { |  | ||||||
| 							crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14); |  | ||||||
| 						} |  | ||||||
| 					} else { |  | ||||||
| 						crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14); |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text; |  | ||||||
|  |  | ||||||
| 					// The first 40 columns are submitted to the CRT only upon completion; |  | ||||||
| 					// they'll be either graphics or blank, depending on which side we are |  | ||||||
| 					// of line 192. |  | ||||||
| 					if(column_ < 40) { |  | ||||||
| 						if(row_ < 192) { |  | ||||||
| 							GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text; |  | ||||||
| 							bool requires_high_density = pixel_mode != GraphicsMode::Text; |  | ||||||
| 							if(!column_ || requires_high_density != pixels_are_high_density_) { |  | ||||||
| 								if(column_) output_data_to_column(column_); |  | ||||||
| 								pixel_pointer_ = crt_->allocate_write_area(561); |  | ||||||
| 								pixel_pointer_column_ = column_; |  | ||||||
| 								pixels_are_high_density_ = requires_high_density; |  | ||||||
| 								graphics_carry_ = 0; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							const int pixel_end = std::min(40, ending_column); |  | ||||||
| 							const int character_row = row_ >> 3; |  | ||||||
| 							const int pixel_row = row_ & 7; |  | ||||||
| 							const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); |  | ||||||
| 							const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address); |  | ||||||
|  |  | ||||||
| 							switch(pixel_mode) { |  | ||||||
| 								case GraphicsMode::Text: { |  | ||||||
| 									const uint8_t inverses[] = { |  | ||||||
| 										0xff, |  | ||||||
| 										static_cast<uint8_t>((flash_ / flash_length) * 0xff), |  | ||||||
| 										0x00, |  | ||||||
| 										0x00 |  | ||||||
| 									}; |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)); |  | ||||||
| 										const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row); |  | ||||||
|  |  | ||||||
| 										const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6]; |  | ||||||
|  |  | ||||||
| 										// The character ROM is output MSB to LSB rather than LSB to MSB. |  | ||||||
| 										pixel_pointer_[0] = character_pattern & 0x40; |  | ||||||
| 										pixel_pointer_[1] = character_pattern & 0x20; |  | ||||||
| 										pixel_pointer_[2] = character_pattern & 0x10; |  | ||||||
| 										pixel_pointer_[3] = character_pattern & 0x08; |  | ||||||
| 										pixel_pointer_[4] = character_pattern & 0x04; |  | ||||||
| 										pixel_pointer_[5] = character_pattern & 0x02; |  | ||||||
| 										pixel_pointer_[6] = character_pattern & 0x01; |  | ||||||
| 										graphics_carry_ = character_pattern & 0x40; |  | ||||||
| 										pixel_pointer_ += 7; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::LowRes: { |  | ||||||
| 									const int row_shift = (row_&4); |  | ||||||
| 									// TODO: decompose into two loops, possibly. |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t nibble = (bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0x0f; |  | ||||||
|  |  | ||||||
| 										// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this |  | ||||||
| 										// 14-sample output window is starting at the beginning of a colour cycle or halfway through. |  | ||||||
| 										if(c&1) { |  | ||||||
| 											pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4; |  | ||||||
| 											pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8; |  | ||||||
| 											pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 1; |  | ||||||
| 											pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2; |  | ||||||
| 											graphics_carry_ = nibble & 8; |  | ||||||
| 										} else { |  | ||||||
| 											pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1; |  | ||||||
| 											pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2; |  | ||||||
| 											pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 4; |  | ||||||
| 											pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8; |  | ||||||
| 											graphics_carry_ = nibble & 2; |  | ||||||
| 										} |  | ||||||
| 										pixel_pointer_ += 14; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::HighRes: { |  | ||||||
| 									const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)); |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c)); |  | ||||||
|  |  | ||||||
| 										// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel. |  | ||||||
| 										// If there is a delay, the previous output level is held to bridge the gap. |  | ||||||
| 										if(graphic & 0x80) { |  | ||||||
| 											pixel_pointer_[0] = graphics_carry_; |  | ||||||
| 											pixel_pointer_[1] = pixel_pointer_[2] = graphic & 0x01; |  | ||||||
| 											pixel_pointer_[3] = pixel_pointer_[4] = graphic & 0x02; |  | ||||||
| 											pixel_pointer_[5] = pixel_pointer_[6] = graphic & 0x04; |  | ||||||
| 											pixel_pointer_[7] = pixel_pointer_[8] = graphic & 0x08; |  | ||||||
| 											pixel_pointer_[9] = pixel_pointer_[10] = graphic & 0x10; |  | ||||||
| 											pixel_pointer_[11] = pixel_pointer_[12] = graphic & 0x20; |  | ||||||
| 											pixel_pointer_[13] = graphic & 0x40; |  | ||||||
| 										} else { |  | ||||||
| 											pixel_pointer_[0] = pixel_pointer_[1] = graphic & 0x01; |  | ||||||
| 											pixel_pointer_[2] = pixel_pointer_[3] = graphic & 0x02; |  | ||||||
| 											pixel_pointer_[4] = pixel_pointer_[5] = graphic & 0x04; |  | ||||||
| 											pixel_pointer_[6] = pixel_pointer_[7] = graphic & 0x08; |  | ||||||
| 											pixel_pointer_[8] = pixel_pointer_[9] = graphic & 0x10; |  | ||||||
| 											pixel_pointer_[10] = pixel_pointer_[11] = graphic & 0x20; |  | ||||||
| 											pixel_pointer_[12] = pixel_pointer_[13] = graphic & 0x40; |  | ||||||
| 										} |  | ||||||
| 										graphics_carry_ = graphic & 0x40; |  | ||||||
| 										pixel_pointer_ += 14; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							if(ending_column >= 40) { |  | ||||||
| 								output_data_to_column(40); |  | ||||||
| 							} |  | ||||||
| 						} else { |  | ||||||
| 							if(ending_column >= 40) { |  | ||||||
| 								crt_->output_blank(560); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					/* |  | ||||||
| 						The left border, sync, right border pattern doesn't depend on whether |  | ||||||
| 						there were pixels this row and is output as soon as it is known. |  | ||||||
| 					*/ |  | ||||||
|  |  | ||||||
| 					const int first_blank_start = std::max(40, column_); |  | ||||||
| 					const int first_blank_end = std::min(first_sync_column, ending_column); |  | ||||||
| 					if(first_blank_end > first_blank_start) { |  | ||||||
| 						crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 14); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					const int sync_start = std::max(first_sync_column, column_); |  | ||||||
| 					const int sync_end = std::min(first_sync_column + sync_length, ending_column); |  | ||||||
| 					if(sync_end > sync_start) { |  | ||||||
| 						crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 14); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					int second_blank_start; |  | ||||||
| 					if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) { |  | ||||||
| 						const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); |  | ||||||
| 						const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); |  | ||||||
| 						if(colour_burst_end > colour_burst_start) { |  | ||||||
| 							crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14); |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						second_blank_start = std::max(first_sync_column + 7, column_); |  | ||||||
| 					} else { |  | ||||||
| 						second_blank_start = std::max(first_sync_column + 4, column_); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					if(ending_column > second_blank_start) { |  | ||||||
| 						crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				int_cycles -= cycles_this_line; |  | ||||||
| 				column_ = (column_ + cycles_this_line) % 65; |  | ||||||
| 				if(!column_) { |  | ||||||
| 					row_ = (row_ + 1) % 262; |  | ||||||
| 					flash_ = (flash_ + 1) % (2 * flash_length); |  | ||||||
|  |  | ||||||
| 					// Add an extra half a colour cycle of blank; this isn't counted in the run_for |  | ||||||
| 					// count explicitly but is promised. |  | ||||||
| 					crt_->output_blank(2); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -307,28 +300,295 @@ template <class BusHandler> class Video: public VideoBase { | |||||||
|  |  | ||||||
| 			// Calculate the address and return the value. | 			// Calculate the address and return the value. | ||||||
| 			uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25); | 			uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25); | ||||||
| 			return bus_handler_.perform_read(read_address); | 			uint8_t value, aux_value; | ||||||
|  | 			bus_handler_.perform_read(read_address, 1, &value, &aux_value); | ||||||
|  | 			return value; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		bool get_is_vertical_blank(Cycles offset) { | ||||||
|  | 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||||
|  | 			// (so what was column 0 is now column 25). | ||||||
|  | 			int mapped_column = column_ + offset.as_int(); | ||||||
|  |  | ||||||
|  | 			// Map that backwards from the internal pixels-at-start generation to pixels-at-end | ||||||
|  | 			// (so what was column 0 is now column 25). | ||||||
|  | 			mapped_column += 25; | ||||||
|  |  | ||||||
|  | 			// Apply carry into the row counter and test it for location. | ||||||
|  | 			int mapped_row = row_ + (mapped_column / 65); | ||||||
|  | 			return (mapped_row % 262) >= 192; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		/*! | ||||||
|  | 			Advances time by @c cycles; expects to be fed by the CPU clock. | ||||||
|  | 			Implicitly adds an extra half a colour clock at the end of | ||||||
|  | 			line. | ||||||
|  | 		*/ | ||||||
|  | 		void advance(Cycles cycles) { | ||||||
|  | 			/* | ||||||
|  | 				Addressing scheme used throughout is that column 0 is the first column with pixels in it; | ||||||
|  | 				row 0 is the first row with pixels in it. | ||||||
|  |  | ||||||
|  | 				A frame is oriented around 65 cycles across, 262 lines down. | ||||||
|  | 			*/ | ||||||
|  | 			static const int first_sync_line = 220;		// A complete guess. Information needed. | ||||||
|  | 			static const int first_sync_column = 49;	// Also a guess. | ||||||
|  | 			static const int sync_length = 4;			// One of the two likely candidates. | ||||||
|  |  | ||||||
|  | 			int int_cycles = cycles.as_int(); | ||||||
|  | 			while(int_cycles) { | ||||||
|  | 				const int cycles_this_line = std::min(65 - column_, int_cycles); | ||||||
|  | 				const int ending_column = column_ + cycles_this_line; | ||||||
|  |  | ||||||
|  | 				if(row_ >= first_sync_line && row_ < first_sync_line + 3) { | ||||||
|  | 					// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising | ||||||
|  | 					// pulses (and hencce keep hsync approximately where it should be during vsync). | ||||||
|  | 					const int blank_start = std::max(first_sync_column - sync_length, column_); | ||||||
|  | 					const int blank_end = std::min(first_sync_column, ending_column); | ||||||
|  | 					if(blank_end > blank_start) { | ||||||
|  | 						if(blank_start > column_) { | ||||||
|  | 							crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14); | ||||||
|  | 						} | ||||||
|  | 						crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14); | ||||||
|  | 						if(blank_end < ending_column) { | ||||||
|  | 							crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					const GraphicsMode line_mode = graphics_mode(row_); | ||||||
|  |  | ||||||
|  | 					// Determine whether there's any fetching to do. Fetching occurs during the first | ||||||
|  | 					// 40 columns of rows prior to 192. | ||||||
|  | 					if(row_ < 192 && column_ < 40) { | ||||||
|  | 						const int character_row = row_ >> 3; | ||||||
|  | 						const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | ||||||
|  |  | ||||||
|  | 						// Grab the memory contents that'll be needed momentarily. | ||||||
|  | 						const int fetch_end = std::min(40, ending_column); | ||||||
|  | 						uint16_t fetch_address; | ||||||
|  | 						switch(line_mode) { | ||||||
|  | 							default: | ||||||
|  | 							case GraphicsMode::Text: | ||||||
|  | 							case GraphicsMode::DoubleText: | ||||||
|  | 							case GraphicsMode::LowRes: | ||||||
|  | 							case GraphicsMode::FatLowRes: | ||||||
|  | 							case GraphicsMode::DoubleLowRes: { | ||||||
|  | 								const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address); | ||||||
|  | 								fetch_address = static_cast<uint16_t>(text_address + column_); | ||||||
|  | 							} break; | ||||||
|  |  | ||||||
|  | 							case GraphicsMode::HighRes: | ||||||
|  | 							case GraphicsMode::DoubleHighRes: | ||||||
|  | 								fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_); | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						bus_handler_.perform_read( | ||||||
|  | 							fetch_address, | ||||||
|  | 							static_cast<size_t>(fetch_end - column_), | ||||||
|  | 							&base_stream_[static_cast<size_t>(column_)], | ||||||
|  | 							&auxiliary_stream_[static_cast<size_t>(column_)]); | ||||||
|  | 						// TODO: should character modes be mapped to character pixel outputs here? | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(row_ < 192) { | ||||||
|  | 						// The pixel area is the first 40.5 columns; base contents | ||||||
|  | 						// remain where they would naturally be but auxiliary | ||||||
|  | 						// graphics appear to the left of that. | ||||||
|  | 						if(!column_) { | ||||||
|  | 							pixel_pointer_ = crt_->allocate_write_area(568); | ||||||
|  | 							graphics_carry_ = 0; | ||||||
|  | 							was_double_ = true; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if(column_ < 40) { | ||||||
|  | 							const int pixel_start = std::max(0, column_); | ||||||
|  | 							const int pixel_end = std::min(40, ending_column); | ||||||
|  | 							const int pixel_row = row_ & 7; | ||||||
|  |  | ||||||
|  | 							const bool is_double = Video::is_double_mode(line_mode); | ||||||
|  | 							if(!is_double && was_double_) { | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 0] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 1] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 2] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 3] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 4] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 5] = | ||||||
|  | 								pixel_pointer_[pixel_start*14 + 6] = 0; | ||||||
|  | 							} | ||||||
|  | 							was_double_ = is_double; | ||||||
|  |  | ||||||
|  | 							switch(line_mode) { | ||||||
|  | 								case GraphicsMode::Text: | ||||||
|  | 									output_text( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										static_cast<size_t>(pixel_row)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleText: | ||||||
|  | 									output_double_text( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										static_cast<size_t>(pixel_row)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::LowRes: | ||||||
|  | 									output_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::FatLowRes: | ||||||
|  | 									output_fat_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleLowRes: | ||||||
|  | 									output_double_low_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start), | ||||||
|  | 										pixel_start, | ||||||
|  | 										pixel_row); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::HighRes: | ||||||
|  | 									output_high_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14 + 7], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								case GraphicsMode::DoubleHighRes: | ||||||
|  | 									output_double_high_resolution( | ||||||
|  | 										&pixel_pointer_[pixel_start * 14], | ||||||
|  | 										&base_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										&auxiliary_stream_[static_cast<size_t>(pixel_start)], | ||||||
|  | 										static_cast<size_t>(pixel_end - pixel_start)); | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 								default: break; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							if(pixel_end == 40) { | ||||||
|  | 								if(was_double_) { | ||||||
|  | 									pixel_pointer_[563] = | ||||||
|  | 									pixel_pointer_[564] = | ||||||
|  | 									pixel_pointer_[565] = | ||||||
|  | 									pixel_pointer_[566] = | ||||||
|  | 									pixel_pointer_[567] = 0; | ||||||
|  | 								} else { | ||||||
|  | 									if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) | ||||||
|  | 										pixel_pointer_[567] = graphics_carry_; | ||||||
|  | 									else | ||||||
|  | 										pixel_pointer_[567] = 0; | ||||||
|  | 								} | ||||||
|  |  | ||||||
|  | 								crt_->output_data(568, 568); | ||||||
|  | 								pixel_pointer_ = nullptr; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						if(column_ < 40 && ending_column >= 40) { | ||||||
|  | 							crt_->output_blank(568); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					/* | ||||||
|  | 						The left border, sync, right border pattern doesn't depend on whether | ||||||
|  | 						there were pixels this row and is output as soon as it is known. | ||||||
|  | 					*/ | ||||||
|  |  | ||||||
|  | 					if(column_ < first_sync_column && ending_column >= first_sync_column) { | ||||||
|  | 						crt_->output_blank((first_sync_column - 41)*14 - 1); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) { | ||||||
|  | 						crt_->output_sync(sync_length*14); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					int second_blank_start; | ||||||
|  | 					if(!is_text_mode(graphics_mode(row_+1))) { | ||||||
|  | 						const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); | ||||||
|  | 						const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); | ||||||
|  | 						if(colour_burst_end > colour_burst_start) { | ||||||
|  | 							crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						second_blank_start = std::max(first_sync_column + sync_length + 3, column_); | ||||||
|  | 					} else { | ||||||
|  | 						second_blank_start = std::max(first_sync_column + sync_length, column_); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(ending_column > second_blank_start) { | ||||||
|  | 						crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				int_cycles -= cycles_this_line; | ||||||
|  | 				column_ = (column_ + cycles_this_line) % 65; | ||||||
|  | 				if(!column_) { | ||||||
|  | 					row_ = (row_ + 1) % 262; | ||||||
|  | 					flash_ = (flash_ + 1) % (2 * flash_length); | ||||||
|  | 					if(!alternative_character_set_) { | ||||||
|  | 						character_zones[1].xor_mask = flash_mask(); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Add an extra half a colour cycle of blank; this isn't counted in the run_for | ||||||
|  | 					// count explicitly but is promised. | ||||||
|  | 					crt_->output_blank(2); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		GraphicsMode graphics_mode(int row) { | ||||||
|  | 			if( | ||||||
|  | 				text_ || | ||||||
|  | 				(mixed_ && row >= 160 && row < 192) | ||||||
|  | 			) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; | ||||||
|  | 			if(high_resolution_) { | ||||||
|  | 				return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; | ||||||
|  | 			} else { | ||||||
|  | 				if(columns_80_) return GraphicsMode::DoubleLowRes; | ||||||
|  | 				if(annunciator_3_) return GraphicsMode::FatLowRes; | ||||||
|  | 				return GraphicsMode::LowRes; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int video_page() { | ||||||
|  | 			return (store_80_ || !page2_) ? 0 : 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		uint16_t get_row_address(int row) { | 		uint16_t get_row_address(int row) { | ||||||
| 			const int character_row = row >> 3; | 			const int character_row = row >> 3; | ||||||
| 			const int pixel_row = row & 7; | 			const int pixel_row = row & 7; | ||||||
| 			const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | 			const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7)); | ||||||
|  |  | ||||||
| 			GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text; | 			const GraphicsMode pixel_mode = graphics_mode(row); | ||||||
| 			return (pixel_mode == GraphicsMode::HighRes) ? | 			return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? | ||||||
| 				static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : | 				static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : | ||||||
| 				static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address); | 				static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		static const int flash_length = 8406; |  | ||||||
| 		BusHandler &bus_handler_; | 		BusHandler &bus_handler_; | ||||||
| 		void output_data_to_column(int column) { |  | ||||||
| 			int length = column - pixel_pointer_column_; |  | ||||||
| 			crt_->output_data(static_cast<unsigned int>(length*14), static_cast<unsigned int>(length * (pixels_are_high_density_ ? 14 : 7))); |  | ||||||
| 			pixel_pointer_ = nullptr; |  | ||||||
| 		} |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| @@ -76,24 +75,16 @@ class Joystick: public Inputs::ConcreteJoystick { | |||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public JoystickMachine::Machine, | 	public JoystickMachine::Machine, | ||||||
| 	public Outputs::CRT::Delegate { | 	public Outputs::CRT::Delegate { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() { | 		ConcreteMachine(const Analyser::Static::Atari::Target &target) { | ||||||
| 			set_clock_rate(NTSC_clock_rate); | 			set_clock_rate(NTSC_clock_rate); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 			const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data; | ||||||
| 			close_output(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			auto *const atari_target = dynamic_cast<const Analyser::Static::Atari::Target *>(target); |  | ||||||
| 			const std::vector<uint8_t> &rom = target->media.cartridges.front()->get_segments().front().data; |  | ||||||
|  |  | ||||||
| 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | 			using PagingModel = Analyser::Static::Atari::Target::PagingModel; | ||||||
| 			switch(atari_target->paging_model) { | 			switch(target.paging_model) { | ||||||
| 				case PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | 				case PagingModel::ActivisionStack:	bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom));	break; | ||||||
| 				case PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | 				case PagingModel::CBSRamPlus:		bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom));		break; | ||||||
| 				case PagingModel::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | 				case PagingModel::CommaVid:			bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom));			break; | ||||||
| @@ -105,21 +96,21 @@ class ConcreteMachine: | |||||||
| 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | 				case PagingModel::Tigervision:		bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom));		break; | ||||||
|  |  | ||||||
| 				case PagingModel::Atari8k: | 				case PagingModel::Atari8k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.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 PagingModel::Atari16k: | 				case PagingModel::Atari16k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.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 PagingModel::Atari32k: | 				case PagingModel::Atari32k: | ||||||
| 					if(atari_target->uses_superchip) { | 					if(target.uses_superchip) { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom)); | ||||||
| 					} else { | 					} else { | ||||||
| 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | 						bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom)); | ||||||
| @@ -131,8 +122,8 @@ class ConcreteMachine: | |||||||
| 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | 			joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		~ConcreteMachine() { | ||||||
| 			return false; | 			close_output(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
| @@ -254,8 +245,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Atari2600; | using namespace Atari2600; | ||||||
|  |  | ||||||
| Machine *Machine::Atari2600() { | Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Atari2600::ConcreteMachine; | 	using Target = Analyser::Static::Atari::Target; | ||||||
|  | 	const Target *const atari_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Atari2600::ConcreteMachine(*atari_target); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,6 +9,10 @@ | |||||||
| #ifndef Atari2600_cpp | #ifndef Atari2600_cpp | ||||||
| #define Atari2600_cpp | #define Atari2600_cpp | ||||||
|  |  | ||||||
|  | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include "Atari2600Inputs.h" | #include "Atari2600Inputs.h" | ||||||
|  |  | ||||||
| namespace Atari2600 { | namespace Atari2600 { | ||||||
| @@ -21,7 +25,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Atari 2600 on the heap. | 		/// Creates and returns an Atari 2600 on the heap. | ||||||
| 		static Machine *Atari2600(); | 		static Machine *Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		/// 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; | ||||||
|   | |||||||
| @@ -204,7 +204,7 @@ template<class T> class Cartridge: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		CPU::MOS6502::Processor<Cartridge<T>, true> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_; | ||||||
| 		std::vector<uint8_t> rom_; | 		std::vector<uint8_t> rom_; | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ namespace CRTMachine { | |||||||
| 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | 	that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate | ||||||
| 	should that clock rate change. | 	should that clock rate change. | ||||||
| */ | */ | ||||||
| class Machine: public ROMMachine::Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		/*! | 		/*! | ||||||
| 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | 			Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ | |||||||
| #include "../../Components/AY38910/AY38910.hpp"	// For the Super Game Module. | #include "../../Components/AY38910/AY38910.hpp"	// For the Super Game Module. | ||||||
| #include "../../Components/SN76489/SN76489.hpp" | #include "../../Components/SN76489/SN76489.hpp" | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" |  | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../JoystickMachine.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  |  | ||||||
| @@ -108,11 +107,10 @@ class ConcreteMachine: | |||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, |  | ||||||
| 	public JoystickMachine::Machine { | 	public JoystickMachine::Machine { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), | 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -122,6 +120,36 @@ class ConcreteMachine: | |||||||
| 			set_clock_rate(3579545); | 			set_clock_rate(3579545); | ||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
| 			joysticks_.emplace_back(new Joystick); | 			joysticks_.emplace_back(new Joystick); | ||||||
|  |  | ||||||
|  | 			const auto roms = rom_fetcher( | ||||||
|  | 				"ColecoVision", | ||||||
|  | 				{ "coleco.rom" }); | ||||||
|  |  | ||||||
|  | 			if(!roms[0]) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bios_ = *roms[0]; | ||||||
|  | 			bios_.resize(8192); | ||||||
|  |  | ||||||
|  | 			if(!target.media.cartridges.empty()) { | ||||||
|  | 				const auto &segment = target.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; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -153,48 +181,6 @@ class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			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 ROMMachine::ROMFetcher &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 | 		// MARK: Z80::BusHandler | ||||||
| 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | 		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { | ||||||
| 			// The SN76489 will use its ready line to trigger the Z80's wait for three | 			// The SN76489 will use its ready line to trigger the Z80's wait for three | ||||||
| @@ -408,8 +394,8 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Coleco::Vision; | using namespace Coleco::Vision; | ||||||
|  |  | ||||||
| Machine *Machine::ColecoVision() { | Machine *Machine::ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	return new ConcreteMachine(*target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -9,13 +9,16 @@ | |||||||
| #ifndef ColecoVision_hpp | #ifndef ColecoVision_hpp | ||||||
| #define ColecoVision_hpp | #define ColecoVision_hpp | ||||||
|  |  | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace Coleco { | namespace Coleco { | ||||||
| namespace Vision { | namespace Vision { | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
| 		static Machine *ColecoVision(); | 		static Machine *ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,25 @@ | |||||||
| #ifndef Commodore1540_hpp | #ifndef Commodore1540_hpp | ||||||
| #define Commodore1540_hpp | #define Commodore1540_hpp | ||||||
|  |  | ||||||
|  | namespace Commodore { | ||||||
|  | namespace C1540 { | ||||||
|  |  | ||||||
|  | /// Defines the type of drive this 1540 hardware is configured as. | ||||||
|  | enum class Personality { | ||||||
|  | 	C1540, | ||||||
|  | 	C1541 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	Implementation note: this is defined up here so that it precedes | ||||||
|  | 	C1540Base.hpp below. The alternative option was to factor it out, | ||||||
|  | 	but the whole point of the C1540.hpp/C1540Base.hpp split is supposed | ||||||
|  | 	to be to create a single file of public interface. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
| #include "../SerialBus.hpp" | #include "../SerialBus.hpp" | ||||||
| #include "../../ROMMachine.hpp" | #include "../../ROMMachine.hpp" | ||||||
| #include "../../../Storage/Disk/Disk.hpp" | #include "../../../Storage/Disk/Disk.hpp" | ||||||
| @@ -20,18 +39,9 @@ namespace C1540 { | |||||||
| /*! | /*! | ||||||
| 	Provides an emulation of the C1540. | 	Provides an emulation of the C1540. | ||||||
| */ | */ | ||||||
| class Machine: public MachineBase, public ROMMachine::Machine { | class Machine: public MachineBase { | ||||||
| 	public: | 	public: | ||||||
| 		enum Personality { | 		Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| 			C1540, |  | ||||||
| 			C1541 |  | ||||||
| 		}; |  | ||||||
| 		Machine(Personality p); |  | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Sets the source for this drive's ROM image. |  | ||||||
| 		*/ |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names); |  | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the serial bus to which this drive should attach itself. | 			Sets the serial bus to which this drive should attach itself. | ||||||
| @@ -43,9 +53,6 @@ class Machine: public MachineBase, public ROMMachine::Machine { | |||||||
|  |  | ||||||
| 		/// Inserts @c disk into the drive. | 		/// Inserts @c disk into the drive. | ||||||
| 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | 		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk); | ||||||
|  |  | ||||||
| 	private: |  | ||||||
| 		Personality personality_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|  |  | ||||||
| using namespace Commodore::C1540; | using namespace Commodore::C1540; | ||||||
|  |  | ||||||
| MachineBase::MachineBase() : | MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 		Storage::Disk::Controller(1000000), | 		Storage::Disk::Controller(1000000), | ||||||
| 		m6502_(*this), | 		m6502_(*this), | ||||||
| 		drive_(new Storage::Disk::Drive(1000000, 300, 2)), | 		drive_(new Storage::Disk::Drive(1000000, 300, 2)), | ||||||
| @@ -38,9 +38,22 @@ MachineBase::MachineBase() : | |||||||
|  |  | ||||||
| 	// attach the only drive there is | 	// attach the only drive there is | ||||||
| 	set_drive(drive_); | 	set_drive(drive_); | ||||||
|  |  | ||||||
|  | 	std::string rom_name; | ||||||
|  | 	switch(personality) { | ||||||
|  | 		case Personality::C1540:	rom_name = "1540.bin";	break; | ||||||
|  | 		case Personality::C1541:	rom_name = "1541.bin";	break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {} | 	auto roms = rom_fetcher("Commodore1540", {rom_name}); | ||||||
|  | 	if(!roms[0]) { | ||||||
|  | 		throw ROMMachine::Error::MissingROMs; | ||||||
|  | 	} | ||||||
|  | 	std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
|  | 	MachineBase(personality, rom_fetcher) {} | ||||||
|  |  | ||||||
| void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { | ||||||
| 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | 	Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus); | ||||||
| @@ -82,19 +95,6 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, | |||||||
| 	return Cycles(1); | 	return Cycles(1); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Machine::set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) { |  | ||||||
| 	std::string rom_name; |  | ||||||
| 	switch(personality_) { |  | ||||||
| 		case Personality::C1540:	rom_name = "1540.bin";	break; |  | ||||||
| 		case Personality::C1541:	rom_name = "1541.bin";	break; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	auto roms = roms_with_names("Commodore1540", {rom_name}); |  | ||||||
| 	if(!roms[0]) return false; |  | ||||||
| 	std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size())); |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) { | ||||||
| 	drive_->set_disk(disk); | 	drive_->set_disk(disk); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ | |||||||
|  |  | ||||||
| #include "../../../../Storage/Disk/Controller/DiskController.hpp" | #include "../../../../Storage/Disk/Controller/DiskController.hpp" | ||||||
|  |  | ||||||
|  | #include "../C1540.hpp" | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace C1540 { | namespace C1540 { | ||||||
|  |  | ||||||
| @@ -125,7 +127,7 @@ class MachineBase: | |||||||
| 	public Storage::Disk::Controller { | 	public Storage::Disk::Controller { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		MachineBase(); | 		MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | 		Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); | ||||||
| @@ -141,7 +143,7 @@ class MachineBase: | |||||||
| 		void set_activity_observer(Activity::Observer *observer); | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		CPU::MOS6502::Processor<MachineBase, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_; | ||||||
| 		std::shared_ptr<Storage::Disk::Drive> drive_; | 		std::shared_ptr<Storage::Disk::Drive> drive_; | ||||||
|  |  | ||||||
| 		uint8_t ram_[0x800]; | 		uint8_t ram_[0x800]; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| #include "Keyboard.hpp" | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
| #include "../../../Activity/Source.hpp" | #include "../../../Activity/Source.hpp" | ||||||
| #include "../../ConfigurationTarget.hpp" | #include "../../MediaTarget.hpp" | ||||||
| #include "../../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../../KeyboardMachine.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
| #include "../../JoystickMachine.hpp" | #include "../../JoystickMachine.hpp" | ||||||
| @@ -62,18 +62,6 @@ enum JoystickInput { | |||||||
| 	Fire = 0x20 | 	Fire = 0x20 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum ROM { |  | ||||||
| 	CharactersDanish = 0, |  | ||||||
| 	CharactersEnglish, |  | ||||||
| 	CharactersJapanese, |  | ||||||
| 	CharactersSwedish, |  | ||||||
| 	KernelDanish, |  | ||||||
| 	KernelJapanese, |  | ||||||
| 	KernelNTSC, |  | ||||||
| 	KernelPAL, |  | ||||||
| 	KernelSwedish |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder; | 	Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder; | ||||||
| 	sensing the presence or absence of a tape and controlling the tape motor; and reading the current | 	sensing the presence or absence of a tape and controlling the tape motor; and reading the current | ||||||
| @@ -292,7 +280,7 @@ class Joystick: public Inputs::ConcreteJoystick { | |||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public JoystickMachine::Machine, | 	public JoystickMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| @@ -304,7 +292,7 @@ class ConcreteMachine: | |||||||
| 	public ClockingHint::Observer, | 	public ClockingHint::Observer, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| 				user_port_via_port_handler_(new UserPortVIA), | 				user_port_via_port_handler_(new UserPortVIA), | ||||||
| 				keyboard_via_port_handler_(new KeyboardVIA), | 				keyboard_via_port_handler_(new KeyboardVIA), | ||||||
| @@ -332,127 +320,68 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// install a joystick | 			// install a joystick | ||||||
| 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | 			joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); | ||||||
|  |  | ||||||
|  | 			std::vector<std::string> rom_names = { "basic.bin" }; | ||||||
|  | 			switch(target.region) { | ||||||
|  | 				default: | ||||||
|  | 					rom_names.push_back("characters-english.bin"); | ||||||
|  | 					rom_names.push_back("kernel-pal.bin"); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::Region::American: | ||||||
|  | 					rom_names.push_back("characters-english.bin"); | ||||||
|  | 					rom_names.push_back("kernel-ntsc.bin"); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::Region::Danish: | ||||||
|  | 					rom_names.push_back("characters-danish.bin"); | ||||||
|  | 					rom_names.push_back("kernel-danish.bin"); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::Region::Japanese: | ||||||
|  | 					rom_names.push_back("characters-japanese.bin"); | ||||||
|  | 					rom_names.push_back("kernel-japanese.bin"); | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Commodore::Target::Region::Swedish: | ||||||
|  | 					rom_names.push_back("characters-swedish.bin"); | ||||||
|  | 					rom_names.push_back("kernel-japanese.bin"); | ||||||
|  | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. | 			const auto roms = rom_fetcher("Vic20", rom_names); | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			rom_fetcher_ = roms_with_names; |  | ||||||
|  |  | ||||||
| 			auto roms = roms_with_names( | 			for(const auto &rom: roms) { | ||||||
| 				"Vic20", | 				if(!rom) { | ||||||
| 				{ | 					throw ROMMachine::Error::MissingROMs; | ||||||
| 					"characters-danish.bin", |  | ||||||
| 					"characters-english.bin", |  | ||||||
| 					"characters-japanese.bin", |  | ||||||
| 					"characters-swedish.bin", |  | ||||||
| 					"kernel-danish.bin", |  | ||||||
| 					"kernel-japanese.bin", |  | ||||||
| 					"kernel-ntsc.bin", |  | ||||||
| 					"kernel-pal.bin", |  | ||||||
| 					"kernel-swedish.bin", |  | ||||||
| 					"basic.bin" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				auto &data = roms[index]; |  | ||||||
| 				if(!data) return false; |  | ||||||
| 				if(index < 9) roms_[index] = std::move(*data); else basic_rom_ = std::move(*data); |  | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			basic_rom_ = std::move(*roms[0]); | ||||||
|  | 			character_rom_ = std::move(*roms[1]); | ||||||
|  | 			kernel_rom_ = std::move(*roms[2]); | ||||||
|  |  | ||||||
| 			// Characters ROMs should be 4kb. | 			// Characters ROMs should be 4kb. | ||||||
| 			for(std::size_t index = 0; index < 4; ++index) roms_[index].resize(4096); | 			character_rom_.resize(4096); | ||||||
| 			// Kernel ROMs and the BASIC ROM should be 8kb. | 			// Kernel ROMs and the BASIC ROM should be 8kb. | ||||||
| 			for(std::size_t index = 4; index < roms.size(); ++index) roms_[index].resize(8192); | 			kernel_rom_.resize(8192); | ||||||
|  |  | ||||||
| 			return true; | 			if(target.has_c1540) { | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			commodore_target_ = *dynamic_cast<const Analyser::Static::Commodore::Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(!commodore_target_.loading_command.empty()) { |  | ||||||
| 				type_string(commodore_target_.loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(commodore_target_.has_c1540) { |  | ||||||
| 				// construct the 1540 | 				// construct the 1540 | ||||||
| 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | 				c1540_.reset(new ::Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher)); | ||||||
|  |  | ||||||
| 				// attach it to the serial bus | 				// attach it to the serial bus | ||||||
| 				c1540_->set_serial_bus(serial_bus_); | 				c1540_->set_serial_bus(serial_bus_); | ||||||
|  |  | ||||||
| 				// give it a means to obtain its ROM |  | ||||||
| 				c1540_->set_rom_fetcher(rom_fetcher_); |  | ||||||
|  |  | ||||||
| 				// give it a little warm up | 				// give it a little warm up | ||||||
| 				c1540_->run_for(Cycles(2000000)); | 				c1540_->run_for(Cycles(2000000)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { |  | ||||||
| 			if(!media.tapes.empty()) { |  | ||||||
| 				tape_->set_tape(media.tapes.front()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!media.disks.empty() && c1540_) { |  | ||||||
| 				c1540_->set_disk(media.disks.front()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!media.cartridges.empty()) { |  | ||||||
| 				rom_address_ = 0xa000; |  | ||||||
| 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; |  | ||||||
| 				rom_length_ = static_cast<uint16_t>(rom_image.size()); |  | ||||||
|  |  | ||||||
| 				rom_ = rom_image; |  | ||||||
| 				rom_.resize(0x2000); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			set_use_fast_tape(); |  | ||||||
|  |  | ||||||
| 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { |  | ||||||
| 			if(key != KeyRestore) |  | ||||||
| 				keyboard_via_port_handler_->set_key_state(key, is_pressed); |  | ||||||
| 			else |  | ||||||
| 				user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void clear_all_keys() override final { |  | ||||||
| 			keyboard_via_port_handler_->clear_all_keys(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { |  | ||||||
| 			return joysticks_; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_ntsc_6560() { |  | ||||||
| 			set_clock_rate(1022727); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC); |  | ||||||
| 				mos6560_->set_clock_rate(1022727); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_pal_6560() { |  | ||||||
| 			set_clock_rate(1108404); |  | ||||||
| 			if(mos6560_) { |  | ||||||
| 				mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL); |  | ||||||
| 				mos6560_->set_clock_rate(1108404); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_memory_map(Analyser::Static::Commodore::Target::MemoryModel memory_model, Analyser::Static::Commodore::Target::Region region) { |  | ||||||
| 			// Determine PAL/NTSC | 			// Determine PAL/NTSC | ||||||
| 			if(region == Analyser::Static::Commodore::Target::Region::American || region == Analyser::Static::Commodore::Target::Region::Japanese) { | 			if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) { | ||||||
| 				// NTSC | 				// NTSC | ||||||
| 				set_ntsc_6560(); | 				set_clock_rate(1022727); | ||||||
|  | 				output_mode_ = MOS::MOS6560::OutputMode::NTSC; | ||||||
| 			} else { | 			} else { | ||||||
| 				// PAL | 				// PAL | ||||||
| 				set_pal_6560(); | 				set_clock_rate(1108404); | ||||||
|  | 				output_mode_ = MOS::MOS6560::OutputMode::PAL; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Initialise the memory maps as all pointing to nothing | 			// Initialise the memory maps as all pointing to nothing | ||||||
| @@ -465,7 +394,7 @@ class ConcreteMachine: | |||||||
| 	write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); | 	write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); | ||||||
|  |  | ||||||
| 			// Add 6502-visible RAM as requested | 			// Add 6502-visible RAM as requested | ||||||
| 			switch(memory_model) { | 			switch(target.memory_model) { | ||||||
| 				case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded: | 				case Analyser::Static::Commodore::Target::MemoryModel::Unexpanded: | ||||||
| 					// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000. | 					// The default Vic-20 memory map has 1kb at address 0 and another 4kb at address 0x1000. | ||||||
| 					set_ram(0x0000, 0x0400); | 					set_ram(0x0000, 0x0400); | ||||||
| @@ -512,39 +441,53 @@ class ConcreteMachine: | |||||||
| 			write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); | 			write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, static_cast<uint16_t>(basic_rom_.size())); | ||||||
|  |  | ||||||
| 			// install the system ROM | 			// install the system ROM | ||||||
| 			ROM character_rom; | 			write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, static_cast<uint16_t>(character_rom_.size())); | ||||||
| 			ROM kernel_rom; | 			write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, static_cast<uint16_t>(character_rom_.size())); | ||||||
| 			switch(region) { | 			write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, static_cast<uint16_t>(kernel_rom_.size())); | ||||||
| 				default: |  | ||||||
| 					character_rom = CharactersEnglish; | 			insert_media(target.media); | ||||||
| 					kernel_rom = KernelPAL; | 			if(!target.loading_command.empty()) { | ||||||
| 				break; | 				type_string(target.loading_command); | ||||||
| 				case Analyser::Static::Commodore::Target::Region::American: | 			} | ||||||
| 					character_rom = CharactersEnglish; |  | ||||||
| 					kernel_rom = KernelNTSC; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Danish: |  | ||||||
| 					character_rom = CharactersDanish; |  | ||||||
| 					kernel_rom = KernelDanish; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Japanese: |  | ||||||
| 					character_rom = CharactersJapanese; |  | ||||||
| 					kernel_rom = KernelJapanese; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Commodore::Target::Region::Swedish: |  | ||||||
| 					character_rom = CharactersSwedish; |  | ||||||
| 					kernel_rom = KernelSwedish; |  | ||||||
| 				break; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 			write_to_map(processor_read_memory_map_, roms_[character_rom].data(), 0x8000, static_cast<uint16_t>(roms_[character_rom].size())); | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			write_to_map(mos6560_bus_handler_.video_memory_map, roms_[character_rom].data(), 0x0000, static_cast<uint16_t>(roms_[character_rom].size())); | 			if(!media.tapes.empty()) { | ||||||
| 			write_to_map(processor_read_memory_map_, roms_[kernel_rom].data(), 0xe000, static_cast<uint16_t>(roms_[kernel_rom].size())); | 				tape_->set_tape(media.tapes.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// install the inserted ROM if there is one | 			if(!media.disks.empty() && c1540_) { | ||||||
| 			if(!rom_.empty()) { | 				c1540_->set_disk(media.disks.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(!media.cartridges.empty()) { | ||||||
|  | 				rom_address_ = 0xa000; | ||||||
|  | 				std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data; | ||||||
|  | 				rom_length_ = static_cast<uint16_t>(rom_image.size()); | ||||||
|  |  | ||||||
|  | 				rom_ = rom_image; | ||||||
|  | 				rom_.resize(0x2000); | ||||||
| 				write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_); | 				write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			set_use_fast_tape(); | ||||||
|  |  | ||||||
|  | 			return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
|  | 			if(key != KeyRestore) | ||||||
|  | 				keyboard_via_port_handler_->set_key_state(key, is_pressed); | ||||||
|  | 			else | ||||||
|  | 				user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void clear_all_keys() override final { | ||||||
|  | 			keyboard_via_port_handler_->clear_all_keys(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return joysticks_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy CPU::MOS6502::Processor | 		// to satisfy CPU::MOS6502::Processor | ||||||
| @@ -680,8 +623,8 @@ class ConcreteMachine: | |||||||
| 		void setup_output(float aspect_ratio) override final { | 		void setup_output(float aspect_ratio) override final { | ||||||
| 			mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_)); | 			mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_)); | ||||||
| 			mos6560_->set_high_frequency_cutoff(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | 			mos6560_->set_high_frequency_cutoff(1600);	// There is a 1.6Khz low-pass filter in the Vic-20. | ||||||
| 			// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set. | 			mos6560_->set_output_mode(output_mode_); | ||||||
| 			set_memory_map(commodore_target_.memory_model, commodore_target_.region); | 			mos6560_->set_clock_rate(get_clock_rate()); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void close_output() override final { | 		void close_output() override final { | ||||||
| @@ -760,11 +703,7 @@ class ConcreteMachine: | |||||||
| 		void update_video() { | 		void update_video() { | ||||||
| 			mos6560_->run_for(cycles_since_mos6560_update_.flush()); | 			mos6560_->run_for(cycles_since_mos6560_update_.flush()); | ||||||
| 		} | 		} | ||||||
| 		Analyser::Static::Commodore::Target commodore_target_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; |  | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  roms_[9]; |  | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  character_rom_; | 		std::vector<uint8_t>  character_rom_; | ||||||
| 		std::vector<uint8_t>  basic_rom_; | 		std::vector<uint8_t>  basic_rom_; | ||||||
| @@ -775,8 +714,6 @@ class ConcreteMachine: | |||||||
| 		uint8_t ram_[0x8000]; | 		uint8_t ram_[0x8000]; | ||||||
| 		uint8_t colour_ram_[0x0400]; | 		uint8_t colour_ram_[0x0400]; | ||||||
|  |  | ||||||
| 		std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> rom_fetcher_; |  | ||||||
|  |  | ||||||
| 		uint8_t *processor_read_memory_map_[64]; | 		uint8_t *processor_read_memory_map_[64]; | ||||||
| 		uint8_t *processor_write_memory_map_[64]; | 		uint8_t *processor_write_memory_map_[64]; | ||||||
| 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | 		void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { | ||||||
| @@ -794,6 +731,7 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 		Cycles cycles_since_mos6560_update_; | 		Cycles cycles_since_mos6560_update_; | ||||||
| 		Vic6560BusHandler mos6560_bus_handler_; | 		Vic6560BusHandler mos6560_bus_handler_; | ||||||
|  | 		MOS::MOS6560::OutputMode output_mode_; | ||||||
| 		std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_; | 		std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_; | ||||||
| 		std::shared_ptr<UserPortVIA> user_port_via_port_handler_; | 		std::shared_ptr<UserPortVIA> user_port_via_port_handler_; | ||||||
| 		std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; | 		std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_; | ||||||
| @@ -822,8 +760,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Commodore::Vic20; | using namespace Commodore::Vic20; | ||||||
|  |  | ||||||
| Machine *Machine::Vic20() { | Machine *Machine::Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Vic20::ConcreteMachine; | 	using Target = Analyser::Static::Commodore::Target; | ||||||
|  | 	const Target *const commodore_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Vic20::ConcreteMachine(*commodore_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,6 +10,11 @@ | |||||||
| #define Vic20_hpp | #define Vic20_hpp | ||||||
|  |  | ||||||
| #include "../../../Configurable/Configurable.hpp" | #include "../../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace Commodore { | namespace Commodore { | ||||||
| namespace Vic20 { | namespace Vic20 { | ||||||
| @@ -22,7 +27,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns a Vic-20. | 		/// Creates and returns a Vic-20. | ||||||
| 		static Machine *Vic20(); | 		static Machine *Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,38 +0,0 @@ | |||||||
| // |  | ||||||
| //  ConfigurationTarget.h |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 08/09/2016. |  | ||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef ConfigurationTarget_hpp |  | ||||||
| #define ConfigurationTarget_hpp |  | ||||||
|  |  | ||||||
| #include "../Analyser/Static/StaticAnalyser.hpp" |  | ||||||
| #include "../Configurable/Configurable.hpp" |  | ||||||
|  |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| namespace ConfigurationTarget { |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
| 	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. |  | ||||||
| */ |  | ||||||
| class Machine { |  | ||||||
| 	public: |  | ||||||
| 		/// Instructs the machine to configure itself as described by @c target and insert the included media. |  | ||||||
| 		virtual void configure_as_target(const Analyser::Static::Target *target) = 0; |  | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Requests that the machine insert @c media as a modification to current state |  | ||||||
|  |  | ||||||
| 			@returns @c true if any media was inserted; @c false otherwise. |  | ||||||
| 		*/ |  | ||||||
| 		virtual bool insert_media(const Analyser::Static::Media &media) = 0; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif /* ConfigurationTarget_h */ |  | ||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../Configurable/Configurable.hpp" | #include "../Configurable/Configurable.hpp" | ||||||
| #include "../Activity/Source.hpp" | #include "../Activity/Source.hpp" | ||||||
| #include "ConfigurationTarget.hpp" | #include "MediaTarget.hpp" | ||||||
| #include "CRTMachine.hpp" | #include "CRTMachine.hpp" | ||||||
| #include "JoystickMachine.hpp" | #include "JoystickMachine.hpp" | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
| @@ -27,11 +27,11 @@ struct DynamicMachine { | |||||||
| 	virtual ~DynamicMachine() {} | 	virtual ~DynamicMachine() {} | ||||||
|  |  | ||||||
| 	virtual Activity::Source *activity_source() = 0; | 	virtual Activity::Source *activity_source() = 0; | ||||||
| 	virtual ConfigurationTarget::Machine *configuration_target() = 0; | 	virtual Configurable::Device *configurable_device() = 0; | ||||||
| 	virtual CRTMachine::Machine *crt_machine() = 0; | 	virtual CRTMachine::Machine *crt_machine() = 0; | ||||||
| 	virtual JoystickMachine::Machine *joystick_machine() = 0; | 	virtual JoystickMachine::Machine *joystick_machine() = 0; | ||||||
| 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | 	virtual KeyboardMachine::Machine *keyboard_machine() = 0; | ||||||
| 	virtual Configurable::Device *configurable_device() = 0; | 	virtual MediaTarget::Machine *media_target() = 0; | ||||||
|  |  | ||||||
| 	/*! | 	/*! | ||||||
| 		Provides a raw pointer to the underlying machine if and only if this dynamic machine really is | 		Provides a raw pointer to the underlying machine if and only if this dynamic machine really is | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #include "Electron.hpp" | #include "Electron.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -41,7 +41,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| @@ -49,7 +49,7 @@ class ConcreteMachine: | |||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| 				sound_generator_(audio_queue_), | 				sound_generator_(audio_queue_), | ||||||
| 				speaker_(sound_generator_) { | 				speaker_(sound_generator_) { | ||||||
| @@ -61,60 +61,53 @@ class ConcreteMachine: | |||||||
| 			set_clock_rate(2000000); | 			set_clock_rate(2000000); | ||||||
|  |  | ||||||
| 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | 			speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); | ||||||
|  |  | ||||||
|  | 			std::vector<std::string> rom_names = {"basic.rom", "os.rom"}; | ||||||
|  | 			if(target.has_adfs) { | ||||||
|  | 				rom_names.push_back("ADFS-E00_1.rom"); | ||||||
|  | 				rom_names.push_back("ADFS-E00_2.rom"); | ||||||
|  | 			} | ||||||
|  | 			const size_t dfs_rom_position = rom_names.size(); | ||||||
|  | 			if(target.has_dfs) { | ||||||
|  | 				rom_names.push_back("DFS-1770-2.20.rom"); | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("Electron", rom_names); | ||||||
|  |  | ||||||
|  | 			for(const auto &rom: roms) { | ||||||
|  | 				if(!rom) { | ||||||
|  | 					throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			set_rom(ROM::BASIC, *roms[0], false); | ||||||
|  | 			set_rom(ROM::OS, *roms[1], false); | ||||||
|  |  | ||||||
|  | 			if(target.has_dfs || target.has_adfs) { | ||||||
|  | 				plus3_.reset(new Plus3); | ||||||
|  |  | ||||||
|  | 				if(target.has_dfs) { | ||||||
|  | 					set_rom(ROM::Slot0, *roms[dfs_rom_position], true); | ||||||
|  | 				} | ||||||
|  | 				if(target.has_adfs) { | ||||||
|  | 					set_rom(ROM::Slot4, *roms[2], true); | ||||||
|  | 					set_rom(ROM::Slot5, *roms[3], true); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  |  | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(target.should_shift_restart) { | ||||||
|  | 				shift_restart_counter_ = 1000000; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| 			audio_queue_.flush(); | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final { |  | ||||||
| 			uint8_t *target = nullptr; |  | ||||||
| 			switch(slot) { |  | ||||||
| 				case ROMSlotDFS:	dfs_ = data;			return; |  | ||||||
| 				case ROMSlotADFS1:	adfs1_ = data;			return; |  | ||||||
| 				case ROMSlotADFS2:	adfs2_ = data;			return; |  | ||||||
|  |  | ||||||
| 				case ROMSlotOS:		target = os_;			break; |  | ||||||
| 				default: |  | ||||||
| 					target = roms_[slot]; |  | ||||||
| 					rom_write_masks_[slot] = is_writeable; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// 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; |  | ||||||
| 			} |  | ||||||
| 			rom_inserted_[slot] = true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"Electron", |  | ||||||
| 				{ |  | ||||||
| 					"DFS-1770-2.20.rom", |  | ||||||
| 					"ADFS-E00_1.rom",	"ADFS-E00_2.rom", |  | ||||||
| 					"basic.rom",		"os.rom" |  | ||||||
| 				}); |  | ||||||
| 			ROMSlot slots[] = { |  | ||||||
| 				ROMSlotDFS, |  | ||||||
| 				ROMSlotADFS1, ROMSlotADFS2, |  | ||||||
| 				ROMSlotBASIC, ROMSlotOS |  | ||||||
| 			}; |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				auto &data = roms[index]; |  | ||||||
| 				if(!data) return false; |  | ||||||
| 				set_rom(slots[index], *data, false); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool isPressed) override final { | 		void set_key_state(uint16_t key, bool isPressed) override final { | ||||||
| 			if(key == KeyBreak) { | 			if(key == KeyBreak) { | ||||||
| 				m6502_.set_reset_line(isPressed); | 				m6502_.set_reset_line(isPressed); | ||||||
| @@ -131,32 +124,6 @@ class ConcreteMachine: | |||||||
| 			if(is_holding_shift_) set_key_state(KeyShift, true); | 			if(is_holding_shift_) set_key_state(KeyShift, true); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const acorn_target = dynamic_cast<const Analyser::Static::Acorn::Target *>(target); |  | ||||||
|  |  | ||||||
| 			if(!acorn_target->loading_command.empty()) { |  | ||||||
| 				type_string(acorn_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(acorn_target->should_shift_restart) { |  | ||||||
| 				shift_restart_counter_ = 1000000; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(acorn_target->has_dfs || acorn_target->has_adfs) { |  | ||||||
| 				plus3_.reset(new Plus3); |  | ||||||
|  |  | ||||||
| 				if(acorn_target->has_dfs) { |  | ||||||
| 					set_rom(ROMSlot0, dfs_, true); |  | ||||||
| 				} |  | ||||||
| 				if(acorn_target->has_adfs) { |  | ||||||
| 					set_rom(ROMSlot4, adfs1_, true); |  | ||||||
| 					set_rom(ROMSlot5, adfs2_, true); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::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()); | ||||||
| @@ -167,11 +134,11 @@ class ConcreteMachine: | |||||||
| 				plus3_->set_disk(media.disks.front(), 0); | 				plus3_->set_disk(media.disks.front(), 0); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			ROMSlot slot = ROMSlot12; | 			ROM slot = ROM::Slot12; | ||||||
| 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | 			for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) { | ||||||
| 				const ROMSlot first_slot_tried = slot; | 				const ROM first_slot_tried = slot; | ||||||
| 				while(rom_inserted_[slot]) { | 				while(rom_inserted_[static_cast<int>(slot)]) { | ||||||
| 					slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15); | 					slot = static_cast<ROM>((static_cast<int>(slot) + 1) & 15); | ||||||
| 					if(slot == first_slot_tried) return false; | 					if(slot == first_slot_tried) return false; | ||||||
| 				} | 				} | ||||||
| 				set_rom(slot, cartridge->get_segments().front().data, false); | 				set_rom(slot, cartridge->get_segments().front().data, false); | ||||||
| @@ -258,7 +225,7 @@ class ConcreteMachine: | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							// latch the paged ROM in case external hardware is being emulated | 							// latch the paged ROM in case external hardware is being emulated | ||||||
| 							active_rom_ = (Electron::ROMSlot)(*value & 0xf); | 							active_rom_ = *value & 0xf; | ||||||
|  |  | ||||||
| 							// apply the ULA's test | 							// apply the ULA's test | ||||||
| 							if(*value & 0x08) { | 							if(*value & 0x08) { | ||||||
| @@ -363,7 +330,7 @@ class ConcreteMachine: | |||||||
| 									} | 									} | ||||||
| 								} | 								} | ||||||
| 								if(basic_is_active_) { | 								if(basic_is_active_) { | ||||||
| 									*value &= roms_[ROMSlotBASIC][address & 16383]; | 									*value &= roms_[static_cast<int>(ROM::BASIC)][address & 16383]; | ||||||
| 								} | 								} | ||||||
| 							} else if(rom_write_masks_[active_rom_]) { | 							} else if(rom_write_masks_[active_rom_]) { | ||||||
| 								roms_[active_rom_][address & 16383] = *value; | 								roms_[active_rom_][address & 16383] = *value; | ||||||
| @@ -494,6 +461,50 @@ class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		enum class ROM { | ||||||
|  | 			Slot0 = 0, | ||||||
|  | 			Slot1,	Slot2,	Slot3, | ||||||
|  | 			Slot4,	Slot5,	Slot6,	Slot7, | ||||||
|  |  | ||||||
|  | 			Keyboard = 8,	Slot9, | ||||||
|  | 			BASIC = 10,		Slot11, | ||||||
|  |  | ||||||
|  | 			Slot12,	Slot13,	Slot14,	Slot15, | ||||||
|  |  | ||||||
|  | 			OS,		DFS, | ||||||
|  | 			ADFS1,	ADFS2 | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot | ||||||
|  | 			is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. | ||||||
|  | 		*/ | ||||||
|  | 		void set_rom(ROM slot, const std::vector<uint8_t> &data, bool is_writeable) { | ||||||
|  | 			uint8_t *target = nullptr; | ||||||
|  | 			switch(slot) { | ||||||
|  | 				case ROM::DFS:		dfs_ = data;			return; | ||||||
|  | 				case ROM::ADFS1:	adfs1_ = data;			return; | ||||||
|  | 				case ROM::ADFS2:	adfs2_ = data;			return; | ||||||
|  |  | ||||||
|  | 				case ROM::OS:		target = os_;			break; | ||||||
|  | 				default: | ||||||
|  | 					target = roms_[static_cast<int>(slot)]; | ||||||
|  | 					rom_write_masks_[static_cast<int>(slot)] = is_writeable; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// 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; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if(static_cast<int>(slot) < 16) | ||||||
|  | 				rom_inserted_[static_cast<int>(slot)] = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// MARK: - Work deferral updates. | 		// MARK: - Work deferral updates. | ||||||
| 		inline void update_display() { | 		inline void update_display() { | ||||||
| 			if(cycles_since_display_update_ > 0) { | 			if(cycles_since_display_update_ > 0) { | ||||||
| @@ -530,7 +541,7 @@ class ConcreteMachine: | |||||||
| 			m6502_.set_irq_line(interrupt_status_ & 1); | 			m6502_.set_irq_line(interrupt_status_ & 1); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		// Things that directly constitute the memory map. | 		// Things that directly constitute the memory map. | ||||||
| 		uint8_t roms_[16][16384]; | 		uint8_t roms_[16][16384]; | ||||||
| @@ -540,7 +551,7 @@ class ConcreteMachine: | |||||||
| 		std::vector<uint8_t> dfs_, adfs1_, adfs2_; | 		std::vector<uint8_t> dfs_, adfs1_, adfs2_; | ||||||
|  |  | ||||||
| 		// Paging | 		// Paging | ||||||
| 		ROMSlot active_rom_ = ROMSlot::ROMSlot0; | 		int active_rom_ = static_cast<int>(ROM::Slot0); | ||||||
| 		bool keyboard_is_active_ = false; | 		bool keyboard_is_active_ = false; | ||||||
| 		bool basic_is_active_ = false; | 		bool basic_is_active_ = false; | ||||||
|  |  | ||||||
| @@ -590,8 +601,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace Electron; | using namespace Electron; | ||||||
|  |  | ||||||
| Machine *Machine::Electron() { | Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new Electron::ConcreteMachine; | 	using Target = Analyser::Static::Acorn::Target; | ||||||
|  | 	const Target *const acorn_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new Electron::ConcreteMachine(*acorn_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
| #define Electron_hpp | #define Electron_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| @@ -17,20 +19,6 @@ | |||||||
|  |  | ||||||
| namespace Electron { | namespace Electron { | ||||||
|  |  | ||||||
| enum ROMSlot: uint8_t { |  | ||||||
| 	ROMSlot0 = 0, |  | ||||||
| 	ROMSlot1,	ROMSlot2,	ROMSlot3, |  | ||||||
| 	ROMSlot4,	ROMSlot5,	ROMSlot6,	ROMSlot7, |  | ||||||
|  |  | ||||||
| 	ROMSlotKeyboard = 8,	ROMSlot9, |  | ||||||
| 	ROMSlotBASIC = 10,		ROMSlot11, |  | ||||||
|  |  | ||||||
| 	ROMSlot12,	ROMSlot13,	ROMSlot14,	ROMSlot15, |  | ||||||
|  |  | ||||||
| 	ROMSlotOS,		ROMSlotDFS, |  | ||||||
| 	ROMSlotADFS1,	ROMSlotADFS2 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// @returns The options available for an Electron. | /// @returns The options available for an Electron. | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| @@ -45,13 +33,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Electron. | 		/// Creates and returns an Electron. | ||||||
| 		static Machine *Electron(); | 		static Machine *Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		/*! |  | ||||||
| 			Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot |  | ||||||
| 			is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. |  | ||||||
| 		*/ |  | ||||||
| 		virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,7 +32,8 @@ | |||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../JoystickMachine.hpp" | ||||||
|  | #include "../MediaTarget.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" | ||||||
| @@ -54,13 +55,19 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
|  |  | ||||||
| class AYPortHandler: public GI::AY38910::PortHandler { | class AYPortHandler: public GI::AY38910::PortHandler { | ||||||
| 	public: | 	public: | ||||||
| 		AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {} | 		AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) { | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 			joysticks_.emplace_back(new Joystick); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		void set_port_output(bool port_b, uint8_t value) { | 		void set_port_output(bool port_b, uint8_t value) { | ||||||
| 			if(port_b) { | 			if(port_b) { | ||||||
| 				// Bits 0-3: touchpad handshaking (?) | 				// Bits 0-3: touchpad handshaking (?) | ||||||
| 				// Bit 4-5: monostable timer pulses | 				// Bit 4-5: monostable timer pulses | ||||||
|  |  | ||||||
| 				// Bit 6: joystick select | 				// Bit 6: joystick select | ||||||
|  | 				selected_joystick_ = (value >> 6) & 1; | ||||||
|  |  | ||||||
| 				// Bit 7: code LED, if any | 				// Bit 7: code LED, if any | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -69,29 +76,75 @@ class AYPortHandler: public GI::AY38910::PortHandler { | |||||||
| 			if(!port_b) { | 			if(!port_b) { | ||||||
| 				// Bits 0-5: Joystick (up, down, left, right, A, B) | 				// Bits 0-5: Joystick (up, down, left, right, A, B) | ||||||
| 				// Bit 6: keyboard switch (not universal) | 				// Bit 6: keyboard switch (not universal) | ||||||
|  |  | ||||||
| 				// Bit 7: tape input | 				// Bit 7: tape input | ||||||
| 				return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80); | 				return | ||||||
|  | 					(static_cast<Joystick *>(joysticks_[selected_joystick_].get())->get_state() & 0x3f) | | ||||||
|  | 					0x40 | | ||||||
|  | 					(tape_player_.get_input() ? 0x00 : 0x80); | ||||||
| 			} | 			} | ||||||
| 			return 0xff; | 			return 0xff; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() { | ||||||
|  | 			return joysticks_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		Storage::Tape::BinaryTapePlayer &tape_player_; | 		Storage::Tape::BinaryTapePlayer &tape_player_; | ||||||
|  |  | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_; | ||||||
|  | 		size_t selected_joystick_ = 0; | ||||||
|  | 		class Joystick: public Inputs::ConcreteJoystick { | ||||||
|  | 			public: | ||||||
|  | 				Joystick() : | ||||||
|  | 					ConcreteJoystick({ | ||||||
|  | 						Input(Input::Up), | ||||||
|  | 						Input(Input::Down), | ||||||
|  | 						Input(Input::Left), | ||||||
|  | 						Input(Input::Right), | ||||||
|  | 						Input(Input::Fire, 0), | ||||||
|  | 						Input(Input::Fire, 1), | ||||||
|  | 					}) {} | ||||||
|  |  | ||||||
|  | 				void did_set_input(const Input &input, bool is_active) override { | ||||||
|  | 					uint8_t mask = 0; | ||||||
|  | 					switch(input.type) { | ||||||
|  | 						default: return; | ||||||
|  | 						case Input::Up:		mask = 0x01;	break; | ||||||
|  | 						case Input::Down:	mask = 0x02;	break; | ||||||
|  | 						case Input::Left:	mask = 0x04;	break; | ||||||
|  | 						case Input::Right:	mask = 0x08;	break; | ||||||
|  | 						case Input::Fire: | ||||||
|  | 							if(input.info.control.index >= 2) return; | ||||||
|  | 							mask = input.info.control.index ? 0x20 : 0x10; | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(is_active) state_ &= ~mask; else state_ |= mask; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				uint8_t get_state() { | ||||||
|  | 					return state_; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			private: | ||||||
|  | 				uint8_t state_ = 0xff; | ||||||
|  | 		}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConcreteMachine: | class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
|  | 	public JoystickMachine::Machine, | ||||||
| 	public MemoryMap, | 	public MemoryMap, | ||||||
| 	public ClockingHint::Observer, | 	public ClockingHint::Observer, | ||||||
| 	public Activity::Source { | 	public Activity::Source { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(): | 		ConcreteMachine(const Analyser::Static::MSX::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			i8255_(i8255_port_handler_), | 			i8255_(i8255_port_handler_), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -112,6 +165,51 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| 			// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. | 			// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. | ||||||
| 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | 			mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); | ||||||
|  |  | ||||||
|  | 			// Fetch the necessary ROMs. | ||||||
|  | 			std::vector<std::string> rom_names = {"msx.rom"}; | ||||||
|  | 			if(target.has_disk_drive) { | ||||||
|  | 				rom_names.push_back("disk.rom"); | ||||||
|  | 			} | ||||||
|  | 			const auto roms = rom_fetcher("MSX", rom_names); | ||||||
|  |  | ||||||
|  | 			if(!roms[0] || (target.has_disk_drive && !roms[1])) { | ||||||
|  | 				throw ROMMachine::Error::MissingROMs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			memory_slots_[0].source = std::move(*roms[0]); | ||||||
|  | 			memory_slots_[0].source.resize(32768); | ||||||
|  |  | ||||||
|  | 			for(size_t c = 0; c < 8; ++c) { | ||||||
|  | 				for(size_t slot = 0; slot < 3; ++slot) { | ||||||
|  | 					memory_slots_[slot].read_pointers[c] = unpopulated_; | ||||||
|  | 					memory_slots_[slot].write_pointers[c] = scratch_; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				memory_slots_[3].read_pointers[c] = | ||||||
|  | 				memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			map(0, 0, 0, 32768); | ||||||
|  | 			page_memory(0); | ||||||
|  |  | ||||||
|  | 			// Add a disk cartridge if any disks were supplied. | ||||||
|  | 			if(target.has_disk_drive) { | ||||||
|  | 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); | ||||||
|  | 				memory_slots_[2].source = std::move(*roms[1]); | ||||||
|  | 				memory_slots_[2].source.resize(16384); | ||||||
|  |  | ||||||
|  | 				map(2, 0, 0x4000, 0x2000); | ||||||
|  | 				unmap(2, 0x6000, 0x2000); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Insert the media. | ||||||
|  | 			insert_media(target.media); | ||||||
|  |  | ||||||
|  | 			// Type whatever has been requested. | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -152,25 +250,6 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override { |  | ||||||
| 			auto *const msx_target = dynamic_cast<const Analyser::Static::MSX::Target *>(target); |  | ||||||
|  |  | ||||||
| 			// Add a disk cartridge if any disks were supplied. |  | ||||||
| 			if(msx_target->has_disk_drive) { |  | ||||||
| 				map(2, 0, 0x4000, 0x2000); |  | ||||||
| 				unmap(2, 0x6000, 0x2000); |  | ||||||
| 				memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Insert the media. |  | ||||||
| 			insert_media(target->media); |  | ||||||
|  |  | ||||||
| 			// Type whatever has been requested. |  | ||||||
| 			if(!msx_target->loading_command.empty()) { |  | ||||||
| 				type_string(msx_target->loading_command); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::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(); | ||||||
| @@ -467,39 +546,6 @@ class ConcreteMachine: | |||||||
| 			audio_queue_.perform(); | 			audio_queue_.perform(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			auto roms = roms_with_names( |  | ||||||
| 				"MSX", |  | ||||||
| 				{ |  | ||||||
| 					"msx.rom", |  | ||||||
| 					"disk.rom" |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			if(!roms[0] || !roms[1]) return false; |  | ||||||
|  |  | ||||||
| 			memory_slots_[0].source = std::move(*roms[0]); |  | ||||||
| 			memory_slots_[0].source.resize(32768); |  | ||||||
|  |  | ||||||
| 			memory_slots_[2].source = std::move(*roms[1]); |  | ||||||
| 			memory_slots_[2].source.resize(16384); |  | ||||||
|  |  | ||||||
| 			for(size_t c = 0; c < 8; ++c) { |  | ||||||
| 				for(size_t slot = 0; slot < 3; ++slot) { |  | ||||||
| 					memory_slots_[slot].read_pointers[c] = unpopulated_; |  | ||||||
| 					memory_slots_[slot].write_pointers[c] = scratch_; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				memory_slots_[3].read_pointers[c] = |  | ||||||
| 				memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			map(0, 0, 0, 32768); |  | ||||||
| 			page_memory(0); |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		void set_keyboard_line(int line) { | 		void set_keyboard_line(int line) { | ||||||
| 			selected_key_line_ = line; | 			selected_key_line_ = line; | ||||||
| 		} | 		} | ||||||
| @@ -568,6 +614,11 @@ class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Joysticks | ||||||
|  | 		std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override { | ||||||
|  | 			return ay_port_handler_.get_joysticks(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		DiskROM *get_disk_rom() { | 		DiskROM *get_disk_rom() { | ||||||
| 			return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get()); | 			return dynamic_cast<DiskROM *>(memory_slots_[2].handler.get()); | ||||||
| @@ -683,8 +734,10 @@ class ConcreteMachine: | |||||||
|  |  | ||||||
| using namespace MSX; | using namespace MSX; | ||||||
|  |  | ||||||
| Machine *Machine::MSX() { | Machine *Machine::MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	return new ConcreteMachine; | 	using Target = Analyser::Static::MSX::Target; | ||||||
|  | 	const Target *const msx_target = dynamic_cast<const Target *>(target); | ||||||
|  | 	return new ConcreteMachine(*msx_target, rom_fetcher); | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -10,17 +10,22 @@ | |||||||
| #define MSX_hpp | #define MSX_hpp | ||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
|  | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace MSX { | namespace MSX { | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
| 		static Machine *MSX(); | 		static Machine *MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<Configurable::Option>> get_options(); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* MSX_hpp */ | #endif /* MSX_hpp */ | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								Machines/MediaTarget.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Machines/MediaTarget.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  MediaTarget.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 08/09/2016. | ||||||
|  | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MediaTarget_hpp | ||||||
|  | #define MediaTarget_hpp | ||||||
|  |  | ||||||
|  | #include "../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../Configurable/Configurable.hpp" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace MediaTarget { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A MediaTarget::Machine is anything that can accept new media while running. | ||||||
|  | */ | ||||||
|  | class Machine { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Requests that the machine insert @c media as a modification to current state | ||||||
|  |  | ||||||
|  | 			@returns @c true if any media was inserted; @c false otherwise. | ||||||
|  | 		*/ | ||||||
|  | 		virtual bool insert_media(const Analyser::Static::Media &media) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MediaTarget_hpp */ | ||||||
| @@ -13,7 +13,7 @@ | |||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
| #include "../../Activity/Source.hpp" | #include "../../Activity/Source.hpp" | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -193,7 +193,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { | |||||||
|  |  | ||||||
| template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine: | template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public CPU::MOS6502::BusHandler, | 	public CPU::MOS6502::BusHandler, | ||||||
| @@ -206,9 +206,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 	public Machine { | 	public Machine { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(const Analyser::Static::Oric::Target *target) : | 		ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 				m6502_(*this), | 				m6502_(*this), | ||||||
| 				rom_type_(target ? target->rom : Analyser::Static::Oric::Target::ROM::BASIC10), |  | ||||||
| 				ay8910_(audio_queue_), | 				ay8910_(audio_queue_), | ||||||
| 				speaker_(ay8910_), | 				speaker_(ay8910_), | ||||||
| 				via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), | 				via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), | ||||||
| @@ -222,16 +221,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { | 			if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { | ||||||
| 				diskii_.set_clocking_hint_observer(this); | 				diskii_.set_clocking_hint_observer(this); | ||||||
| 			} | 			} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { |  | ||||||
| 			audio_queue_.flush(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			std::vector<std::string> rom_names = {"colour.rom"}; | 			std::vector<std::string> rom_names = {"colour.rom"}; | ||||||
| 			switch(rom_type_) { | 			switch(target.rom) { | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom");	break; | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom");	break; | ||||||
| 				case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom");	break; | 				case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom");	break; | ||||||
| @@ -242,10 +234,12 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz:	rom_names.push_back("8dos.rom");		break; | 				case Analyser::Static::Oric::Target::DiskInterface::Pravetz:	rom_names.push_back("8dos.rom");		break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			auto roms = roms_with_names("Oric", rom_names); | 			const auto roms = rom_fetcher("Oric", rom_names); | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { | 			for(std::size_t index = 0; index < roms.size(); ++index) { | ||||||
| 				if(!roms[index]) return false; | 				if(!roms[index]) { | ||||||
|  | 					throw ROMMachine::Error::MissingROMs; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			colour_rom_ = std::move(*roms[0]); | 			colour_rom_ = std::move(*roms[0]); | ||||||
| @@ -261,8 +255,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 					pravetz_rom_ = std::move(*roms[2]); | 					pravetz_rom_ = std::move(*roms[2]); | ||||||
| 					pravetz_rom_.resize(512); | 					pravetz_rom_.resize(512); | ||||||
|  |  | ||||||
| 					auto state_machine_rom = roms_with_names("DiskII", {"state-machine-16.rom"}); | 					auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"}); | ||||||
| 					if(!state_machine_rom[0]) return false; | 					if(!state_machine_rom[0]) { | ||||||
|  | 						throw ROMMachine::Error::MissingROMs; | ||||||
|  | 					} | ||||||
| 					diskii_.set_state_machine(*state_machine_rom[0]); | 					diskii_.set_state_machine(*state_machine_rom[0]); | ||||||
| 				} break; | 				} break; | ||||||
| 			} | 			} | ||||||
| @@ -271,9 +267,35 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			rom_.resize(16384); | 			rom_.resize(16384); | ||||||
| 			paged_rom_ = rom_.data(); | 			paged_rom_ = rom_.data(); | ||||||
|  |  | ||||||
| 			if(video_output_) video_output_->set_colour_rom(colour_rom_); | 			switch(target.disk_interface) { | ||||||
|  | 				default: break; | ||||||
|  | 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc: | ||||||
|  | 					microdisc_did_change_paging_flags(µdisc_); | ||||||
|  | 					microdisc_.set_delegate(this); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			return true; | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch(target.rom) { | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::BASIC10: | ||||||
|  | 					tape_get_byte_address_ = 0xe630; | ||||||
|  | 					tape_speed_address_ = 0x67; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::BASIC11: | ||||||
|  | 				case Analyser::Static::Oric::Target::ROM::Pravetz: | ||||||
|  | 					tape_get_byte_address_ = 0xe6c9; | ||||||
|  | 					tape_speed_address_ = 0x024d; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		~ConcreteMachine() { | ||||||
|  | 			audio_queue_.flush(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
| @@ -292,41 +314,10 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 			use_fast_tape_hack_ = activate; | 			use_fast_tape_hack_ = activate; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// to satisfy ConfigurationTarget::Machine |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target); |  | ||||||
|  |  | ||||||
| 			switch(oric_target->disk_interface) { |  | ||||||
| 				default: break; |  | ||||||
| 				case Analyser::Static::Oric::Target::DiskInterface::Microdisc: |  | ||||||
| 					microdisc_did_change_paging_flags(µdisc_); |  | ||||||
| 					microdisc_.set_delegate(this); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if(!oric_target->loading_command.empty()) { |  | ||||||
| 				type_string(oric_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			switch(rom_type_) { |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC10: |  | ||||||
| 					tape_get_byte_address_ = 0xe630; |  | ||||||
| 					tape_speed_address_ = 0x67; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::BASIC11: |  | ||||||
| 				case Analyser::Static::Oric::Target::ROM::Pravetz: |  | ||||||
| 					tape_get_byte_address_ = 0xe6c9; |  | ||||||
| 					tape_speed_address_ = 0x024d; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override final { | 		bool insert_media(const Analyser::Static::Media &media) override final { | ||||||
| 			bool inserted = false; | 			bool inserted = false; | ||||||
|  |  | ||||||
| 			if(media.tapes.size()) { | 			if(!media.tapes.empty()) { | ||||||
| 				tape_player_.set_tape(media.tapes.front()); | 				tape_player_.set_tape(media.tapes.front()); | ||||||
| 				inserted = true; | 				inserted = true; | ||||||
| 			} | 			} | ||||||
| @@ -584,10 +575,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
| 		const uint16_t basic_invisible_ram_top_ = 0xffff; | 		const uint16_t basic_invisible_ram_top_ = 0xffff; | ||||||
| 		const uint16_t basic_visible_ram_top_ = 0xbfff; | 		const uint16_t basic_visible_ram_top_ = 0xbfff; | ||||||
|  |  | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		// RAM and ROM | 		// RAM and ROM | ||||||
| 		Analyser::Static::Oric::Target::ROM rom_type_; |  | ||||||
| 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | ||||||
| 		uint8_t ram_[65536]; | 		uint8_t ram_[65536]; | ||||||
| 		Cycles cycles_since_video_update_; | 		Cycles cycles_since_video_update_; | ||||||
| @@ -650,13 +640,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co | |||||||
|  |  | ||||||
| using namespace Oric; | using namespace Oric; | ||||||
|  |  | ||||||
| Machine *Machine::Oric(const Analyser::Static::Target *target_hint) { | Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint); | 	auto *const oric_target = dynamic_cast<const Analyser::Static::Oric::Target *>(target_hint); | ||||||
| 	using DiskInterface = Analyser::Static::Oric::Target::DiskInterface; | 	using DiskInterface = Analyser::Static::Oric::Target::DiskInterface; | ||||||
| 	switch(oric_target->disk_interface) { | 	switch(oric_target->disk_interface) { | ||||||
| 		default:						return new ConcreteMachine<DiskInterface::None>(oric_target); | 		default:						return new ConcreteMachine<DiskInterface::None>(*oric_target, rom_fetcher); | ||||||
| 		case DiskInterface::Microdisc:	return new ConcreteMachine<DiskInterface::Microdisc>(oric_target); | 		case DiskInterface::Microdisc:	return new ConcreteMachine<DiskInterface::Microdisc>(*oric_target, rom_fetcher); | ||||||
| 		case DiskInterface::Pravetz:	return new ConcreteMachine<DiskInterface::Pravetz>(oric_target); | 		case DiskInterface::Pravetz:	return new ConcreteMachine<DiskInterface::Pravetz>(*oric_target, rom_fetcher); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace Oric { | namespace Oric { | ||||||
|  |  | ||||||
| @@ -25,7 +26,7 @@ class Machine { | |||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		/// Creates and returns an Oric. | 		/// Creates and returns an Oric. | ||||||
| 		static Machine *Oric(const Analyser::Static::Target *target_hint); | 		static Machine *Oric(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,13 +16,18 @@ | |||||||
|  |  | ||||||
| namespace ROMMachine { | namespace ROMMachine { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Defines the signature for a function that must be supplied by the host environment in order to give machines | ||||||
|  | 	a route for fetching any system ROMs they might need. | ||||||
|  |  | ||||||
|  | 	The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects | ||||||
|  | 	to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the | ||||||
|  | 	ROM from @c names that corresponds by index, or else are the nullptr | ||||||
|  | */ | ||||||
| typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher; | 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 { | enum class Error { | ||||||
| 	/*! | 	MissingROMs | ||||||
| 		Provides the machine with a way to obtain such ROMs as it needs. |  | ||||||
| 	*/ |  | ||||||
| 	virtual bool set_rom_fetcher(const ROMFetcher &rom_with_name) { return true; } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,38 +25,38 @@ namespace { | |||||||
|  |  | ||||||
| ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { | ::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { | ||||||
| 	error = Machine::Error::None; | 	error = Machine::Error::None; | ||||||
| 	::Machine::DynamicMachine *machine = nullptr; |  | ||||||
|  | 	Machine::DynamicMachine *machine = nullptr; | ||||||
|  | 	try { | ||||||
|  | #define BindD(name, m)	case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher));	break; | ||||||
|  | #define Bind(m)	BindD(m, m) | ||||||
| 		switch(target->machine) { | 		switch(target->machine) { | ||||||
| 		case Analyser::Machine::AmstradCPC:		machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());				break; | 			Bind(AmstradCPC) | ||||||
| 		case Analyser::Machine::AppleII:		machine = new Machine::TypedDynamicMachine<AppleII::Machine>(AppleII::Machine::AppleII());						break; | 			Bind(AppleII) | ||||||
| 		case Analyser::Machine::Atari2600:		machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());				break; | 			Bind(Atari2600) | ||||||
| 		case Analyser::Machine::ColecoVision:	machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision());	break; | 			BindD(Coleco::Vision, ColecoVision) | ||||||
| 		case Analyser::Machine::Electron:		machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());					break; | 			Bind(Electron) | ||||||
| 		case Analyser::Machine::MSX:			machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX());									break; | 			Bind(MSX) | ||||||
| 		case Analyser::Machine::Oric:			machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric(target));							break; | 			Bind(Oric) | ||||||
| 		case Analyser::Machine::Vic20:			machine = new Machine::TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());		break; | 			BindD(Commodore::Vic20, Vic20) | ||||||
| 		case Analyser::Machine::ZX8081:			machine = new Machine::TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));					break; | 			Bind(ZX8081) | ||||||
|  |  | ||||||
| 			default: | 			default: | ||||||
| 				error = Machine::Error::UnknownMachine; | 				error = Machine::Error::UnknownMachine; | ||||||
| 			return nullptr; | 			return nullptr; | ||||||
| 		} | 		} | ||||||
|  | #undef Bind | ||||||
| 	// TODO: this shouldn't depend on CRT machine's inclusion of ROM machine. | 	} catch(ROMMachine::Error construction_error) { | ||||||
| 	CRTMachine::Machine *crt_machine = machine->crt_machine(); | 		switch(construction_error) { | ||||||
| 	if(crt_machine) { | 			case ROMMachine::Error::MissingROMs: | ||||||
| 		if(!machine->crt_machine()->set_rom_fetcher(rom_fetcher)) { |  | ||||||
| 			delete machine; |  | ||||||
| 				error = Machine::Error::MissingROM; | 				error = Machine::Error::MissingROM; | ||||||
| 			return nullptr; | 			break; | ||||||
|  | 			default: | ||||||
|  | 				error = Machine::Error::UnknownError; | ||||||
|  | 			break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ConfigurationTarget::Machine *configuration_target = machine->configuration_target(); |  | ||||||
| 	if(configuration_target) { |  | ||||||
| 		machine->configuration_target()->configure_as_target(target); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return machine; | 	return machine; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -130,7 +130,6 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin | |||||||
| 	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(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options())); |  | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ namespace Machine { | |||||||
|  |  | ||||||
| enum class Error { | enum class Error { | ||||||
| 	None, | 	None, | ||||||
|  | 	UnknownError, | ||||||
| 	UnknownMachine, | 	UnknownMachine, | ||||||
| 	MissingROM, | 	MissingROM, | ||||||
| 	NoTargets | 	NoTargets | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine | |||||||
| 			return get<Activity::Source>(); | 			return get<Activity::Source>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ConfigurationTarget::Machine *configuration_target() override { | 		MediaTarget::Machine *media_target() override { | ||||||
| 			return get<ConfigurationTarget::Machine>(); | 			return get<MediaTarget::Machine>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		CRTMachine::Machine *crt_machine() override { | 		CRTMachine::Machine *crt_machine() override { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| #include "ZX8081.hpp" | #include "ZX8081.hpp" | ||||||
|  |  | ||||||
| #include "../ConfigurationTarget.hpp" | #include "../MediaTarget.hpp" | ||||||
| #include "../CRTMachine.hpp" | #include "../CRTMachine.hpp" | ||||||
| #include "../KeyboardMachine.hpp" | #include "../KeyboardMachine.hpp" | ||||||
|  |  | ||||||
| @@ -59,14 +59,14 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() { | |||||||
|  |  | ||||||
| template<bool is_zx81> class ConcreteMachine: | template<bool is_zx81> class ConcreteMachine: | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| 	public ConfigurationTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| 	public Configurable::Device, | 	public Configurable::Device, | ||||||
| 	public Utility::TypeRecipient, | 	public Utility::TypeRecipient, | ||||||
| 	public CPU::Z80::BusHandler, | 	public CPU::Z80::BusHandler, | ||||||
| 	public Machine { | 	public Machine { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine() : | 		ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
| 			z80_(*this), | 			z80_(*this), | ||||||
| 			tape_player_(ZX8081ClockRate), | 			tape_player_(ZX8081ClockRate), | ||||||
| 			ay_(audio_queue_), | 			ay_(audio_queue_), | ||||||
| @@ -74,6 +74,55 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			set_clock_rate(ZX8081ClockRate); | 			set_clock_rate(ZX8081ClockRate); | ||||||
| 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f); | 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f); | ||||||
| 			clear_all_keys(); | 			clear_all_keys(); | ||||||
|  |  | ||||||
|  | 			const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM; | ||||||
|  | 			const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" }); | ||||||
|  | 			if(!roms[0]) throw ROMMachine::Error::MissingROMs; | ||||||
|  |  | ||||||
|  | 			rom_ = std::move(*roms[0]); | ||||||
|  | 			rom_.resize(use_zx81_rom ? 8192 : 4096); | ||||||
|  |  | ||||||
|  | 			if(is_zx81) { | ||||||
|  | 				tape_trap_address_ = 0x37c; | ||||||
|  | 				tape_return_address_ = 0x380; | ||||||
|  | 				vsync_start_ = HalfCycles(32); | ||||||
|  | 				vsync_end_ = HalfCycles(64); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0340; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x03c3; | ||||||
|  | 			} else { | ||||||
|  | 				tape_trap_address_ = 0x220; | ||||||
|  | 				tape_return_address_ = 0x248; | ||||||
|  | 				vsync_start_ = HalfCycles(26); | ||||||
|  | 				vsync_end_ = HalfCycles(66); | ||||||
|  | 				automatic_tape_motor_start_address_ = 0x0206; | ||||||
|  | 				automatic_tape_motor_end_address_ = 0x024d; | ||||||
|  | 			} | ||||||
|  | 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); | ||||||
|  |  | ||||||
|  | 			switch(target.memory_model) { | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: | ||||||
|  | 					ram_.resize(1024); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 1023; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: | ||||||
|  | 					ram_.resize(16384); | ||||||
|  | 					ram_base_ = 16384; | ||||||
|  | 					ram_mask_ = 16383; | ||||||
|  | 				break; | ||||||
|  | 				case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: | ||||||
|  | 					ram_.resize(65536); | ||||||
|  | 					ram_base_ = 8192; | ||||||
|  | 					ram_mask_ = 65535; | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			Memory::Fuzz(ram_); | ||||||
|  |  | ||||||
|  | 			if(!target.loading_command.empty()) { | ||||||
|  | 				type_string(target.loading_command); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			insert_media(target.media); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -105,7 +154,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 				video_->run_for(cycle.length); | 				video_->run_for(cycle.length); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207)); | 			if(is_zx81) horizontal_counter_ %= HalfCycles(Cycles(207)); | ||||||
| 			if(!tape_advance_delay_) { | 			if(!tape_advance_delay_) { | ||||||
| 				tape_player_.run_for(cycle.length); | 				tape_player_.run_for(cycle.length); | ||||||
| 			} else { | 			} else { | ||||||
| @@ -129,7 +178,7 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 						set_vsync(false); | 						set_vsync(false); | ||||||
| 					} | 					} | ||||||
| 					if(!(address & 2)) nmi_is_enabled_ = false; | 					if(!(address & 2)) nmi_is_enabled_ = false; | ||||||
| 					if(!(address & 1)) nmi_is_enabled_ = is_zx81_; | 					if(!(address & 1)) nmi_is_enabled_ = is_zx81; | ||||||
|  |  | ||||||
| 					// The below emulates the ZonX AY expansion device. | 					// The below emulates the ZonX AY expansion device. | ||||||
| 					if(is_zx81) { | 					if(is_zx81) { | ||||||
| @@ -281,54 +330,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 			z80_.run_for(cycles); | 			z80_.run_for(cycles); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void configure_as_target(const Analyser::Static::Target *target) override final { |  | ||||||
| 			auto *const zx8081_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target); |  | ||||||
| 			is_zx81_ = zx8081_target->is_ZX81; |  | ||||||
| 			if(is_zx81_) { |  | ||||||
| 				rom_ = zx81_rom_; |  | ||||||
| 				tape_trap_address_ = 0x37c; |  | ||||||
| 				tape_return_address_ = 0x380; |  | ||||||
| 				vsync_start_ = HalfCycles(32); |  | ||||||
| 				vsync_end_ = HalfCycles(64); |  | ||||||
| 				automatic_tape_motor_start_address_ = 0x0340; |  | ||||||
| 				automatic_tape_motor_end_address_ = 0x03c3; |  | ||||||
| 			} else { |  | ||||||
| 				rom_ = zx8081_target->ZX80_uses_ZX81_ROM ? zx81_rom_ : zx80_rom_; |  | ||||||
| 				tape_trap_address_ = 0x220; |  | ||||||
| 				tape_return_address_ = 0x248; |  | ||||||
| 				vsync_start_ = HalfCycles(26); |  | ||||||
| 				vsync_end_ = HalfCycles(66); |  | ||||||
| 				automatic_tape_motor_start_address_ = 0x0206; |  | ||||||
| 				automatic_tape_motor_end_address_ = 0x024d; |  | ||||||
| 			} |  | ||||||
| 			rom_mask_ = static_cast<uint16_t>(rom_.size() - 1); |  | ||||||
|  |  | ||||||
| 			switch(zx8081_target->memory_model) { |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::Unexpanded: |  | ||||||
| 					ram_.resize(1024); |  | ||||||
| 					ram_base_ = 16384; |  | ||||||
| 					ram_mask_ = 1023; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::SixteenKB: |  | ||||||
| 					ram_.resize(16384); |  | ||||||
| 					ram_base_ = 16384; |  | ||||||
| 					ram_mask_ = 16383; |  | ||||||
| 				break; |  | ||||||
| 				case Analyser::Static::ZX8081::Target::MemoryModel::SixtyFourKB: |  | ||||||
| 					ram_.resize(65536); |  | ||||||
| 					ram_base_ = 8192; |  | ||||||
| 					ram_mask_ = 65535; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 			Memory::Fuzz(ram_); |  | ||||||
|  |  | ||||||
| 			if(!zx8081_target->loading_command.empty()) { |  | ||||||
| 				type_string(zx8081_target->loading_command); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			insert_media(target->media); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::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()); | ||||||
| @@ -339,30 +340,10 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void type_string(const std::string &string) override final { | 		void type_string(const std::string &string) override final { | ||||||
| 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_)); | 			std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81)); | ||||||
| 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | 			Utility::TypeRecipient::add_typer(string, std::move(mapper)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Obtains the system ROMs. |  | ||||||
| 		bool set_rom_fetcher(const ROMMachine::ROMFetcher &roms_with_names) override { |  | ||||||
| 			const auto roms = roms_with_names( |  | ||||||
| 				"ZX8081", |  | ||||||
| 				{ |  | ||||||
| 					"zx80.rom",	"zx81.rom", |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 			for(std::size_t index = 0; index < roms.size(); ++index) { |  | ||||||
| 				if(!roms[index]) return false; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			zx80_rom_ = std::move(*roms[0]); |  | ||||||
| 			zx81_rom_ = std::move(*roms[1]); |  | ||||||
| 			zx80_rom_.resize(4096); |  | ||||||
| 			zx81_rom_.resize(8192); |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// MARK: - Keyboard | 		// MARK: - Keyboard | ||||||
| 		void set_key_state(uint16_t key, bool is_pressed) override final { | 		void set_key_state(uint16_t key, bool is_pressed) override final { | ||||||
| 			if(is_pressed) | 			if(is_pressed) | ||||||
| @@ -436,7 +417,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; | 		CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_; | ||||||
|  |  | ||||||
| 		std::unique_ptr<Video> video_; | 		std::unique_ptr<Video> video_; | ||||||
| 		std::vector<uint8_t> zx81_rom_, zx80_rom_; |  | ||||||
|  |  | ||||||
| 		uint16_t tape_trap_address_, tape_return_address_; | 		uint16_t tape_trap_address_, tape_return_address_; | ||||||
| 		uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_; | 		uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_; | ||||||
| @@ -456,7 +436,6 @@ template<bool is_zx81> class ConcreteMachine: | |||||||
| 		HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; | 		HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_; | ||||||
| 		Storage::Tape::ZX8081::Parser parser_; | 		Storage::Tape::ZX8081::Parser parser_; | ||||||
|  |  | ||||||
| 		bool is_zx81_; |  | ||||||
| 		bool nmi_is_enabled_ = false; | 		bool nmi_is_enabled_ = false; | ||||||
|  |  | ||||||
| 		HalfCycles vsync_start_, vsync_end_; | 		HalfCycles vsync_start_, vsync_end_; | ||||||
| @@ -522,14 +501,12 @@ 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 Analyser::Static::Target *target_hint) { | Machine *Machine::ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	const Analyser::Static::ZX8081::Target *const hint = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target_hint); | 	const Analyser::Static::ZX8081::Target *const zx_target = dynamic_cast<const Analyser::Static::ZX8081::Target *>(target); | ||||||
|  |  | ||||||
| 	// Instantiate the correct type of machine. | 	// Instantiate the correct type of machine. | ||||||
| 	if(hint->is_ZX81) | 	if(zx_target->is_ZX81)	return new ZX8081::ConcreteMachine<true>(*zx_target, rom_fetcher); | ||||||
| 		return new ZX8081::ConcreteMachine<true>(); | 	else					return new ZX8081::ConcreteMachine<false>(*zx_target, rom_fetcher); | ||||||
| 	else |  | ||||||
| 		return new ZX8081::ConcreteMachine<false>(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Machine::~Machine() {} | Machine::~Machine() {} | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include "../../Configurable/Configurable.hpp" | #include "../../Configurable/Configurable.hpp" | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
|  | #include "../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace ZX8081 { | namespace ZX8081 { | ||||||
|  |  | ||||||
| @@ -21,7 +22,7 @@ class Machine { | |||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|  |  | ||||||
| 		static Machine *ZX8081(const Analyser::Static::Target *target_hint); | 		static Machine *ZX8081(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); | ||||||
|  |  | ||||||
| 		virtual void set_tape_is_playing(bool is_playing) = 0; | 		virtual void set_tape_is_playing(bool is_playing) = 0; | ||||||
| 		virtual bool get_tape_is_playing() = 0; | 		virtual bool get_tape_is_playing() = 0; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| 	objects = { | 	objects = { | ||||||
|  |  | ||||||
| /* Begin PBXBuildFile section */ | /* Begin PBXBuildFile section */ | ||||||
|  | 		4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; }; | ||||||
| 		4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; | 		4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; | ||||||
| 		4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | 		4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | ||||||
| 		4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | 		4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; | ||||||
| @@ -595,8 +596,8 @@ | |||||||
| 		4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; | 		4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; | ||||||
| 		4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; | 		4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; | ||||||
| 		4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; | 		4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; | ||||||
| 		4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; }; | 		4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; }; | ||||||
| 		4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */; }; | 		4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; }; | ||||||
| 		4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | 		4BBB70A8202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | ||||||
| 		4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | 		4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */; }; | ||||||
| 		4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; | 		4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; | ||||||
| @@ -605,6 +606,7 @@ | |||||||
| 		4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; | 		4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; | ||||||
| 		4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; | 		4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; | ||||||
| 		4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; | 		4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; | ||||||
|  | 		4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; }; | ||||||
| 		4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | 		4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; | ||||||
| 		4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | 		4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | ||||||
| 		4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | 		4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; }; | ||||||
| @@ -614,6 +616,8 @@ | |||||||
| 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | ||||||
| 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | ||||||
| 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | ||||||
|  | 		4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; }; | ||||||
|  | 		4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */; }; | ||||||
| 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | ||||||
| 		4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; | 		4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; | ||||||
| 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | ||||||
| @@ -687,6 +691,7 @@ | |||||||
| /* End PBXCopyFilesBuildPhase section */ | /* End PBXCopyFilesBuildPhase section */ | ||||||
|  |  | ||||||
| /* Begin PBXFileReference section */ | /* Begin PBXFileReference section */ | ||||||
|  | 		4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; }; | ||||||
| 		4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; }; | 		4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; }; | ||||||
| 		4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; }; | 		4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; }; | 		4B0333AE2094081A0050B93D /* AppleDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AppleDSK.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1002,6 +1007,7 @@ | |||||||
| 		4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | 		4B894516201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | 		4B894517201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; }; | 		4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockDeferrer.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; }; | 		4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; }; | 		4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; }; | 		4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1027,7 +1033,7 @@ | |||||||
| 		4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | 		4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; }; | 		4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; }; | ||||||
| 		4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; }; | 		4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; }; | ||||||
| 		4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; sourceTree = "<group>"; }; | 		4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; }; | 		4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; }; | 		4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; }; | 		4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1324,8 +1330,8 @@ | |||||||
| 		4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; }; | 		4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; }; | 		4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; }; | 		4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiConfigurationTarget.hpp; sourceTree = "<group>"; }; | 		4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurationTarget.cpp; sourceTree = "<group>"; }; | 		4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; }; | 		4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; }; | ||||||
| @@ -1343,6 +1349,8 @@ | |||||||
| 		4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; }; | 		4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; }; | 		4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; }; | 		4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; }; | ||||||
|  | 		4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; }; | ||||||
| 		4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; }; | 		4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; }; | 		4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; }; | 		4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1356,6 +1364,8 @@ | |||||||
| 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | ||||||
|  | 		4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Rectangle.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Rectangle.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | ||||||
| @@ -1538,14 +1548,15 @@ | |||||||
| 		4B1414631B588A1100E04248 /* Test Binaries */ = { | 		4B1414631B588A1100E04248 /* Test Binaries */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, |  | ||||||
| 				4B9252CD1E74D28200B76AF1 /* Atari ROMs */, | 				4B9252CD1E74D28200B76AF1 /* Atari ROMs */, | ||||||
| 				4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, | 				4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, | ||||||
|  | 				4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, | ||||||
|  | 				4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */, | ||||||
| 				4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, | 				4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, | ||||||
| 				4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, | 				4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, | ||||||
| 				4BE9A6B21EDE294200CBCB47 /* Zexall */, |  | ||||||
| 				4BBF49B41ED2881600AB3669 /* FUSE */, | 				4BBF49B41ED2881600AB3669 /* FUSE */, | ||||||
| 				4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */, | 				4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */, | ||||||
|  | 				4BE9A6B21EDE294200CBCB47 /* Zexall */, | ||||||
| 			); | 			); | ||||||
| 			name = "Test Binaries"; | 			name = "Test Binaries"; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -2756,6 +2767,7 @@ | |||||||
| 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
|  | 				4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, | ||||||
| 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | ||||||
| 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | ||||||
| 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | ||||||
| @@ -2820,7 +2832,7 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, | 				4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, | ||||||
| 				4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, | 				4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */, | ||||||
| 				4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, | 				4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, | ||||||
| 				4BBB709C2020109C002FE009 /* DynamicMachine.hpp */, | 				4BBB709C2020109C002FE009 /* DynamicMachine.hpp */, | ||||||
| 				4B7041271F92C26900735E45 /* JoystickMachine.hpp */, | 				4B7041271F92C26900735E45 /* JoystickMachine.hpp */, | ||||||
| @@ -2858,16 +2870,16 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */, | 				4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */, | ||||||
| 				4BBB70A3202011C2002FE009 /* MultiConfigurationTarget.cpp */, |  | ||||||
| 				4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */, | 				4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */, | ||||||
| 				4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */, | 				4B1B88C6202E469300B67DFF /* MultiJoystickMachine.cpp */, | ||||||
| 				4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */, | 				4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */, | ||||||
|  | 				4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */, | ||||||
| 				4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */, | 				4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */, | ||||||
| 				4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */, | 				4B1B88BF202E3DB200B67DFF /* MultiConfigurable.hpp */, | ||||||
| 				4BBB70A2202011C2002FE009 /* MultiConfigurationTarget.hpp */, |  | ||||||
| 				4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */, | 				4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */, | ||||||
| 				4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */, | 				4B1B88C7202E469300B67DFF /* MultiJoystickMachine.hpp */, | ||||||
| 				4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */, | 				4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */, | ||||||
|  | 				4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */, | ||||||
| 				4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */, | 				4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */, | ||||||
| 			); | 			); | ||||||
| 			path = Implementation; | 			path = Implementation; | ||||||
| @@ -2896,6 +2908,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */, | 				4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */, | ||||||
| 				4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, | 				4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, | ||||||
|  | 				4BC891AB20F6EAB300EDE5B3 /* Rectangle.cpp */, | ||||||
| 				4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, | 				4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, | ||||||
| 				4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, | 				4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, | ||||||
| 				4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */, | 				4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */, | ||||||
| @@ -2903,6 +2916,7 @@ | |||||||
| 				4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, | 				4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, | ||||||
| 				4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, | 				4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, | ||||||
| 				4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, | 				4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, | ||||||
|  | 				4BC891AC20F6EAB300EDE5B3 /* Rectangle.hpp */, | ||||||
| 				4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, | 				4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, | ||||||
| 				4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, | 				4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, | ||||||
| 				4BC3B74C1CD194CC00F86E85 /* Shaders */, | 				4BC3B74C1CD194CC00F86E85 /* Shaders */, | ||||||
| @@ -2910,6 +2924,15 @@ | |||||||
| 			path = Internals; | 			path = Internals; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BBFE83B21015D9C00BF1C40 /* Joystick Manager */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */, | ||||||
|  | 				4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */, | ||||||
|  | 			); | ||||||
|  | 			path = "Joystick Manager"; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4BC3B74C1CD194CC00F86E85 /* Shaders */ = { | 		4BC3B74C1CD194CC00F86E85 /* Shaders */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -3127,6 +3150,7 @@ | |||||||
| 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | 				4BB06B211F316A3F00600C7A /* ForceInline.hpp */, | ||||||
| 				4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, | 				4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, | ||||||
| 				4B449C942063389900A095C8 /* TimeTypes.hpp */, | 				4B449C942063389900A095C8 /* TimeTypes.hpp */, | ||||||
|  | 				4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */, | ||||||
| 			); | 			); | ||||||
| 			name = ClockReceiver; | 			name = ClockReceiver; | ||||||
| 			path = ../../ClockReceiver; | 			path = ../../ClockReceiver; | ||||||
| @@ -3311,6 +3335,7 @@ | |||||||
| 				4BB2998A1B587D8400A49093 /* lseix in Resources */, | 				4BB2998A1B587D8400A49093 /* lseix in Resources */, | ||||||
| 				4BB2994E1B587D8400A49093 /* dexn in Resources */, | 				4BB2994E1B587D8400A49093 /* dexn in Resources */, | ||||||
| 				4BB299971B587D8400A49093 /* nopa in Resources */, | 				4BB299971B587D8400A49093 /* nopa in Resources */, | ||||||
|  | 				4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */, | ||||||
| 				4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */, | 				4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */, | ||||||
| 				4BB299521B587D8400A49093 /* eoray in Resources */, | 				4BB299521B587D8400A49093 /* eoray in Resources */, | ||||||
| 				4BB299411B587D8400A49093 /* cpyb in Resources */, | 				4BB299411B587D8400A49093 /* cpyb in Resources */, | ||||||
| @@ -3614,6 +3639,7 @@ | |||||||
| 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | ||||||
| 				4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
|  | 				4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */, | ||||||
| 				4B894539201967B4007DE474 /* Tape.cpp in Sources */, | 				4B894539201967B4007DE474 /* Tape.cpp in Sources */, | ||||||
| 				4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */, | 				4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */, | ||||||
| 				4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, | 				4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, | ||||||
| @@ -3691,7 +3717,7 @@ | |||||||
| 				4BAD13441FF709C700FD114A /* MSX.cpp in Sources */, | 				4BAD13441FF709C700FD114A /* MSX.cpp in Sources */, | ||||||
| 				4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */, | 				4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */, | 				4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */, | ||||||
| 				4BBB70A5202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */, | 				4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | ||||||
| 				4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */, | 				4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */, | ||||||
| 				4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */, | 				4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */, | ||||||
| 				4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, | 				4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, | ||||||
| @@ -3817,6 +3843,7 @@ | |||||||
| 				4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, | 				4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, | ||||||
| 				4B89452C201967B4007DE474 /* Tape.cpp in Sources */, | 				4B89452C201967B4007DE474 /* Tape.cpp in Sources */, | ||||||
| 				4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, | 				4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, | ||||||
|  | 				4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */, | ||||||
| 				4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */, | 				4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */, | ||||||
| 				4B89451C201967B4007DE474 /* Disk.cpp in Sources */, | 				4B89451C201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
| 				4B302184208A550100773308 /* DiskII.cpp in Sources */, | 				4B302184208A550100773308 /* DiskII.cpp in Sources */, | ||||||
| @@ -3840,6 +3867,7 @@ | |||||||
| 				4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, | 				4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, | ||||||
| 				4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, | 				4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, | ||||||
| 				4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, | 				4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, | ||||||
|  | 				4BC891AD20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */, | ||||||
| 				4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, | 				4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, | ||||||
| 				4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */, | 				4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */, | ||||||
| 				4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| @@ -3856,7 +3884,7 @@ | |||||||
| 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | ||||||
| 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | ||||||
| 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
| 				4BBB70A4202011C2002FE009 /* MultiConfigurationTarget.cpp in Sources */, | 				4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | ||||||
| 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | ||||||
| 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | ||||||
|   | |||||||
| @@ -111,6 +111,17 @@ | |||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </menuItem> | ||||||
|                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> |                             <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> | ||||||
|  |                             <menuItem title="Save Screenshot" keyEquivalent="D" id="BVJ-oQ-hUp"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem title="Insert..." keyEquivalent="O" id="qQa-kh-4nz"> | ||||||
|  |                                 <connections> | ||||||
|  |                                     <action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/> | ||||||
|  |                                 </connections> | ||||||
|  |                             </menuItem> | ||||||
|  |                             <menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/> | ||||||
|                             <menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK"> |                             <menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK"> | ||||||
|                                 <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |                                 <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> | ||||||
|                                 <connections> |                                 <connections> | ||||||
|   | |||||||
| @@ -4,6 +4,12 @@ | |||||||
| <dict> | <dict> | ||||||
| 	<key>com.apple.security.app-sandbox</key> | 	<key>com.apple.security.app-sandbox</key> | ||||||
| 	<true/> | 	<true/> | ||||||
|  | 	<key>com.apple.security.assets.pictures.read-write</key> | ||||||
|  | 	<true/> | ||||||
|  | 	<key>com.apple.security.device.bluetooth</key> | ||||||
|  | 	<true/> | ||||||
|  | 	<key>com.apple.security.device.usb</key> | ||||||
|  | 	<true/> | ||||||
| 	<key>com.apple.security.files.user-selected.read-write</key> | 	<key>com.apple.security.files.user-selected.read-write</key> | ||||||
| 	<true/> | 	<true/> | ||||||
| </dict> | </dict> | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ | |||||||
|  |  | ||||||
| #import	"CSOpenGLView.h" | #import	"CSOpenGLView.h" | ||||||
| #import "CSAudioQueue.h" | #import "CSAudioQueue.h" | ||||||
|  |  | ||||||
| #import "CSBestEffortUpdater.h" | #import "CSBestEffortUpdater.h" | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
| #include "KeyCodes.h" | #include "KeyCodes.h" | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ | |||||||
| import Cocoa | import Cocoa | ||||||
|  |  | ||||||
| class DocumentController: NSDocumentController { | class DocumentController: NSDocumentController { | ||||||
|  | 	let joystickManager = CSJoystickManager() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ | |||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. | //  Copyright 2016 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
| import Cocoa |  | ||||||
| import AudioToolbox | import AudioToolbox | ||||||
|  | import Cocoa | ||||||
|  |  | ||||||
| class MachineDocument: | class MachineDocument: | ||||||
| 	NSDocument, | 	NSDocument, | ||||||
| @@ -56,7 +56,6 @@ class MachineDocument: | |||||||
| 		super.windowControllerDidLoadNib(aController) | 		super.windowControllerDidLoadNib(aController) | ||||||
| 		aController.window?.contentAspectRatio = self.aspectRatio() | 		aController.window?.contentAspectRatio = self.aspectRatio() | ||||||
| 		setupMachineOutput() | 		setupMachineOutput() | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | 	// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in | ||||||
| @@ -213,6 +212,7 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Runtime media insertion. | ||||||
| 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | 	final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) { | ||||||
| 		let mediaSet = CSMediaSet(fileAt: URL) | 		let mediaSet = CSMediaSet(fileAt: URL) | ||||||
| 		if let mediaSet = mediaSet { | 		if let mediaSet = mediaSet { | ||||||
| @@ -220,6 +220,21 @@ class MachineDocument: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	@IBAction final func insertMedia(_ sender: AnyObject!) { | ||||||
|  | 		let openPanel = NSOpenPanel() | ||||||
|  | 		openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window." | ||||||
|  | 		openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in | ||||||
|  | 			if response == .OK { | ||||||
|  | 				for url in openPanel.urls { | ||||||
|  | 					let mediaSet = CSMediaSet(fileAt: url) | ||||||
|  | 					if let mediaSet = mediaSet { | ||||||
|  | 						mediaSet.apply(to: self.machine) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: NSDocument overrides | 	// MARK: NSDocument overrides | ||||||
| 	override func data(ofType typeName: String) throws -> Data { | 	override func data(ofType typeName: String) throws -> Data { | ||||||
| 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | 		throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) | ||||||
| @@ -229,6 +244,13 @@ class MachineDocument: | |||||||
| 	func windowDidResignKey(_ notification: Notification) { | 	func windowDidResignKey(_ notification: Notification) { | ||||||
| 		if let machine = self.machine { | 		if let machine = self.machine { | ||||||
| 			machine.clearAllKeys() | 			machine.clearAllKeys() | ||||||
|  | 			machine.joystickManager = nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func windowDidBecomeKey(_ notification: Notification) { | ||||||
|  | 		if let machine = self.machine { | ||||||
|  | 			machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -259,7 +281,7 @@ class MachineDocument: | |||||||
| 	@IBAction func createMachine(_ sender: NSButton?) { | 	@IBAction func createMachine(_ sender: NSButton?) { | ||||||
| 		self.configureAs(machinePicker!.selectedMachine()) | 		self.configureAs(machinePicker!.selectedMachine()) | ||||||
| 		machinePicker = nil | 		machinePicker = nil | ||||||
| 		sender?.window?.close() | 		self.windowControllers[0].window?.endSheet(self.machinePickerPanel!) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | 	@IBAction func cancelCreateMachine(_ sender: NSButton?) { | ||||||
| @@ -280,6 +302,7 @@ class MachineDocument: | |||||||
| 			switch item.action { | 			switch item.action { | ||||||
| 				case #selector(self.useKeyboardAsKeyboard): | 				case #selector(self.useKeyboardAsKeyboard): | ||||||
| 					if machine == nil || !machine.hasKeyboard { | 					if machine == nil || !machine.hasKeyboard { | ||||||
|  | 						menuItem.state = .off | ||||||
| 						return false | 						return false | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -288,6 +311,7 @@ class MachineDocument: | |||||||
|  |  | ||||||
| 				case #selector(self.useKeyboardAsJoystick): | 				case #selector(self.useKeyboardAsJoystick): | ||||||
| 					if machine == nil || !machine.hasJoystick { | 					if machine == nil || !machine.hasJoystick { | ||||||
|  | 						menuItem.state = .off | ||||||
| 						return false | 						return false | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -297,12 +321,38 @@ class MachineDocument: | |||||||
| 				case #selector(self.showActivity(_:)): | 				case #selector(self.showActivity(_:)): | ||||||
| 					return self.activityPanel != nil | 					return self.activityPanel != nil | ||||||
|  |  | ||||||
|  | 				case #selector(self.insertMedia(_:)): | ||||||
|  | 					return self.machine != nil && self.machine.canInsertMedia | ||||||
|  |  | ||||||
| 				default: break | 				default: break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return super.validateUserInterfaceItem(item) | 		return super.validateUserInterfaceItem(item) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Screenshot capture. | ||||||
|  | 	@IBAction func saveScreenshot(_ sender: AnyObject!) { | ||||||
|  | 		// Grab a date formatter and form a file name. | ||||||
|  | 		let dateFormatter = DateFormatter() | ||||||
|  | 		dateFormatter.dateStyle = .short | ||||||
|  | 		dateFormatter.timeStyle = .long | ||||||
|  |  | ||||||
|  | 		let filename = ("Clock Signal Screen Shot " + dateFormatter.string(from: Date()) + ".png").replacingOccurrences(of: "/", with: "-") | ||||||
|  | 			.replacingOccurrences(of: ":", with: ".") | ||||||
|  | 		let pictursURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0] | ||||||
|  | 		let url = pictursURL.appendingPathComponent(filename) | ||||||
|  |  | ||||||
|  | 		// Obtain the machine's current display. | ||||||
|  | 		var imageRepresentation: NSBitmapImageRep? = nil | ||||||
|  | 		self.openGLView.perform { | ||||||
|  | 			imageRepresentation = self.machine.imageRepresentation | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Encode as a PNG and save. | ||||||
|  | 		let pngData = imageRepresentation!.representation(using: .png, properties: [:]) | ||||||
|  | 		try! pngData?.write(to: url) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// MARK: Activity display. | 	// MARK: Activity display. | ||||||
| 	class LED { | 	class LED { | ||||||
| 		let levelIndicator: NSLevelIndicator | 		let levelIndicator: NSLevelIndicator | ||||||
|   | |||||||
| @@ -13,39 +13,43 @@ | |||||||
| 				<string>bin</string> | 				<string>bin</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge</string> | 			<string>cartridge.png</string> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Atari 2600 Cartridge</string> | 			<string>Atari 2600 Cartridge</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>rom</string> | 				<string>rom</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>chip</string> | 			<string>chip.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ROM Image</string> | 			<string>ROM Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -53,100 +57,110 @@ | |||||||
| 				<string>uef</string> | 				<string>uef</string> | ||||||
| 				<string>uef.gz</string> | 				<string>uef.gz</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC UEF Image</string> | 			<string>Electron/BBC UEF Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>prg</string> | 				<string>prg</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Program</string> | 			<string>Commodore Program</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tap</string> | 				<string>tap</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>g64</string> | 				<string>g64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Disk</string> | 			<string>Commodore Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>d64</string> | 				<string>d64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore 1540/1 Disk</string> | 			<string>Commodore 1540/1 Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -157,40 +171,44 @@ | |||||||
| 				<string>adl</string> | 				<string>adl</string> | ||||||
| 				<string>adm</string> | 				<string>adm</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC Disk Image</string> | 			<string>Electron/BBC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dsk</string> | 				<string>dsk</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Editor</string> | 			<string>Editor</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -198,20 +216,22 @@ | |||||||
| 				<string>o</string> | 				<string>o</string> | ||||||
| 				<string>80</string> | 				<string>80</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX80 Tape Image</string> | 			<string>ZX80 Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -220,178 +240,196 @@ | |||||||
| 				<string>81</string> | 				<string>81</string> | ||||||
| 				<string>p81</string> | 				<string>p81</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ZX81 Tape Image</string> | 			<string>ZX81 Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>csw</string> | 				<string>csw</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tzx</string> | 				<string>tzx</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cdt</string> | 				<string>cdt</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Amstrad CPC Tape Image</string> | 			<string>Amstrad CPC Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>hfe</string> | 				<string>hfe</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>HxC Disk Image</string> | 			<string>HxC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>cas</string> | 				<string>cas</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>dmk</string> | 				<string>dmk</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy35</string> | 			<string>floppy35.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>tsx</string> | 				<string>tsx</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cassette</string> | 			<string>cassette.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>MSX Tape Image</string> | 			<string>MSX Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>col</string> | 				<string>col</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>cartridge</string> | 			<string>cartridge.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ColecoVision Cartridge</string> | 			<string>ColecoVision Cartridge</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 		<dict> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| @@ -401,20 +439,22 @@ | |||||||
| 				<string>do</string> | 				<string>do</string> | ||||||
| 				<string>po</string> | 				<string>po</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeOSTypes</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>????</string> | ||||||
|  | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string>floppy525</string> | 			<string>floppy525.png</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Apple II Disk Image</string> | 			<string>Apple II Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| 			<string>Viewer</string> | 			<string>Viewer</string> | ||||||
| 			<key>LSItemContentTypes</key> |  | ||||||
| 			<array> |  | ||||||
| 				<string>public.item</string> |  | ||||||
| 			</array> |  | ||||||
| 			<key>LSTypeIsPackage</key> | 			<key>LSTypeIsPackage</key> | ||||||
| 			<integer>0</integer> | 			<false/> | ||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
|  | 			<key>LSHandlerRank</key> | ||||||
|  | 			<string>Owner</string> | ||||||
| 		</dict> | 		</dict> | ||||||
| 	</array> | 	</array> | ||||||
| 	<key>CFBundleExecutable</key> | 	<key>CFBundleExecutable</key> | ||||||
| @@ -436,7 +476,7 @@ | |||||||
| 	<key>CFBundleVersion</key> | 	<key>CFBundleVersion</key> | ||||||
| 	<string>1</string> | 	<string>1</string> | ||||||
| 	<key>LSApplicationCategoryType</key> | 	<key>LSApplicationCategoryType</key> | ||||||
| 	<string></string> | 	<string>public.app-category.entertainment</string> | ||||||
| 	<key>LSMinimumSystemVersion</key> | 	<key>LSMinimumSystemVersion</key> | ||||||
| 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | 	<string>$(MACOSX_DEPLOYMENT_TARGET)</string> | ||||||
| 	<key>NSHumanReadableCopyright</key> | 	<key>NSHumanReadableCopyright</key> | ||||||
|   | |||||||
| @@ -0,0 +1,80 @@ | |||||||
|  | // | ||||||
|  | //  CSJoystickManager.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 19/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a single joystick button. | ||||||
|  | 	Buttons have an index and are either currently pressed, or not. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickButton: NSObject | ||||||
|  | /// The button index. By convention the USB spec defines the first button as number 1. | ||||||
|  | @property(nonatomic, readonly) NSInteger index; | ||||||
|  | @property(nonatomic, readonly) bool isPressed; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | typedef NS_ENUM(NSInteger, CSJoystickAxisType) { | ||||||
|  | 	CSJoystickAxisTypeX, | ||||||
|  | 	CSJoystickAxisTypeY, | ||||||
|  | 	CSJoystickAxisTypeZ, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick axis. | ||||||
|  | 	Axes have a nominated type and a continuous value between 0 and 1. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickAxis: NSObject | ||||||
|  | @property(nonatomic, readonly) CSJoystickAxisType type; | ||||||
|  | /// The current position of this axis in the range [0, 1]. | ||||||
|  | @property(nonatomic, readonly) float position; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | typedef NS_OPTIONS(NSInteger, CSJoystickHatDirection) { | ||||||
|  | 	CSJoystickHatDirectionUp = 1 << 0, | ||||||
|  | 	CSJoystickHatDirectionDown = 1 << 1, | ||||||
|  | 	CSJoystickHatDirectionLeft = 1 << 2, | ||||||
|  | 	CSJoystickHatDirectionRight = 1 << 3, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick hat. | ||||||
|  | 	A hat is a digital directional input, so e.g. this is how thumbpads are represented. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickHat: NSObject | ||||||
|  | @property(nonatomic, readonly) CSJoystickHatDirection direction; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a joystick. | ||||||
|  |  | ||||||
|  | 	A joystick is a collection of buttons, axes and hats, each of which holds a current | ||||||
|  | 	state. The holder must use @c update to cause this joystick to read a fresh copy | ||||||
|  | 	of its state. | ||||||
|  | */ | ||||||
|  | @interface CSJoystick: NSObject | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickButton *> *buttons; | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickAxis *> *axes; | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystickHat *> *hats; | ||||||
|  |  | ||||||
|  | - (void)update; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	The joystick manager watches for joystick connections and disconnections and | ||||||
|  | 	offers a list of joysticks currently attached. | ||||||
|  |  | ||||||
|  | 	Be warned: this means using Apple's IOKit directly to watch for Bluetooth and | ||||||
|  | 	USB HID devices. So to use this code, make sure you have USB and Bluetooth | ||||||
|  | 	enabled for the app's sandbox. | ||||||
|  | */ | ||||||
|  | @interface CSJoystickManager : NSObject | ||||||
|  | @property(nonatomic, readonly) NSArray<CSJoystick *> *joysticks; | ||||||
|  |  | ||||||
|  | /// Updates all joysticks. | ||||||
|  | - (void)update; | ||||||
|  | @end | ||||||
							
								
								
									
										333
									
								
								OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | |||||||
|  | // | ||||||
|  | //  CSJoystickManager.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 19/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
|  | @import IOKit; | ||||||
|  | #include <IOKit/hid/IOHIDLib.h> | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickButton | ||||||
|  |  | ||||||
|  | @implementation CSJoystickButton { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_index = index; | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setIsPressed:(bool)isPressed { | ||||||
|  | 	_isPressed = isPressed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickAxis | ||||||
|  |  | ||||||
|  | @implementation CSJoystickAxis { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 		_type = type; | ||||||
|  | 		_position = 0.5f; | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setPosition:(float)position { | ||||||
|  | 	_position = position; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickHat | ||||||
|  |  | ||||||
|  | @implementation CSJoystickHat { | ||||||
|  | 	IOHIDElementRef _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithElement:(IOHIDElementRef)element { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_element = (IOHIDElementRef)CFRetain(element); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_element); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDElementRef)element { | ||||||
|  | 	return _element; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setDirection:(CSJoystickHatDirection)direction { | ||||||
|  | 	_direction = direction; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystick | ||||||
|  |  | ||||||
|  | @implementation CSJoystick { | ||||||
|  | 	IOHIDDeviceRef _device; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons | ||||||
|  | 	axes:(NSArray<CSJoystickAxis *> *)axes | ||||||
|  | 	hats:(NSArray<CSJoystickHat *> *)hats | ||||||
|  | 	device:(IOHIDDeviceRef)device { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		// Sort buttons by index. | ||||||
|  | 		_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]]; | ||||||
|  |  | ||||||
|  | 		// Sort axes by enum value. | ||||||
|  | 		_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]]; | ||||||
|  |  | ||||||
|  | 		// Hats have no guaranteed ordering. | ||||||
|  | 		_hats = hats; | ||||||
|  |  | ||||||
|  | 		// Keep hold of the device. | ||||||
|  | 		_device = (IOHIDDeviceRef)CFRetain(device); | ||||||
|  | 	} | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	CFRelease(_device); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSString *)description { | ||||||
|  | 	return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)update { | ||||||
|  | 	// Update buttons. | ||||||
|  | 	for(CSJoystickButton *button in _buttons) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			// Some pressure-sensitive buttons return values greater than 1 for hard presses, | ||||||
|  | 			// but this class rationalised everything to Boolean. | ||||||
|  | 			button.isPressed = !!IOHIDValueGetIntegerValue(value); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update hats. | ||||||
|  | 	for(CSJoystickHat *hat in _hats) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			// Hats report a direction, which is either one of eight or one of four. | ||||||
|  | 			CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(hat.element); | ||||||
|  | 			const CFIndex range = 1 + IOHIDElementGetLogicalMax(hat.element) - IOHIDElementGetLogicalMin(hat.element); | ||||||
|  | 			integerValue *= 8 / range; | ||||||
|  |  | ||||||
|  | 			// Map from the HID direction to the bit field. | ||||||
|  | 			switch(integerValue) { | ||||||
|  | 				default:	hat.direction = 0;															break; | ||||||
|  | 				case 0:		hat.direction = CSJoystickHatDirectionUp;									break; | ||||||
|  | 				case 1:		hat.direction = CSJoystickHatDirectionUp | CSJoystickHatDirectionRight;		break; | ||||||
|  | 				case 2:		hat.direction = CSJoystickHatDirectionRight;								break; | ||||||
|  | 				case 3:		hat.direction = CSJoystickHatDirectionRight | CSJoystickHatDirectionDown;	break; | ||||||
|  | 				case 4:		hat.direction = CSJoystickHatDirectionDown;									break; | ||||||
|  | 				case 5:		hat.direction = CSJoystickHatDirectionDown | CSJoystickHatDirectionLeft;	break; | ||||||
|  | 				case 6:		hat.direction = CSJoystickHatDirectionLeft;									break; | ||||||
|  | 				case 7:		hat.direction = CSJoystickHatDirectionLeft | CSJoystickHatDirectionUp;		break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update axes. | ||||||
|  | 	for(CSJoystickAxis *axis in _axes) { | ||||||
|  | 		IOHIDValueRef value; | ||||||
|  | 		if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) { | ||||||
|  | 			const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element); | ||||||
|  | 			const CFIndex range = 1 + IOHIDElementGetLogicalMax(axis.element) - IOHIDElementGetLogicalMin(axis.element); | ||||||
|  | 			axis.position = (float)integerValue / (float)range; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (IOHIDDeviceRef)device { | ||||||
|  | 	return _device; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | #pragma mark - CSJoystickManager | ||||||
|  |  | ||||||
|  | @interface CSJoystickManager () | ||||||
|  | - (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender; | ||||||
|  | - (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { | ||||||
|  | 	[(__bridge  CSJoystickManager *)context deviceMatched:device result:result sender:sender]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { | ||||||
|  | 	[(__bridge  CSJoystickManager *)context deviceRemoved:device result:result sender:sender]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @implementation CSJoystickManager { | ||||||
|  | 	IOHIDManagerRef _hidManager; | ||||||
|  | 	NSMutableArray<CSJoystick *> *_joysticks; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)init { | ||||||
|  | 	self = [super init]; | ||||||
|  | 	if(self) { | ||||||
|  | 		_joysticks = [[NSMutableArray alloc] init]; | ||||||
|  |  | ||||||
|  | 		_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); | ||||||
|  | 		if(!_hidManager) return nil; | ||||||
|  |  | ||||||
|  | 		NSArray<NSDictionary<NSString *, NSNumber *> *> *const multiple = @[ | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick) }, | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_GamePad) }, | ||||||
|  | 			@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_MultiAxisController) }, | ||||||
|  | 		]; | ||||||
|  |  | ||||||
|  | 		IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)multiple); | ||||||
|  | 		IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self); | ||||||
|  | 		IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self); | ||||||
|  | 		IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | ||||||
|  |  | ||||||
|  | 		if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { | ||||||
|  | 			NSLog(@"Failed to open HID manager"); | ||||||
|  | 			// something | ||||||
|  | 			return nil; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)dealloc { | ||||||
|  | 	IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | ||||||
|  | 	IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); | ||||||
|  | 	CFRelease(_hidManager); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender { | ||||||
|  | 	// Double check this joystick isn't already known. | ||||||
|  | 	for(CSJoystick *joystick in _joysticks) { | ||||||
|  | 		if(joystick.device == device) return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Prepare to collate a list of buttons, axes and hats for the new device. | ||||||
|  | 	NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init]; | ||||||
|  | 	NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init]; | ||||||
|  | 	NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init]; | ||||||
|  |  | ||||||
|  | 	// Inspect all elements for those that are comprehensible to this code. | ||||||
|  | 	const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); | ||||||
|  | 	for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) { | ||||||
|  | 		const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index); | ||||||
|  |  | ||||||
|  | 		// Check that this element is either on the generic desktop page or else is a button. | ||||||
|  | 		const uint32_t usagePage = IOHIDElementGetUsagePage(element); | ||||||
|  | 		if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue; | ||||||
|  |  | ||||||
|  | 		// Then inspect the type. | ||||||
|  | 		switch(IOHIDElementGetType(element)) { | ||||||
|  | 			default: break; | ||||||
|  |  | ||||||
|  | 			case kIOHIDElementTypeInput_Button: { | ||||||
|  | 				// Add a button; pretty easy stuff. 'Usage' provides a button index. | ||||||
|  | 				const uint32_t usage = IOHIDElementGetUsage(element); | ||||||
|  | 				[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]]; | ||||||
|  | 			} break; | ||||||
|  |  | ||||||
|  | 			case kIOHIDElementTypeInput_Misc: | ||||||
|  | 			case kIOHIDElementTypeInput_Axis: { | ||||||
|  | 				CSJoystickAxisType axisType; | ||||||
|  | 				switch(IOHIDElementGetUsage(element)) { | ||||||
|  | 					default: continue; | ||||||
|  |  | ||||||
|  | 					// Three analogue axes are implemented here; there are another three sets | ||||||
|  | 					// of these that could be parsed in the future if interesting. | ||||||
|  | 					case kHIDUsage_GD_X:	axisType = CSJoystickAxisTypeX;		break; | ||||||
|  | 					case kHIDUsage_GD_Y:	axisType = CSJoystickAxisTypeY;		break; | ||||||
|  | 					case kHIDUsage_GD_Z:	axisType = CSJoystickAxisTypeZ;		break; | ||||||
|  |  | ||||||
|  | 					// A hatswitch is a multi-directional control all of its own. | ||||||
|  | 					case kHIDUsage_GD_Hatswitch: | ||||||
|  | 						[hats addObject:[[CSJoystickHat alloc] initWithElement:element]]; | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Add the axis; if it was a hat switch or unrecognised then the code doesn't | ||||||
|  | 				// reach here. | ||||||
|  | 				[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]]; | ||||||
|  | 			} break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	CFRelease(elements); | ||||||
|  |  | ||||||
|  | 	// Add this joystick to the list. | ||||||
|  | 	[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender { | ||||||
|  | 	// If this joystick was recorded, remove it. | ||||||
|  | 	for(CSJoystick *joystick in [_joysticks copy]) { | ||||||
|  | 		if(joystick.device == device) { | ||||||
|  | 			[_joysticks removeObject:joystick]; | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)update { | ||||||
|  | 	[self.joysticks makeObjectsPerformSelector:@selector(update)]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (NSArray<CSJoystick *> *)joysticks { | ||||||
|  | 	return [_joysticks copy]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -12,6 +12,7 @@ | |||||||
| #import "CSFastLoading.h" | #import "CSFastLoading.h" | ||||||
| #import "CSOpenGLView.h" | #import "CSOpenGLView.h" | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
|  | #import "CSJoystickManager.h" | ||||||
|  |  | ||||||
| @class CSMachine; | @class CSMachine; | ||||||
| @protocol CSMachineDelegate | @protocol CSMachineDelegate | ||||||
| @@ -38,6 +39,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| @interface CSMachine : NSObject | @interface CSMachine : NSObject | ||||||
|  |  | ||||||
| - (nonnull instancetype)init NS_UNAVAILABLE; | - (nonnull instancetype)init NS_UNAVAILABLE; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Initialises an instance of CSMachine. | 	Initialises an instance of CSMachine. | ||||||
|  |  | ||||||
| @@ -63,17 +65,21 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| @property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix; | @property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix; | ||||||
|  |  | ||||||
| - (void)paste:(nonnull NSString *)string; | - (void)paste:(nonnull NSString *)string; | ||||||
|  | @property (nonatomic, readonly, nonnull) NSBitmapImageRep *imageRepresentation; | ||||||
|  |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | ||||||
| @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | ||||||
|  |  | ||||||
|  | @property (nonatomic, readonly) BOOL canInsertMedia; | ||||||
|  |  | ||||||
| - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal; | - (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal; | ||||||
|  |  | ||||||
| // Input control. | // Input control. | ||||||
| @property (nonatomic, readonly) BOOL hasKeyboard; | @property (nonatomic, readonly) BOOL hasKeyboard; | ||||||
| @property (nonatomic, readonly) BOOL hasJoystick; | @property (nonatomic, readonly) BOOL hasJoystick; | ||||||
| @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | ||||||
|  | @property (nonatomic, nullable) CSJoystickManager *joystickManager; | ||||||
|  |  | ||||||
| // LED list. | // LED list. | ||||||
| @property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds; | @property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| #include "CSROMFetcher.hpp" | #include "CSROMFetcher.hpp" | ||||||
|  |  | ||||||
| #include "ConfigurationTarget.hpp" | #include "MediaTarget.hpp" | ||||||
| #include "JoystickMachine.hpp" | #include "JoystickMachine.hpp" | ||||||
| #include "KeyboardMachine.hpp" | #include "KeyboardMachine.hpp" | ||||||
| #include "KeyCodes.h" | #include "KeyCodes.h" | ||||||
| @@ -57,9 +57,6 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 		[machine addLED:[NSString stringWithUTF8String:name.c_str()]]; | 		[machine addLED:[NSString stringWithUTF8String:name.c_str()]]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	void register_drive(const std::string &name) override { |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	void set_led_status(const std::string &name, bool lit) override { | 	void set_led_status(const std::string &name, bool lit) override { | ||||||
| 		[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit]; | 		[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit]; | ||||||
| 	} | 	} | ||||||
| @@ -68,9 +65,6 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 		[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]]; | 		[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	void set_drive_motor_status(const std::string &name, bool is_on) override { |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	__unsafe_unretained CSMachine *machine; | 	__unsafe_unretained CSMachine *machine; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -81,7 +75,9 @@ struct ActivityObserver: public Activity::Observer { | |||||||
|  |  | ||||||
| 	CSStaticAnalyser *_analyser; | 	CSStaticAnalyser *_analyser; | ||||||
| 	std::unique_ptr<Machine::DynamicMachine> _machine; | 	std::unique_ptr<Machine::DynamicMachine> _machine; | ||||||
|  | 	JoystickMachine::Machine *_joystickMachine; | ||||||
|  |  | ||||||
|  | 	CSJoystickManager *_joystickManager; | ||||||
| 	std::bitset<65536> _depressedKeys; | 	std::bitset<65536> _depressedKeys; | ||||||
| 	NSMutableArray<NSString *> *_leds; | 	NSMutableArray<NSString *> *_leds; | ||||||
| } | } | ||||||
| @@ -108,6 +104,8 @@ struct ActivityObserver: public Activity::Observer { | |||||||
|  |  | ||||||
| 		_speakerDelegate.machine = self; | 		_speakerDelegate.machine = self; | ||||||
| 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | 		_speakerDelegate.machineAccessLock = _delegateMachineAccessLock; | ||||||
|  |  | ||||||
|  | 		_joystickMachine = _machine->joystick_machine(); | ||||||
| 	} | 	} | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @@ -168,6 +166,54 @@ struct ActivityObserver: public Activity::Observer { | |||||||
|  |  | ||||||
| - (void)runForInterval:(NSTimeInterval)interval { | - (void)runForInterval:(NSTimeInterval)interval { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
|  | 		if(_joystickMachine && _joystickManager) { | ||||||
|  | 			[_joystickManager update]; | ||||||
|  |  | ||||||
|  | 			// TODO: configurable mapping from physical joypad inputs to machine inputs. | ||||||
|  | 			// Until then, apply a default mapping. | ||||||
|  |  | ||||||
|  | 			size_t c = 0; | ||||||
|  | 			std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks(); | ||||||
|  | 			for(CSJoystick *joystick in _joystickManager.joysticks) { | ||||||
|  | 				size_t target = c % machine_joysticks.size(); | ||||||
|  | 				++++c; | ||||||
|  |  | ||||||
|  | 				// Post the first two analogue axes presented by the controller as horizontal and vertical inputs, | ||||||
|  | 				// unless the user seems to be using a hat. | ||||||
|  | 				// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0] | ||||||
|  | 				if(!joystick.hats.count || !joystick.hats[0].direction) { | ||||||
|  | 					if(joystick.axes.count > 0) { | ||||||
|  | 						const float x_axis = joystick.axes[0].position; | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis); | ||||||
|  | 					} | ||||||
|  | 					if(joystick.axes.count > 1) { | ||||||
|  | 						const float y_axis = joystick.axes[1].position; | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					// Forward hats as directions; hats always override analogue inputs. | ||||||
|  | 					for(CSJoystickHat *hat in joystick.hats) { | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft)); | ||||||
|  | 						machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight)); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Forward all fire buttons, mapping as a function of index. | ||||||
|  | 				if(machine_joysticks[target]->get_number_of_fire_buttons()) { | ||||||
|  | 					std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons()); | ||||||
|  | 					for(CSJoystickButton *button in joystick.buttons) { | ||||||
|  | 						if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true; | ||||||
|  | 					} | ||||||
|  | 					for(size_t index = 0; index < button_states.size(); ++index) { | ||||||
|  | 						machine_joysticks[target]->set_input( | ||||||
|  | 							Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index), | ||||||
|  | 							button_states[index]); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		_machine->crt_machine()->run_for(interval); | 		_machine->crt_machine()->run_for(interval); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -197,10 +243,59 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 		keyboardMachine->type_string([paste UTF8String]); | 		keyboardMachine->type_string([paste UTF8String]); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (NSBitmapImageRep *)imageRepresentation { | ||||||
|  | 	// Get the current viewport to establish framebuffer size. Then determine how wide the | ||||||
|  | 	// centre 4/3 of that would be. | ||||||
|  | 	GLint dimensions[4]; | ||||||
|  | 	glGetIntegerv(GL_VIEWPORT, dimensions); | ||||||
|  | 	GLint proportionalWidth = (dimensions[3] * 4) / 3; | ||||||
|  |  | ||||||
|  | 	// Grab the framebuffer contents. | ||||||
|  | 	std::vector<uint8_t> temporaryData(static_cast<size_t>(proportionalWidth * dimensions[3] * 3)); | ||||||
|  | 	glReadPixels((dimensions[2] - proportionalWidth) >> 1, 0, proportionalWidth, dimensions[3], GL_RGB, GL_UNSIGNED_BYTE, temporaryData.data()); | ||||||
|  |  | ||||||
|  | 	// Generate an NSBitmapImageRep and populate it with a vertical flip | ||||||
|  | 	// of the original data. | ||||||
|  | 	NSBitmapImageRep *const result = | ||||||
|  | 		[[NSBitmapImageRep alloc] | ||||||
|  | 			initWithBitmapDataPlanes:NULL | ||||||
|  | 			pixelsWide:proportionalWidth | ||||||
|  | 			pixelsHigh:dimensions[3] | ||||||
|  | 			bitsPerSample:8 | ||||||
|  | 			samplesPerPixel:3 | ||||||
|  | 			hasAlpha:NO | ||||||
|  | 			isPlanar:NO | ||||||
|  | 			colorSpaceName:NSDeviceRGBColorSpace | ||||||
|  | 			bytesPerRow:3 * proportionalWidth | ||||||
|  | 			bitsPerPixel:0]; | ||||||
|  |  | ||||||
|  | 	const size_t line_size = static_cast<size_t>(proportionalWidth * 3); | ||||||
|  | 	for(GLint y = 0; y < dimensions[3]; ++y) { | ||||||
|  | 		memcpy( | ||||||
|  | 			&result.bitmapData[static_cast<size_t>(y) * line_size], | ||||||
|  | 			&temporaryData[static_cast<size_t>(dimensions[3] - y - 1) * line_size], | ||||||
|  | 			line_size); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
| - (void)applyMedia:(const Analyser::Static::Media &)media { | - (void)applyMedia:(const Analyser::Static::Media &)media { | ||||||
| 	@synchronized(self) { | 	@synchronized(self) { | ||||||
| 		ConfigurationTarget::Machine *const configurationTarget = _machine->configuration_target(); | 		MediaTarget::Machine *const mediaTarget = _machine->media_target(); | ||||||
| 		if(configurationTarget) configurationTarget->insert_media(media); | 		if(mediaTarget) mediaTarget->insert_media(media); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)setJoystickManager:(CSJoystickManager *)joystickManager { | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		_joystickManager = joystickManager; | ||||||
|  | 		if(_joystickMachine) { | ||||||
|  | 			std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks(); | ||||||
|  | 			for(const auto &joystick: machine_joysticks) { | ||||||
|  | 				joystick->reset_all_inputs(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -300,7 +395,7 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 					case VK_ANSI_D:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed);	break; | 					case VK_ANSI_D:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed);	break; | ||||||
| 					case VK_ANSI_F:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed);	break; | 					case VK_ANSI_F:		joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed);	break; | ||||||
| 					default: | 					default: | ||||||
| 						if(characters) { | 						if(characters.length) { | ||||||
| 							joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed); | 							joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed); | ||||||
| 						} else { | 						} else { | ||||||
| 							joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); | 							joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); | ||||||
| @@ -421,6 +516,10 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	return [[NSString stringWithUTF8String:name.c_str()] lowercaseString]; | 	return [[NSString stringWithUTF8String:name.c_str()] lowercaseString]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (BOOL)canInsertMedia { | ||||||
|  | 	return !!_machine->media_target(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #pragma mark - Special machines | #pragma mark - Special machines | ||||||
|  |  | ||||||
| - (CSAtari2600 *)atari2600 { | - (CSAtari2600 *)atari2600 { | ||||||
|   | |||||||
| @@ -12,7 +12,9 @@ | |||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | ||||||
| 	CSMachineAppleIIModelAppleII, | 	CSMachineAppleIIModelAppleII, | ||||||
| 	CSMachineAppleIIModelAppleIIPlus | 	CSMachineAppleIIModelAppleIIPlus, | ||||||
|  | 	CSMachineAppleIIModelAppleIIe, | ||||||
|  | 	CSMachineAppleIIModelAppleEnhancedIIe | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | ||||||
|   | |||||||
| @@ -168,7 +168,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| 		using Target = Analyser::Static::AppleII::Target; | 		using Target = Analyser::Static::AppleII::Target; | ||||||
| 		std::unique_ptr<Target> target(new Target); | 		std::unique_ptr<Target> target(new Target); | ||||||
| 		target->machine = Analyser::Machine::AppleII; | 		target->machine = Analyser::Machine::AppleII; | ||||||
| 		target->model = (model == CSMachineAppleIIModelAppleII) ? Target::Model::II : Target::Model::IIplus; | 		switch(model) { | ||||||
|  | 			default: 									target->model = Target::Model::II; 				break; | ||||||
|  | 			case CSMachineAppleIIModelAppleIIPlus:		target->model = Target::Model::IIplus;			break; | ||||||
|  | 			case CSMachineAppleIIModelAppleIIe:			target->model = Target::Model::IIe;				break; | ||||||
|  | 			case CSMachineAppleIIModelAppleEnhancedIIe:	target->model = Target::Model::EnhancedIIe;		break; | ||||||
|  | 		} | ||||||
| 		switch(diskController) { | 		switch(diskController) { | ||||||
| 			default: | 			default: | ||||||
| 			case CSMachineAppleIIDiskControllerNone:			target->disk_controller = Target::DiskController::None;				break; | 			case CSMachineAppleIIDiskControllerNone:			target->disk_controller = Target::DiskController::None;				break; | ||||||
| @@ -184,7 +189,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| - (NSString *)optionsPanelNibName { | - (NSString *)optionsPanelNibName { | ||||||
| 	switch(_targets.front()->machine) { | 	switch(_targets.front()->machine) { | ||||||
| 		case Analyser::Machine::AmstradCPC:	return @"CompositeOptions"; | 		case Analyser::Machine::AmstradCPC:	return @"CompositeOptions"; | ||||||
| 		case Analyser::Machine::AppleII:	return @"AppleIIOptions"; | //		case Analyser::Machine::AppleII:	return @"AppleIIOptions"; | ||||||
| 		case Analyser::Machine::Atari2600:	return @"Atari2600Options"; | 		case Analyser::Machine::Atari2600:	return @"Atari2600Options"; | ||||||
| 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::Electron:	return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::MSX:		return @"QuickLoadCompositeOptions"; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -64,11 +64,11 @@ Gw | |||||||
|                         <tabViewItems> |                         <tabViewItems> | ||||||
|                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> |                             <tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa"> | ||||||
|                                 <view key="view" id="dHz-Yv-GNq"> |                                 <view key="view" id="dHz-Yv-GNq"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="93"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> | ||||||
|                                             <rect key="frame" x="15" y="71" width="46" height="17"/> |                                             <rect key="frame" x="15" y="72" width="46" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -76,7 +76,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> | ||||||
|                                             <rect key="frame" x="15" y="40" width="96" height="17"/> |                                             <rect key="frame" x="15" y="41" width="96" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -84,7 +84,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij"> | ||||||
|                                             <rect key="frame" x="65" y="66" width="91" height="26"/> |                                             <rect key="frame" x="65" y="67" width="115" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F"> |                                             <popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -92,12 +92,14 @@ Gw | |||||||
|                                                     <items> |                                                     <items> | ||||||
|                                                         <menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/> |                                                         <menuItem title="Apple II" state="on" id="VBQ-JG-AeM"/> | ||||||
|                                                         <menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/> |                                                         <menuItem title="Apple II+" tag="1" id="Yme-Wn-Obh"/> | ||||||
|  |                                                         <menuItem title="Apple IIe" tag="2" id="AMt-WU-a0H"/> | ||||||
|  |                                                         <menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/> | ||||||
|                                                     </items> |                                                     </items> | ||||||
|                                                 </menu> |                                                 </menu> | ||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi"> | ||||||
|                                             <rect key="frame" x="115" y="35" width="132" height="26"/> |                                             <rect key="frame" x="115" y="36" width="132" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm"> |                                             <popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="proportionallyDown" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -128,11 +130,11 @@ Gw | |||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
|                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> |                             <tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM"> | ||||||
|                                 <view key="view" id="5zS-Nj-Ynx"> |                                 <view key="view" id="5zS-Nj-Ynx"> | ||||||
|                                     <rect key="frame" x="10" y="33" width="554" height="99"/> |                                     <rect key="frame" x="10" y="33" width="554" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh"> | ||||||
|                                             <rect key="frame" x="65" y="72" width="94" height="26"/> |                                             <rect key="frame" x="65" y="67" width="94" height="26"/> | ||||||
|                                             <popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea"> |                                             <popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="proportionallyDown" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea"> | ||||||
|                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|                                                 <font key="font" metaFont="menu"/> |                                                 <font key="font" metaFont="menu"/> | ||||||
| @@ -146,7 +148,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> | ||||||
|                                             <rect key="frame" x="15" y="77" width="46" height="17"/> |                                             <rect key="frame" x="15" y="72" width="46" height="17"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|   | |||||||
| @@ -130,6 +130,8 @@ class MachinePicker: NSObject { | |||||||
| 				var model: CSMachineAppleIIModel = .appleII | 				var model: CSMachineAppleIIModel = .appleII | ||||||
| 				switch appleIIModelButton!.selectedTag() { | 				switch appleIIModelButton!.selectedTag() { | ||||||
| 					case 1:		model = .appleIIPlus | 					case 1:		model = .appleIIPlus | ||||||
|  | 					case 2:		model = .appleIIe | ||||||
|  | 					case 3:		model = .appleEnhancedIIe | ||||||
| 					case 0:		fallthrough | 					case 0:		fallthrough | ||||||
| 					default:	model = .appleII | 					default:	model = .appleII | ||||||
| 				} | 				} | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ class MOS6502InterruptTests: XCTestCase { | |||||||
| 		super.setUp() | 		super.setUp() | ||||||
|  |  | ||||||
| 		// create a machine full of NOPs | 		// create a machine full of NOPs | ||||||
| 		machine = CSTestMachine6502() | 		machine = CSTestMachine6502(is65C02: false) | ||||||
| 		for c in 0...65535 { | 		for c in 0...65535 { | ||||||
| 			machine.setValue(0xea, forAddress: UInt16(c)) | 			machine.setValue(0xea, forAddress: UInt16(c)) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import XCTest | |||||||
| class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { | ||||||
|  |  | ||||||
| 	private var endTime: UInt32 = 0 | 	private var endTime: UInt32 = 0 | ||||||
| 	private let machine = CSTestMachine6502() | 	private let machine = CSTestMachine6502(is65C02: false) | ||||||
|  |  | ||||||
| 	func testImplied() { | 	func testImplied() { | ||||||
| 		let code: [UInt8] = [ | 		let code: [UInt8] = [ | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class AllSuiteATests: XCTestCase { | |||||||
| 	func testAllSuiteA() { | 	func testAllSuiteA() { | ||||||
| 		if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") { | 		if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") { | ||||||
| 			if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | 			if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | ||||||
| 				let machine = CSTestMachine6502() | 				let machine = CSTestMachine6502(is65C02: false) | ||||||
|  |  | ||||||
| 				machine.setData(allSuiteA, atAddress: 0x4000) | 				machine.setData(allSuiteA, atAddress: 0x4000) | ||||||
| 				machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0);  // end | 				machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0);  // end | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler { | |||||||
| 	func testBCD() { | 	func testBCD() { | ||||||
| 		if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) { | 		if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) { | ||||||
| 			if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | 			if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | ||||||
| 				let machine = CSTestMachine6502() | 				let machine = CSTestMachine6502(is65C02: false) | ||||||
| 				machine.trapHandler = self | 				machine.trapHandler = self | ||||||
|  |  | ||||||
| 				machine.setData(bcdTest, atAddress: 0x2900) | 				machine.setData(bcdTest, atAddress: 0x2900) | ||||||
|   | |||||||
| @@ -32,8 +32,8 @@ class VanillaSerialPort: public Commodore::Serial::Port { | |||||||
| 		_serialBus.reset(new ::Commodore::Serial::Bus); | 		_serialBus.reset(new ::Commodore::Serial::Bus); | ||||||
| 		_serialPort.reset(new VanillaSerialPort); | 		_serialPort.reset(new VanillaSerialPort); | ||||||
|  |  | ||||||
| 		_c1540.reset(new Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); | 		auto rom_fetcher = CSROMFetcher(); | ||||||
| 		_c1540->set_rom_fetcher(CSROMFetcher()); | 		_c1540.reset(new Commodore::C1540::Machine(Commodore::C1540::Personality::C1540, rom_fetcher)); | ||||||
| 		_c1540->set_serial_bus(_serialBus); | 		_c1540->set_serial_bus(_serialBus); | ||||||
| 		Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus); | 		Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -23,7 +23,11 @@ extern const uint8_t CSTestMachine6502JamOpcode; | |||||||
|  |  | ||||||
| @interface CSTestMachine6502 : CSTestMachine | @interface CSTestMachine6502 : CSTestMachine | ||||||
|  |  | ||||||
| - (void)setData:(NSData *)data atAddress:(uint16_t)startAddress; | - (nonnull instancetype)init NS_UNAVAILABLE; | ||||||
|  |  | ||||||
|  | - (nonnull instancetype)initIs65C02:(BOOL)is65C02; | ||||||
|  |  | ||||||
|  | - (void)setData:(nonnull NSData *)data atAddress:(uint16_t)startAddress; | ||||||
| - (void)runForNumberOfCycles:(int)cycles; | - (void)runForNumberOfCycles:(int)cycles; | ||||||
|  |  | ||||||
| - (void)setValue:(uint8_t)value forAddress:(uint16_t)address; | - (void)setValue:(uint8_t)value forAddress:(uint16_t)address; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| #import "TestMachine6502.h" | #import "TestMachine6502.h" | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include "6502AllRAM.hpp" | #include "../../../../Processors/6502/AllRAM/6502AllRAM.hpp" | ||||||
| #import "TestMachine+ForSubclassEyesOnly.h" | #import "TestMachine+ForSubclassEyesOnly.h" | ||||||
|  |  | ||||||
| const uint8_t CSTestMachine6502JamOpcode = CPU::MOS6502::JamOpcode; | const uint8_t CSTestMachine6502JamOpcode = CPU::MOS6502::JamOpcode; | ||||||
| @@ -35,11 +35,12 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) | |||||||
|  |  | ||||||
| #pragma mark - Lifecycle | #pragma mark - Lifecycle | ||||||
|  |  | ||||||
| - (instancetype)init { | - (instancetype)initIs65C02:(BOOL)is65C02 { | ||||||
| 	self = [super init]; | 	self = [super init]; | ||||||
|  |  | ||||||
| 	if(self) { | 	if(self) { | ||||||
| 		_processor = CPU::MOS6502::AllRAMProcessor::Processor(); | 		_processor = CPU::MOS6502::AllRAMProcessor::Processor( | ||||||
|  | 			is65C02 ? CPU::MOS6502::Personality::PWDC65C02 : CPU::MOS6502::Personality::P6502); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return self; | 	return self; | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| @@ -11,10 +11,37 @@ import XCTest | |||||||
|  |  | ||||||
| class KlausDormannTests: XCTestCase { | class KlausDormannTests: XCTestCase { | ||||||
|  |  | ||||||
| 	func testKlausDormann() { | 	fileprivate func runTest(resource: String, is65C02: Bool) -> UInt16 { | ||||||
|  | 		if let filename = Bundle(for: type(of: self)).path(forResource: resource, ofType: "bin") { | ||||||
|  | 			if let functionalTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | ||||||
|  | 				let machine = CSTestMachine6502(is65C02: is65C02) | ||||||
|  |  | ||||||
|  | 				machine.setData(functionalTest, atAddress: 0) | ||||||
|  | 				machine.setValue(0x400, for: .programCounter) | ||||||
|  |  | ||||||
|  | 				while true { | ||||||
|  | 					let oldPC = machine.value(for: .lastOperationAddress) | ||||||
|  | 					machine.runForNumber(ofCycles: 1000) | ||||||
|  | 					let newPC = machine.value(for: .lastOperationAddress) | ||||||
|  |  | ||||||
|  | 					if newPC == oldPC { | ||||||
|  | 						machine.runForNumber(ofCycles: 7) | ||||||
|  |  | ||||||
|  | 						let retestPC = machine.value(for: .lastOperationAddress) | ||||||
|  | 						if retestPC == oldPC { | ||||||
|  | 							return newPC | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/// Runs Klaus Dorman's 6502 tests. | ||||||
|  | 	func test6502() { | ||||||
| 		func errorForTrapAddress(_ address: UInt16) -> String? { | 		func errorForTrapAddress(_ address: UInt16) -> String? { | ||||||
| 			let hexAddress = String(format:"%04x", address) |  | ||||||
| 			switch address { | 			switch address { | ||||||
| 				case 0x3399: return nil // success! | 				case 0x3399: return nil // success! | ||||||
|  |  | ||||||
| @@ -28,29 +55,63 @@ class KlausDormannTests: XCTestCase { | |||||||
| 				case 0x26d2: return "ASL zpg,x produced incorrect flags" | 				case 0x26d2: return "ASL zpg,x produced incorrect flags" | ||||||
| 				case 0x36c6: return "Unexpected RESET" | 				case 0x36c6: return "Unexpected RESET" | ||||||
|  |  | ||||||
| 				default: return "Unknown error at \(hexAddress)" | 				case 0: return "Didn't find tests" | ||||||
|  |  | ||||||
|  | 				default: return "Unknown error at \(String(format:"%04x", address))" | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if let filename = Bundle(for: type(of: self)).path(forResource: "6502_functional_test", ofType: "bin") { | 		let destination = runTest(resource: "6502_functional_test", is65C02: false) | ||||||
| 			if let functionalTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | 		let error = errorForTrapAddress(destination) | ||||||
| 				let machine = CSTestMachine6502() |  | ||||||
|  |  | ||||||
| 				machine.setData(functionalTest, atAddress: 0) |  | ||||||
| 				machine.setValue(0x400, for: .programCounter) |  | ||||||
|  |  | ||||||
| 				while true { |  | ||||||
| 					let oldPC = machine.value(for: .lastOperationAddress) |  | ||||||
| 					machine.runForNumber(ofCycles: 1000) |  | ||||||
| 					let newPC = machine.value(for: .lastOperationAddress) |  | ||||||
|  |  | ||||||
| 					if newPC == oldPC { |  | ||||||
| 						let error = errorForTrapAddress(oldPC) |  | ||||||
| 		XCTAssert(error == nil, "Failed with error \(error!)") | 		XCTAssert(error == nil, "Failed with error \(error!)") | ||||||
| 						return | 	} | ||||||
| 					} |  | ||||||
| 				} | 	/// Runs Klaus Dorman's 65C02 tests. | ||||||
|  | 	func test65C02() { | ||||||
|  | 		func errorForTrapAddress(_ address: UInt16) -> String? { | ||||||
|  | 			switch address { | ||||||
|  | 				case 0x24f1: return nil // success! | ||||||
|  |  | ||||||
|  | 				case 0x0423: return "PHX: value of X not on stack page" | ||||||
|  | 				case 0x0428: return "PHX: stack pointer not decremented" | ||||||
|  | 				case 0x042d: return "PLY: didn't acquire value 0xaa from stack" | ||||||
|  | 				case 0x0432: return "PLY: didn't acquire value 0x55 from stack" | ||||||
|  | 				case 0x0437: return "PLY: stack pointer not incremented" | ||||||
|  | 				case 0x043c: return "PLY: stack pointer not incremented" | ||||||
|  |  | ||||||
|  | 				case 0x066a: return "BRA: branch not taken" | ||||||
|  | 				case 0x0730: return "BBS: branch not taken" | ||||||
|  | 				case 0x0733: return "BBR: branch taken" | ||||||
|  |  | ||||||
|  | 				case 0x2884: return "JMP (abs) exhibited 6502 page-crossing bug" | ||||||
|  | 				case 0x16ca: return "JMP (abs, x) failed" | ||||||
|  |  | ||||||
|  | 				case 0x2785: return "BRK didn't clear the decimal mode flag" | ||||||
|  |  | ||||||
|  | 				case 0x177b: return "INC A didn't function" | ||||||
|  |  | ||||||
|  | 				case 0x1834: return "LDA (zp) acted as JAM" | ||||||
|  | 				case 0x183a: return "STA (zp) acted as JAM" | ||||||
|  | 				case 0x1849: return "LDA/STA (zp) left flags in incorrect state" | ||||||
|  |  | ||||||
|  | 				case 0x1983: return "STZ didn't store zero" | ||||||
|  |  | ||||||
|  | 				case 0x1b03: return "BIT didn't set flags correctly" | ||||||
|  | 				case 0x1c6c: return "BIT immediate didn't set flags correctly" | ||||||
|  |  | ||||||
|  | 				case 0x1d88: return "TRB set Z flag incorrectly" | ||||||
|  | 				case 0x1e7c: return "RMB set flags incorrectly" | ||||||
|  |  | ||||||
|  | 				case 0x2245: return "CMP (zero) didn't work" | ||||||
|  | 				case 0x2506: return "Decimal ADC set flags incorrectly" | ||||||
|  |  | ||||||
|  | 				case 0: return "Didn't find tests" | ||||||
|  | 				default: return "Unknown error at \(String(format:"%04x", address))" | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		let destination = runTest(resource: "65C02_extended_opcodes_test", is65C02: true) | ||||||
|  | 		let error = errorForTrapAddress(destination) | ||||||
|  | 		XCTAssert(error == nil, "Failed with error \(error!)") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler { | |||||||
| 		if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: nil) { | 		if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: nil) { | ||||||
| 			if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | 			if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) { | ||||||
|  |  | ||||||
| 				machine = CSTestMachine6502() | 				machine = CSTestMachine6502(is65C02: false) | ||||||
| 				machine.trapHandler = self | 				machine.trapHandler = self | ||||||
| //				machine.logActivity = true | //				machine.logActivity = true | ||||||
| 				output = "" | 				output = "" | ||||||
|   | |||||||
| @@ -6,21 +6,28 @@ | |||||||
| //  Copyright 2017 Thomas Harte. All rights reserved. | //  Copyright 2017 Thomas Harte. All rights reserved. | ||||||
| // | // | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include <array> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
| #include <cstring> | #include <cstring> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <unistd.h> | ||||||
|  |  | ||||||
| #include <SDL2/SDL.h> | #include <SDL2/SDL.h> | ||||||
|  |  | ||||||
| #include "../../Analyser/Static/StaticAnalyser.hpp" | #include "../../Analyser/Static/StaticAnalyser.hpp" | ||||||
| #include "../../Machines/Utility/MachineForTarget.hpp" | #include "../../Machines/Utility/MachineForTarget.hpp" | ||||||
|  |  | ||||||
| #include "../../Machines/ConfigurationTarget.hpp" | #include "../../Machines/MediaTarget.hpp" | ||||||
| #include "../../Machines/CRTMachine.hpp" | #include "../../Machines/CRTMachine.hpp" | ||||||
|  |  | ||||||
| #include "../../Concurrency/BestEffortUpdater.hpp" | #include "../../Concurrency/BestEffortUpdater.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Activity/Observer.hpp" | ||||||
|  | #include "../../Outputs/CRT/Internals/Rectangle.hpp" | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { | struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { | ||||||
| @@ -31,8 +38,8 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat | |||||||
| 	Machine::DynamicMachine *machine; | 	Machine::DynamicMachine *machine; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // This is set to a relatively large number for now. |  | ||||||
| struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { | struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { | ||||||
|  | 	// This is set to a relatively large number for now. | ||||||
| 	static const int buffer_size = 1024; | 	static const int buffer_size = 1024; | ||||||
|  |  | ||||||
| 	void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override { | 	void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override { | ||||||
| @@ -69,6 +76,89 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { | |||||||
| 	std::vector<int16_t> audio_buffer_; | 	std::vector<int16_t> audio_buffer_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class ActivityObserver: public Activity::Observer { | ||||||
|  | 	public: | ||||||
|  | 		ActivityObserver(Activity::Source *source, float aspect_ratio) { | ||||||
|  | 			// Get the suorce to supply all LEDs and drives. | ||||||
|  | 			source->set_activity_observer(this); | ||||||
|  |  | ||||||
|  | 			// The objective is to display drives on one side of the screen, other LEDs on the other. Drives | ||||||
|  | 			// may or may not have LEDs and this code intends to display only those which do; so a quick | ||||||
|  | 			// comparative processing of the two lists is called for. | ||||||
|  |  | ||||||
|  | 			// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed. | ||||||
|  | 			drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) { | ||||||
|  | 				return std::find(leds_.begin(), leds_.end(), string) == leds_.end(); | ||||||
|  | 			}) - drives_.begin()); | ||||||
|  |  | ||||||
|  | 			// Remove from the list of LEDs any which are drives. Those will be represented separately. | ||||||
|  | 			leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) { | ||||||
|  | 				return std::find(drives_.begin(), drives_.end(), string) != drives_.end(); | ||||||
|  | 			}) - leds_.begin()); | ||||||
|  |  | ||||||
|  | 			set_aspect_ratio(aspect_ratio); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_aspect_ratio(float aspect_ratio) { | ||||||
|  | 			lights_.clear(); | ||||||
|  |  | ||||||
|  | 			// Generate a bunch of LEDs for connected drives. | ||||||
|  | 			const float height = 0.05f; | ||||||
|  | 			const float width = height / aspect_ratio; | ||||||
|  | 			const float right_x = 1.0f - 2.0f * width; | ||||||
|  | 			float y = 1.0f - 2.0f * height; | ||||||
|  | 			for(const auto &drive: drives_) { | ||||||
|  | 				// TODO: use std::make_unique as below, if/when formally embracing C++14. | ||||||
|  | 				lights_.emplace(std::make_pair(drive, std::unique_ptr<OpenGL::Rectangle>(new OpenGL::Rectangle(right_x, y, width, height)))); | ||||||
|  | 				y -= height * 2.0f; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/* | ||||||
|  | 				This would generate LEDs for things other than drives; I'm declining for now | ||||||
|  | 				due to the inexpressiveness of just painting a rectangle. | ||||||
|  |  | ||||||
|  | 				const float left_x = -1.0f + 2.0f * width; | ||||||
|  | 				y = 1.0f - 2.0f * height; | ||||||
|  | 				for(const auto &led: leds_) { | ||||||
|  | 					lights_.emplace(std::make_pair(led, std::make_unique<OpenGL::Rectangle>(left_x, y, width, height))); | ||||||
|  | 					y -= height * 2.0f; | ||||||
|  | 				} | ||||||
|  | 			*/ | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void draw() { | ||||||
|  | 			for(const auto &lit_led: lit_leds_) { | ||||||
|  | 				if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end()) | ||||||
|  | 					lights_[lit_led]->draw(0.0, 0.8, 0.0); | ||||||
|  | 			} | ||||||
|  | 			blinking_leds_.clear(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::vector<std::string> leds_; | ||||||
|  | 		void register_led(const std::string &name) override { | ||||||
|  | 			leds_.push_back(name); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::vector<std::string> drives_; | ||||||
|  | 		void register_drive(const std::string &name) override { | ||||||
|  | 			drives_.push_back(name); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_led_status(const std::string &name, bool lit) override { | ||||||
|  | 			if(lit) lit_leds_.insert(name); | ||||||
|  | 			else lit_leds_.erase(name); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void announce_drive_event(const std::string &name, DriveEvent event) override { | ||||||
|  | 			blinking_leds_.insert(name); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::map<std::string, std::unique_ptr<OpenGL::Rectangle>> lights_; | ||||||
|  | 		std::set<std::string> lit_leds_; | ||||||
|  | 		std::set<std::string> blinking_leds_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) { | bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) { | ||||||
| #define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break; | #define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break; | ||||||
| 	switch(scancode) { | 	switch(scancode) { | ||||||
| @@ -193,6 +283,22 @@ std::string final_path_component(const std::string &path) { | |||||||
| 	return path.substr(final_slash+1, path.size() - final_slash - 1); | 	return path.substr(final_slash+1, path.size() - final_slash - 1); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Executes @c command and returns its STDOUT. | ||||||
|  | */ | ||||||
|  | std::string system_get(const char *command) { | ||||||
|  |     std::unique_ptr<FILE, decltype((pclose))> pipe(popen(command, "r"), pclose); | ||||||
|  |     if(!pipe) return ""; | ||||||
|  |  | ||||||
|  | 	std::string result; | ||||||
|  |     while(!feof(pipe.get())) { | ||||||
|  | 		std::array<char, 256> buffer; | ||||||
|  |         if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) | ||||||
|  |             result += buffer.data(); | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { | int main(int argc, char *argv[]) { | ||||||
| @@ -462,6 +568,16 @@ int main(int argc, char *argv[]) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		If the machine offers anything for activity observation, | ||||||
|  | 		create and register an activity observer. | ||||||
|  | 	*/ | ||||||
|  | 	std::unique_ptr<ActivityObserver> activity_observer; | ||||||
|  | 	Activity::Source *const activity_source = machine->activity_source(); | ||||||
|  | 	if(activity_source) { | ||||||
|  | 		activity_observer.reset(new ActivityObserver(activity_source, 4.0f / 3.0f)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Run the main event loop until the OS tells us to quit. | 	// Run the main event loop until the OS tells us to quit. | ||||||
| 	bool should_quit = false; | 	bool should_quit = false; | ||||||
| 	Uint32 fullscreen_mode = 0; | 	Uint32 fullscreen_mode = 0; | ||||||
| @@ -479,6 +595,7 @@ int main(int argc, char *argv[]) { | |||||||
| 							glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer); | 							glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer); | ||||||
| 							machine->crt_machine()->get_crt()->set_target_framebuffer(target_framebuffer); | 							machine->crt_machine()->get_crt()->set_target_framebuffer(target_framebuffer); | ||||||
| 							SDL_GetWindowSize(window, &window_width, &window_height); | 							SDL_GetWindowSize(window, &window_width, &window_height); | ||||||
|  | 							if(activity_observer) activity_observer->set_aspect_ratio(static_cast<float>(window_width) / static_cast<float>(window_height)); | ||||||
| 						} break; | 						} break; | ||||||
|  |  | ||||||
| 						default: break; | 						default: break; | ||||||
| @@ -487,7 +604,7 @@ int main(int argc, char *argv[]) { | |||||||
|  |  | ||||||
| 				case SDL_DROPFILE: { | 				case SDL_DROPFILE: { | ||||||
| 					Analyser::Static::Media media = Analyser::Static::GetMedia(event.drop.file); | 					Analyser::Static::Media media = Analyser::Static::GetMedia(event.drop.file); | ||||||
| 					machine->configuration_target()->insert_media(media); | 					machine->media_target()->insert_media(media); | ||||||
| 				} break; | 				} break; | ||||||
|  |  | ||||||
| 				case SDL_KEYDOWN: | 				case SDL_KEYDOWN: | ||||||
| @@ -500,6 +617,63 @@ int main(int argc, char *argv[]) { | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					// Capture ctrl+shift+d as a take-a-screenshot command. | ||||||
|  | 					if(event.key.keysym.sym == SDLK_d && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) { | ||||||
|  | 						// Pick a width to capture that will preserve a 4:3 output aspect ratio. | ||||||
|  | 						const int proportional_width = (window_height * 4) / 3; | ||||||
|  |  | ||||||
|  | 						// Grab the screen buffer. | ||||||
|  | 						std::vector<uint8_t> pixels(proportional_width * window_height * 4); | ||||||
|  | 						glReadPixels((window_width - proportional_width) >> 1, 0, proportional_width, window_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); | ||||||
|  |  | ||||||
|  | 						// Flip the buffer vertically, because SDL and OpenGL do not agree about | ||||||
|  | 						// the basis axes. | ||||||
|  | 						std::vector<uint8_t> swap_buffer(proportional_width*4); | ||||||
|  | 						for(int y = 0; y < window_height >> 1; ++y) { | ||||||
|  | 							memcpy(swap_buffer.data(), &pixels[y*proportional_width*4], swap_buffer.size()); | ||||||
|  | 							memcpy(&pixels[y*proportional_width*4], &pixels[(window_height - 1 - y)*proportional_width*4], swap_buffer.size()); | ||||||
|  | 							memcpy(&pixels[(window_height - 1 - y)*proportional_width*4], swap_buffer.data(), swap_buffer.size()); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Pick the directory for images. Try `xdg-user-dir PICTURES` first. | ||||||
|  | 						std::string target_directory = system_get("xdg-user-dir PICTURES"); | ||||||
|  |  | ||||||
|  | 						// Make sure there are no newlines. | ||||||
|  | 						target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\n'), target_directory.end()); | ||||||
|  | 						target_directory.erase(std::remove(target_directory.begin(), target_directory.end(), '\r'), target_directory.end()); | ||||||
|  |  | ||||||
|  | 						// Fall back on the HOME directory if necessary. | ||||||
|  | 						if(target_directory.empty()) target_directory = getenv("HOME"); | ||||||
|  |  | ||||||
|  | 						// Find the first available name of the form ~/clk-screenshot-<number>.bmp. | ||||||
|  | 						size_t index = 0; | ||||||
|  | 						std::string target; | ||||||
|  | 						while(true) { | ||||||
|  | 							target = target_directory + "/clk-screenshot-" + std::to_string(index) + ".bmp"; | ||||||
|  |  | ||||||
|  | 							struct stat file_stats; | ||||||
|  | 							if(stat(target.c_str(), &file_stats)) | ||||||
|  | 								break; | ||||||
|  |  | ||||||
|  | 							++index; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						// Create a suitable SDL surface and save the thing. | ||||||
|  | 						const bool is_big_endian = SDL_BYTEORDER == SDL_BIG_ENDIAN; | ||||||
|  | 						SDL_Surface *const surface = SDL_CreateRGBSurfaceFrom( | ||||||
|  | 							pixels.data(), | ||||||
|  | 							proportional_width, window_height, | ||||||
|  | 							8*4, | ||||||
|  | 							proportional_width*4, | ||||||
|  | 							is_big_endian ? 0xff000000 : 0x000000ff, | ||||||
|  | 							is_big_endian ? 0x00ff0000 : 0x0000ff00, | ||||||
|  | 							is_big_endian ? 0x0000ff00 : 0x00ff0000, | ||||||
|  | 							0); | ||||||
|  | 						SDL_SaveBMP(surface, target.c_str()); | ||||||
|  | 						SDL_FreeSurface(surface); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
| 				// deliberate fallthrough... | 				// deliberate fallthrough... | ||||||
| 				case SDL_KEYUP: { | 				case SDL_KEYUP: { | ||||||
|  |  | ||||||
| @@ -610,6 +784,7 @@ int main(int argc, char *argv[]) { | |||||||
| 		// Display a new frame and wait for vsync. | 		// Display a new frame and wait for vsync. | ||||||
| 		updater.update(); | 		updater.update(); | ||||||
| 		machine->crt_machine()->get_crt()->draw_frame(static_cast<unsigned int>(window_width), static_cast<unsigned int>(window_height), false); | 		machine->crt_machine()->get_crt()->draw_frame(static_cast<unsigned int>(window_width), static_cast<unsigned int>(window_height), false); | ||||||
|  | 		if(activity_observer) activity_observer->draw(); | ||||||
| 		SDL_GL_SwapWindow(window); | 		SDL_GL_SwapWindow(window); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -227,11 +227,18 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | |||||||
| 			output_shader_program_->set_output_size(output_width, output_height, visible_area_); | 			output_shader_program_->set_output_size(output_width, output_height, visible_area_); | ||||||
| 			last_output_width_ = output_width; | 			last_output_width_ = output_width; | ||||||
| 			last_output_height_ = output_height; | 			last_output_height_ = output_height; | ||||||
|  |  | ||||||
|  | 			// Configure a right gutter to crop the right-hand 2% of the display. | ||||||
|  | 			right_overlay_.reset(new OpenGL::Rectangle(output_shader_program_->get_right_extent() * 0.98f, -1.0f, 1.0f, 2.0f)); | ||||||
| 		} | 		} | ||||||
| 		output_shader_program_->bind(); | 		output_shader_program_->bind(); | ||||||
|  |  | ||||||
| 		// draw | 		// draw | ||||||
| 		glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize); | 		glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize); | ||||||
|  |  | ||||||
|  | 		// mask off the gutter | ||||||
|  | 		glDisable(GL_BLEND); | ||||||
|  | 		right_overlay_->draw(0.0, 0.0, 0.0); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| #ifdef GL_NV_texture_barrier | #ifdef GL_NV_texture_barrier | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ | |||||||
|  |  | ||||||
| #include "Shaders/OutputShader.hpp" | #include "Shaders/OutputShader.hpp" | ||||||
| #include "Shaders/IntermediateShader.hpp" | #include "Shaders/IntermediateShader.hpp" | ||||||
|  | #include "Rectangle.hpp" | ||||||
|  |  | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -106,6 +107,18 @@ class OpenGLOutputBuilder { | |||||||
|  |  | ||||||
| 		float integer_coordinate_multiplier_ = 1.0f; | 		float integer_coordinate_multiplier_ = 1.0f; | ||||||
|  |  | ||||||
|  | 		// Maintain a couple of rectangles for masking off the extreme edge of the display; | ||||||
|  | 		// this is a bit of a cheat: there's some tolerance in when a sync pulse will be | ||||||
|  | 		// generated. So it might be slightly later than expected. Which might cause a scan | ||||||
|  | 		// that is slightly longer than expected. Which means that from then on, those scans | ||||||
|  | 		// might have touched parts of the extreme edge of the display which are not rescanned. | ||||||
|  | 		// Which because I've implemented persistence-of-vision as an in-buffer effect will | ||||||
|  | 		// cause perpetual persistence. | ||||||
|  | 		// | ||||||
|  | 		// The fix: just always treat that area as invisible. This is acceptable thanks to | ||||||
|  | 		// the concept of overscan. One is allowed not to display extreme ends of the image. | ||||||
|  | 		std::unique_ptr<OpenGL::Rectangle> right_overlay_; | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		// These two are protected by output_mutex_. | 		// These two are protected by output_mutex_. | ||||||
| 		TextureBuilder texture_builder; | 		TextureBuilder texture_builder; | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								Outputs/CRT/Internals/Rectangle.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Outputs/CRT/Internals/Rectangle.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | // | ||||||
|  | //  Rectangle.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Rectangle.hpp" | ||||||
|  |  | ||||||
|  | using namespace OpenGL; | ||||||
|  |  | ||||||
|  | Rectangle::Rectangle(float x, float y, float width, float height): | ||||||
|  |  	pixel_shader_( | ||||||
|  | 		"#version 150\n" | ||||||
|  |  | ||||||
|  | 		"in vec2 position;" | ||||||
|  |  | ||||||
|  | 		"void main(void)" | ||||||
|  | 		"{" | ||||||
|  | 			"gl_Position = vec4(position, 0.0, 1.0);" | ||||||
|  | 		"}", | ||||||
|  |  | ||||||
|  | 		"#version 150\n" | ||||||
|  |  | ||||||
|  | 		"uniform vec4 colour;" | ||||||
|  | 		"out vec4 fragColour;" | ||||||
|  |  | ||||||
|  | 		"void main(void)" | ||||||
|  | 		"{" | ||||||
|  | 			"fragColour = colour;" | ||||||
|  | 		"}" | ||||||
|  | 	){ | ||||||
|  | 	pixel_shader_.bind(); | ||||||
|  |  | ||||||
|  | 	glGenVertexArrays(1, &drawing_vertex_array_); | ||||||
|  | 	glGenBuffers(1, &drawing_array_buffer_); | ||||||
|  |  | ||||||
|  | 	glBindVertexArray(drawing_vertex_array_); | ||||||
|  | 	glBindBuffer(GL_ARRAY_BUFFER, drawing_array_buffer_); | ||||||
|  |  | ||||||
|  | 	GLint position_attribute = pixel_shader_.get_attrib_location("position"); | ||||||
|  | 	glEnableVertexAttribArray(static_cast<GLuint>(position_attribute)); | ||||||
|  |  | ||||||
|  | 	glVertexAttribPointer( | ||||||
|  | 		(GLuint)position_attribute, | ||||||
|  | 		2, | ||||||
|  | 		GL_FLOAT, | ||||||
|  | 		GL_FALSE, | ||||||
|  | 		2 * sizeof(GLfloat), | ||||||
|  | 		(void *)0); | ||||||
|  |  | ||||||
|  | 	colour_uniform_ = pixel_shader_.get_uniform_location("colour"); | ||||||
|  |  | ||||||
|  | 	float buffer[4*2]; | ||||||
|  |  | ||||||
|  | 	// Store positions. | ||||||
|  | 	buffer[0] = x;			buffer[1] = y; | ||||||
|  | 	buffer[2] = x;			buffer[3] = y + height; | ||||||
|  | 	buffer[4] = x + width;	buffer[5] = y; | ||||||
|  | 	buffer[6] = x + width;	buffer[7] = y + height; | ||||||
|  |  | ||||||
|  | 	// Upload buffer. | ||||||
|  | 	glBindBuffer(GL_ARRAY_BUFFER, drawing_array_buffer_); | ||||||
|  | 	glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Rectangle::draw(float red, float green, float blue) { | ||||||
|  | 	pixel_shader_.bind(); | ||||||
|  | 	glUniform4f(colour_uniform_, red, green, blue, 1.0); | ||||||
|  |  | ||||||
|  | 	glBindVertexArray(drawing_vertex_array_); | ||||||
|  | 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								Outputs/CRT/Internals/Rectangle.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Outputs/CRT/Internals/Rectangle.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // | ||||||
|  | //  Rectangle.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 11/07/2018. | ||||||
|  | //  Copyright © 2018 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef Rectangle_hpp | ||||||
|  | #define Rectangle_hpp | ||||||
|  |  | ||||||
|  | #include "OpenGL.hpp" | ||||||
|  | #include "Shaders/Shader.hpp" | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | namespace OpenGL { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a wrapper for drawing a solid, single-colour rectangle. | ||||||
|  | */ | ||||||
|  | class Rectangle { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Instantiates an instance of Rectange with the coordinates given. | ||||||
|  | 		*/ | ||||||
|  | 		Rectangle(float x, float y, float width, float height); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Draws this rectangle in the colour supplied. | ||||||
|  | 		*/ | ||||||
|  | 		void draw(float red, float green, float blue); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Shader pixel_shader_; | ||||||
|  | 		GLuint drawing_vertex_array_ = 0, drawing_array_buffer_ = 0; | ||||||
|  | 		GLint colour_uniform_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* Rectangle_hpp */ | ||||||
| @@ -94,15 +94,21 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | |||||||
|  |  | ||||||
| void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) { | void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area) { | ||||||
| 	GLfloat outputAspectRatioMultiplier = (static_cast<float>(output_width) / static_cast<float>(output_height)) / (4.0f / 3.0f); | 	GLfloat outputAspectRatioMultiplier = (static_cast<float>(output_width) / static_cast<float>(output_height)) / (4.0f / 3.0f); | ||||||
|  |  | ||||||
| 	GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width; | 	GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width; | ||||||
| 	visible_area.origin.x -= bonusWidth * 0.5f * visible_area.size.width; |  | ||||||
|  | 	right_extent_ = (1.0f / outputAspectRatioMultiplier) / visible_area.size.width; | ||||||
|  |  | ||||||
|  | 	visible_area.origin.x -= bonusWidth * 0.5f; | ||||||
| 	visible_area.size.width *= outputAspectRatioMultiplier; | 	visible_area.size.width *= outputAspectRatioMultiplier; | ||||||
|  |  | ||||||
| 	set_uniform("boundsOrigin", (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y); | 	set_uniform("boundsOrigin", (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y); | ||||||
| 	set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height); | 	set_uniform("boundsSize", (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | float OutputShader::get_right_extent() { | ||||||
|  | 	return right_extent_; | ||||||
|  | } | ||||||
|  |  | ||||||
| void OutputShader::set_source_texture_unit(GLenum unit) { | void OutputShader::set_source_texture_unit(GLenum unit) { | ||||||
| 	set_uniform("texID", (GLint)(unit - GL_TEXTURE0)); | 	set_uniform("texID", (GLint)(unit - GL_TEXTURE0)); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -87,6 +87,14 @@ public: | |||||||
| 		space, 0.5 means use half, etc. | 		space, 0.5 means use half, etc. | ||||||
| 	*/ | 	*/ | ||||||
| 	void set_input_width_scaler(float input_scaler); | 	void set_input_width_scaler(float input_scaler); | ||||||
|  |  | ||||||
|  | 	/*! | ||||||
|  | 		@returns The location, in eye coordinates, of the right edge of the output area. | ||||||
|  | 	*/ | ||||||
|  | 	float get_right_extent(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  | 	float right_extent_ = 0.0f; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ class TextureTarget { | |||||||
| 		/*! | 		/*! | ||||||
| 			Draws this texture to the currently-bound framebuffer, which has the aspect ratio | 			Draws this texture to the currently-bound framebuffer, which has the aspect ratio | ||||||
| 			@c aspect_ratio. This texture will fill the height of the frame buffer, and pick | 			@c aspect_ratio. This texture will fill the height of the frame buffer, and pick | ||||||
| 			an appropriate width based o the aspect ratio. | 			an appropriate width based on the aspect ratio. | ||||||
|  |  | ||||||
| 			@c colour_threshold sets a threshold test that each colour must satisfy to be | 			@c colour_threshold sets a threshold test that each colour must satisfy to be | ||||||
| 			output. A threshold of 0.0f means that all colours will pass through. A threshold | 			output. A threshold of 0.0f means that all colours will pass through. A threshold | ||||||
|   | |||||||
| @@ -33,6 +33,22 @@ enum Register { | |||||||
| 	S | 	S | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	The list of 6502 variants supported by this implementation. | ||||||
|  | */ | ||||||
|  | enum Personality { | ||||||
|  | 	P6502,				// the original [NMOS] 6502, replete with various undocumented instructions | ||||||
|  | 	PNES6502,			// the NES's 6502, which is like a 6502 but lacks decimal mode (though it retains the decimal flag) | ||||||
|  | 	PSynertek65C02,		// a 6502 extended with BRA, P[H/L][X/Y], STZ, TRB, TSB and the (zp) addressing mode and a few other additions | ||||||
|  | 	PWDC65C02,			// like the Synertek, but with BBR, BBS, RMB and SMB | ||||||
|  | 	PRockwell65C02,		// like the WDC, but with STP and WAI | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #define is_65c02(p) 		((p) >= Personality::PSynertek65C02) | ||||||
|  | #define has_bbrbbsrmbsmb(p) ((p) >= Personality::PWDC65C02) | ||||||
|  | #define has_stpwai(p)		((p) >= Personality::PRockwell65C02) | ||||||
|  | #define has_decimal_mode(p) ((p) != Personality::PNES6502) | ||||||
|  |  | ||||||
| /* | /* | ||||||
| 	Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for | 	Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for | ||||||
| 	the corresponding set. | 	the corresponding set. | ||||||
| @@ -110,6 +126,8 @@ class BusHandler { | |||||||
| */ | */ | ||||||
| class ProcessorBase: public ProcessorStorage { | class ProcessorBase: public ProcessorStorage { | ||||||
| 	public: | 	public: | ||||||
|  | 		ProcessorBase(Personality personality) : ProcessorStorage(personality) {} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Gets the value of a register. | 			Gets the value of a register. | ||||||
|  |  | ||||||
| @@ -188,12 +206,12 @@ class ProcessorBase: public ProcessorStorage { | |||||||
| 	can also nominate whether the processor includes support for the ready line. Declining to support the ready line | 	can also nominate whether the processor includes support for the ready line. Declining to support the ready line | ||||||
| 	can produce a minor runtime performance improvement. | 	can produce a minor runtime performance improvement. | ||||||
| */ | */ | ||||||
| template <typename T, bool uses_ready_line> class Processor: public ProcessorBase { | template <Personality personality, typename T, bool uses_ready_line> class Processor: public ProcessorBase { | ||||||
| 	public: | 	public: | ||||||
| 		/*! | 		/*! | ||||||
| 			Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. | 			Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. | ||||||
| 		*/ | 		*/ | ||||||
| 		Processor(T &bus_handler) : bus_handler_(bus_handler) {} | 		Processor(T &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Runs the 6502 for a supplied number of cycles. | 			Runs the 6502 for a supplied number of cycles. | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ using namespace CPU::MOS6502; | |||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { | template <Personality personality> class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteAllRAMProcessor() : | 		ConcreteAllRAMProcessor() : | ||||||
| 			mos6502_(*this) { | 			mos6502_(*this) { | ||||||
| @@ -63,11 +63,20 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		CPU::MOS6502::Processor<ConcreteAllRAMProcessor, false> mos6502_; | 		CPU::MOS6502::Processor<personality, ConcreteAllRAMProcessor, false> mos6502_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| AllRAMProcessor *AllRAMProcessor::Processor() { | AllRAMProcessor *AllRAMProcessor::Processor(Personality personality) { | ||||||
| 	return new ConcreteAllRAMProcessor; | #define Bind(p) case p: return new ConcreteAllRAMProcessor<p>(); | ||||||
|  | 	switch(personality) { | ||||||
|  | 		default: | ||||||
|  | 		Bind(Personality::P6502) | ||||||
|  | 		Bind(Personality::PNES6502) | ||||||
|  | 		Bind(Personality::PSynertek65C02) | ||||||
|  | 		Bind(Personality::PWDC65C02) | ||||||
|  | 		Bind(Personality::PRockwell65C02) | ||||||
|  | 	} | ||||||
|  | #undef Bind | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ class AllRAMProcessor: | |||||||
| 	public ::CPU::AllRAMProcessor { | 	public ::CPU::AllRAMProcessor { | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		static AllRAMProcessor *Processor(); | 		static AllRAMProcessor *Processor(Personality personality); | ||||||
| 		virtual ~AllRAMProcessor() {} | 		virtual ~AllRAMProcessor() {} | ||||||
|  |  | ||||||
| 		virtual void run_for(const Cycles cycles) = 0; | 		virtual void run_for(const Cycles cycles) = 0; | ||||||
|   | |||||||
| @@ -12,8 +12,8 @@ | |||||||
| 	6502.hpp, but it's implementation stuff. | 	6502.hpp, but it's implementation stuff. | ||||||
| */ | */ | ||||||
|  |  | ||||||
| template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::run_for(const Cycles cycles) { | template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::run_for(const Cycles cycles) { | ||||||
| 	static const MicroOp doBranch[] = { | 	static const MicroOp do_branch[] = { | ||||||
| 		CycleReadFromPC, | 		CycleReadFromPC, | ||||||
| 		CycleAddSignedOperandToPC, | 		CycleAddSignedOperandToPC, | ||||||
| 		OperationMoveToNextProgram | 		OperationMoveToNextProgram | ||||||
| @@ -63,11 +63,33 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
|  |  | ||||||
| 	while(number_of_cycles > Cycles(0)) { | 	while(number_of_cycles > Cycles(0)) { | ||||||
|  |  | ||||||
|  | 		// Deal with a potential RDY state, if this 6502 has anything connected to ready. | ||||||
| 		while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { | 		while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { | ||||||
| 			number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); | 			number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if(!uses_ready_line || !ready_is_active_) { | 		// Deal with a potential STP state, if this 6502 implements STP. | ||||||
|  | 		while(has_stpwai(personality) && stop_is_active_ && number_of_cycles > Cycles(0)) { | ||||||
|  | 			number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); | ||||||
|  | 			if(interrupt_requests_ & InterruptRequestFlags::Reset) { | ||||||
|  | 				stop_is_active_ = false; | ||||||
|  | 				checkSchedule(); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Deal with a potential WAI state, if this 6502 implements WAI. | ||||||
|  | 		while(has_stpwai(personality) && wait_is_active_ && number_of_cycles > Cycles(0)) { | ||||||
|  | 			number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); | ||||||
|  | 			interrupt_requests_ |= (irq_line_ & inverse_interrupt_flag_); | ||||||
|  | 			if(interrupt_requests_ & InterruptRequestFlags::NMI || irq_line_) { | ||||||
|  | 				wait_is_active_ = false; | ||||||
|  | 				checkSchedule(); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if((!uses_ready_line || !ready_is_active_) && (!has_stpwai(personality) || (!wait_is_active_ && !stop_is_active_))) { | ||||||
| 			if(nextBusOperation != BusOperation::None) { | 			if(nextBusOperation != BusOperation::None) { | ||||||
| 				bus_access(); | 				bus_access(); | ||||||
| 			} | 			} | ||||||
| @@ -93,11 +115,25 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					} break; | 					} break; | ||||||
|  |  | ||||||
| 					case CycleFetchOperand: | 					case CycleFetchOperand: | ||||||
|  | 						// This is supposed to produce the 65C02's 1-cycle NOPs; they're | ||||||
|  | 						// treated as a special case because they break the rule that | ||||||
|  | 						// governs everything else on the 6502: that two bytes will always | ||||||
|  | 						// be fetched. | ||||||
|  | 						if( | ||||||
|  | 							!is_65c02(personality) || | ||||||
|  | 							(operation_&7) != 3 || | ||||||
|  | 							operation_ == 0xcb || | ||||||
|  | 							operation_ == 0xdb | ||||||
|  | 						) { | ||||||
| 							read_mem(operand_, pc_.full); | 							read_mem(operand_, pc_.full); | ||||||
| 							break; | 							break; | ||||||
|  | 						} else { | ||||||
|  | 							continue; | ||||||
|  | 						} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 					case OperationDecodeOperation: | 					case OperationDecodeOperation: | ||||||
| 						scheduled_program_counter_ = operations[operation_]; | 						scheduled_program_counter_ = operations_[operation_]; | ||||||
| 					continue; | 					continue; | ||||||
|  |  | ||||||
| 					case OperationMoveToNextProgram: | 					case OperationMoveToNextProgram: | ||||||
| @@ -115,6 +151,8 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					case CyclePushPCL:					push(pc_.bytes.low);											break; | 					case CyclePushPCL:					push(pc_.bytes.low);											break; | ||||||
| 					case CyclePushOperand:				push(operand_);													break; | 					case CyclePushOperand:				push(operand_);													break; | ||||||
| 					case CyclePushA:					push(a_);														break; | 					case CyclePushA:					push(a_);														break; | ||||||
|  | 					case CyclePushX:					push(x_);														break; | ||||||
|  | 					case CyclePushY:					push(y_);														break; | ||||||
| 					case CycleNoWritePush: { | 					case CycleNoWritePush: { | ||||||
| 						uint16_t targetAddress = s_ | 0x100; s_--; | 						uint16_t targetAddress = s_ | 0x100; s_--; | ||||||
| 						read_mem(operand_, targetAddress); | 						read_mem(operand_, targetAddress); | ||||||
| @@ -127,28 +165,44 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					case CycleReadFromPC:				throwaway_read(pc_.full);										break; | 					case CycleReadFromPC:				throwaway_read(pc_.full);										break; | ||||||
|  |  | ||||||
| 					case OperationBRKPickVector: | 					case OperationBRKPickVector: | ||||||
| 						// NMI can usurp BRK-vector operations | 						if(is_65c02(personality)) { | ||||||
|  | 							nextAddress.full = 0xfffe; | ||||||
|  | 						} else { | ||||||
|  | 							// NMI can usurp BRK-vector operations on the pre-C 6502s. | ||||||
| 							nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; | 							nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; | ||||||
| 						interrupt_requests_ &= ~InterruptRequestFlags::NMI;	// TODO: this probably doesn't happen now? | 							interrupt_requests_ &= ~InterruptRequestFlags::NMI; | ||||||
|  | 						} | ||||||
| 					continue; | 					continue; | ||||||
| 					case OperationNMIPickVector:		nextAddress.full = 0xfffa;											continue; | 					case OperationNMIPickVector:		nextAddress.full = 0xfffa;											continue; | ||||||
| 					case OperationRSTPickVector:		nextAddress.full = 0xfffc;											continue; | 					case OperationRSTPickVector:		nextAddress.full = 0xfffc;											continue; | ||||||
| 					case CycleReadVectorLow:			read_mem(pc_.bytes.low, nextAddress.full);							break; | 					case CycleReadVectorLow:			read_mem(pc_.bytes.low, nextAddress.full);							break; | ||||||
| 					case CycleReadVectorHigh:			read_mem(pc_.bytes.high, nextAddress.full+1);						break; | 					case CycleReadVectorHigh:			read_mem(pc_.bytes.high, nextAddress.full+1);						break; | ||||||
| 					case OperationSetI:					inverse_interrupt_flag_ = 0;										continue; | 					case OperationSetIRQFlags: | ||||||
|  | 						inverse_interrupt_flag_ = 0; | ||||||
|  | 						if(is_65c02(personality)) decimal_flag_ = false; | ||||||
|  | 					continue; | ||||||
|  | 					case OperationSetNMIRSTFlags: | ||||||
|  | 						if(is_65c02(personality)) decimal_flag_ = false; | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
| 					case CyclePullPCL:					s_++; read_mem(pc_.bytes.low, s_ | 0x100);							break; | 					case CyclePullPCL:					s_++; read_mem(pc_.bytes.low, s_ | 0x100);							break; | ||||||
| 					case CyclePullPCH:					s_++; read_mem(pc_.bytes.high, s_ | 0x100);							break; | 					case CyclePullPCH:					s_++; read_mem(pc_.bytes.high, s_ | 0x100);							break; | ||||||
| 					case CyclePullA:					s_++; read_mem(a_, s_ | 0x100);										break; | 					case CyclePullA:					s_++; read_mem(a_, s_ | 0x100);										break; | ||||||
|  | 					case CyclePullX:					s_++; read_mem(x_, s_ | 0x100);										break; | ||||||
|  | 					case CyclePullY:					s_++; read_mem(y_, s_ | 0x100);										break; | ||||||
| 					case CyclePullOperand:				s_++; read_mem(operand_, s_ | 0x100);								break; | 					case CyclePullOperand:				s_++; read_mem(operand_, s_ | 0x100);								break; | ||||||
| 					case OperationSetFlagsFromOperand:	set_flags(operand_);												continue; | 					case OperationSetFlagsFromOperand:	set_flags(operand_);												continue; | ||||||
| 					case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break;						continue; | 					case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break;						continue; | ||||||
| 					case OperationSetOperandFromFlags:  operand_ = get_flags();												continue; | 					case OperationSetOperandFromFlags:  operand_ = get_flags();												continue; | ||||||
| 					case OperationSetFlagsFromA:		zero_result_ = negative_result_ = a_;								continue; | 					case OperationSetFlagsFromA:		zero_result_ = negative_result_ = a_;								continue; | ||||||
|  | 					case OperationSetFlagsFromX:		zero_result_ = negative_result_ = x_;								continue; | ||||||
|  | 					case OperationSetFlagsFromY:		zero_result_ = negative_result_ = y_;								continue; | ||||||
|  |  | ||||||
| 					case CycleIncrementPCAndReadStack:	pc_.full++; throwaway_read(s_ | 0x100);													break; | 					case CycleIncrementPCAndReadStack:	pc_.full++; throwaway_read(s_ | 0x100);													break; | ||||||
| 					case CycleReadPCLFromAddress:		read_mem(pc_.bytes.low, address_.full);													break; | 					case CycleReadPCLFromAddress:		read_mem(pc_.bytes.low, address_.full);													break; | ||||||
| 					case CycleReadPCHFromAddress:		address_.bytes.low++; read_mem(pc_.bytes.high, address_.full);		break; | 					case CycleReadPCHFromAddressLowInc:	address_.bytes.low++; read_mem(pc_.bytes.high, address_.full);							break; | ||||||
|  | 					case CycleReadPCHFromAddressFixed:	if(!address_.bytes.low) address_.bytes.high++; read_mem(pc_.bytes.high, address_.full);	break; | ||||||
|  | 					case CycleReadPCHFromAddressInc:	address_.full++; read_mem(pc_.bytes.high, address_.full);								break; | ||||||
|  |  | ||||||
| 					case CycleReadAndIncrementPC: { | 					case CycleReadAndIncrementPC: { | ||||||
| 						uint16_t oldPC = pc_.full; | 						uint16_t oldPC = pc_.full; | ||||||
| @@ -156,13 +210,21 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 						throwaway_read(oldPC); | 						throwaway_read(oldPC); | ||||||
| 					} break; | 					} break; | ||||||
|  |  | ||||||
| // MARK: - JAM | // MARK: - JAM, WAI, STP | ||||||
|  |  | ||||||
| 					case CycleScheduleJam: { | 					case OperationScheduleJam: { | ||||||
| 						is_jammed_ = true; | 						is_jammed_ = true; | ||||||
| 						scheduled_program_counter_ = operations[CPU::MOS6502::JamOpcode]; | 						scheduled_program_counter_ = operations_[CPU::MOS6502::JamOpcode]; | ||||||
| 					} continue; | 					} continue; | ||||||
|  |  | ||||||
|  | 					case OperationScheduleStop: | ||||||
|  | 						stop_is_active_ = true; | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 					case OperationScheduleWait: | ||||||
|  | 						wait_is_active_ = true; | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| // MARK: - Bitwise | // MARK: - Bitwise | ||||||
|  |  | ||||||
| 					case OperationORA:	a_ |= operand_;	negative_result_ = zero_result_ = a_;		continue; | 					case OperationORA:	a_ |= operand_;	negative_result_ = zero_result_ = a_;		continue; | ||||||
| @@ -175,10 +237,12 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					case OperationLDX:	x_ = negative_result_ = zero_result_ = operand_;			continue; | 					case OperationLDX:	x_ = negative_result_ = zero_result_ = operand_;			continue; | ||||||
| 					case OperationLDY:	y_ = negative_result_ = zero_result_ = operand_;			continue; | 					case OperationLDY:	y_ = negative_result_ = zero_result_ = operand_;			continue; | ||||||
| 					case OperationLAX:	a_ = x_ = negative_result_ = zero_result_ = operand_;		continue; | 					case OperationLAX:	a_ = x_ = negative_result_ = zero_result_ = operand_;		continue; | ||||||
|  | 					case OperationCopyOperandToA:		a_ = operand_;								continue; | ||||||
|  |  | ||||||
| 					case OperationSTA:	operand_ = a_;											continue; | 					case OperationSTA:	operand_ = a_;											continue; | ||||||
| 					case OperationSTX:	operand_ = x_;											continue; | 					case OperationSTX:	operand_ = x_;											continue; | ||||||
| 					case OperationSTY:	operand_ = y_;											continue; | 					case OperationSTY:	operand_ = y_;											continue; | ||||||
|  | 					case OperationSTZ:	operand_ = 0;											continue; | ||||||
| 					case OperationSAX:	operand_ = a_ & x_;										continue; | 					case OperationSAX:	operand_ = a_ & x_;										continue; | ||||||
| 					case OperationSHA:	operand_ = a_ & x_ & (address_.bytes.high+1);			continue; | 					case OperationSHA:	operand_ = a_ & x_ & (address_.bytes.high+1);			continue; | ||||||
| 					case OperationSHX:	operand_ = x_ & (address_.bytes.high+1);				continue; | 					case OperationSHX:	operand_ = x_ & (address_.bytes.high+1);				continue; | ||||||
| @@ -208,20 +272,40 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 						carry_flag_ = ((~temp16) >> 8)&1; | 						carry_flag_ = ((~temp16) >> 8)&1; | ||||||
| 					} continue; | 					} continue; | ||||||
|  |  | ||||||
| // MARK: - BIT | // MARK: - BIT, TSB, TRB | ||||||
|  |  | ||||||
| 					case OperationBIT: | 					case OperationBIT: | ||||||
| 						zero_result_ = operand_ & a_; | 						zero_result_ = operand_ & a_; | ||||||
| 						negative_result_ = operand_; | 						negative_result_ = operand_; | ||||||
| 						overflow_flag_ = operand_&Flag::Overflow; | 						overflow_flag_ = operand_&Flag::Overflow; | ||||||
| 					continue; | 					continue; | ||||||
|  | 					case OperationBITNoNV: | ||||||
|  | 						zero_result_ = operand_ & a_; | ||||||
|  | 					continue; | ||||||
|  | 					case OperationTRB: | ||||||
|  | 						zero_result_ = operand_ & a_; | ||||||
|  | 						operand_ &= ~a_; | ||||||
|  | 					continue; | ||||||
|  | 					case OperationTSB: | ||||||
|  | 						zero_result_ = operand_ & a_; | ||||||
|  | 						operand_ |= a_; | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
|  | // MARK: - RMB and SMB | ||||||
|  |  | ||||||
|  | 					case OperationRMB: | ||||||
|  | 						operand_ &= ~(1 << (operation_ >> 4)); | ||||||
|  | 					continue; | ||||||
|  | 					case OperationSMB: | ||||||
|  | 						operand_ |= 1 << ((operation_ >> 4)&7); | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
| // MARK: - ADC/SBC (and INS) | // MARK: - ADC/SBC (and INS) | ||||||
|  |  | ||||||
| 					case OperationINS: | 					case OperationINS: | ||||||
| 						operand_++;			// deliberate fallthrough | 						operand_++;			// deliberate fallthrough | ||||||
| 					case OperationSBC: | 					case OperationSBC: | ||||||
| 						if(decimal_flag_) { | 						if(decimal_flag_ && has_decimal_mode(personality)) { | ||||||
| 							const uint16_t notCarry = carry_flag_ ^ 0x1; | 							const uint16_t notCarry = carry_flag_ ^ 0x1; | ||||||
| 							const uint16_t decimalResult = static_cast<uint16_t>(a_) - static_cast<uint16_t>(operand_) - notCarry; | 							const uint16_t decimalResult = static_cast<uint16_t>(a_) - static_cast<uint16_t>(operand_) - notCarry; | ||||||
| 							uint16_t temp16; | 							uint16_t temp16; | ||||||
| @@ -239,6 +323,12 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
|  |  | ||||||
| 							carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry; | 							carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry; | ||||||
| 							a_ = static_cast<uint8_t>(temp16); | 							a_ = static_cast<uint8_t>(temp16); | ||||||
|  |  | ||||||
|  | 							if(is_65c02(personality)) { | ||||||
|  | 								negative_result_ = zero_result_ = a_; | ||||||
|  | 								read_mem(operand_, address_.full); | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
| 							continue; | 							continue; | ||||||
| 						} else { | 						} else { | ||||||
| 							operand_ = ~operand_; | 							operand_ = ~operand_; | ||||||
| @@ -246,7 +336,7 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
|  |  | ||||||
| 					// deliberate fallthrough | 					// deliberate fallthrough | ||||||
| 					case OperationADC: | 					case OperationADC: | ||||||
| 						if(decimal_flag_) { | 						if(decimal_flag_ && has_decimal_mode(personality)) { | ||||||
| 							const uint16_t decimalResult = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_); | 							const uint16_t decimalResult = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_); | ||||||
|  |  | ||||||
| 							uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_; | 							uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_; | ||||||
| @@ -259,6 +349,12 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 							carry_flag_ = (result >> 8) ? 1 : 0; | 							carry_flag_ = (result >> 8) ? 1 : 0; | ||||||
| 							a_ = static_cast<uint8_t>(result); | 							a_ = static_cast<uint8_t>(result); | ||||||
| 							zero_result_ = static_cast<uint8_t>(decimalResult); | 							zero_result_ = static_cast<uint8_t>(decimalResult); | ||||||
|  |  | ||||||
|  | 							if(is_65c02(personality)) { | ||||||
|  | 								negative_result_ = zero_result_ = a_; | ||||||
|  | 								read_mem(operand_, address_.full); | ||||||
|  | 								break; | ||||||
|  | 							} | ||||||
| 						} else { | 						} else { | ||||||
| 							const uint16_t result = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_); | 							const uint16_t result = static_cast<uint16_t>(a_) + static_cast<uint16_t>(operand_) + static_cast<uint16_t>(carry_flag_); | ||||||
| 							overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; | 							overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; | ||||||
| @@ -345,6 +441,8 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
|  |  | ||||||
| 					case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue; | 					case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue; | ||||||
| 					case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue; | 					case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue; | ||||||
|  | 					case OperationINA: a_++; negative_result_ = zero_result_ = a_; continue; | ||||||
|  | 					case OperationDEA: a_--; negative_result_ = zero_result_ = a_; continue; | ||||||
| 					case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue; | 					case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue; | ||||||
| 					case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue; | 					case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue; | ||||||
| 					case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue; | 					case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue; | ||||||
| @@ -368,32 +466,42 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
|  |  | ||||||
| // MARK: - Addressing Mode Work | // MARK: - Addressing Mode Work | ||||||
|  |  | ||||||
|  | #define page_crossing_stall_read()	\ | ||||||
|  | 	if(is_65c02(personality)) {	\ | ||||||
|  | 		throwaway_read(pc_.full - 1);	\ | ||||||
|  | 	} else {	\ | ||||||
|  | 		throwaway_read(address_.full);	\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 					case CycleAddXToAddressLow: | 					case CycleAddXToAddressLow: | ||||||
| 						nextAddress.full = address_.full + x_; | 						nextAddress.full = address_.full + x_; | ||||||
| 						address_.bytes.low = nextAddress.bytes.low; | 						address_.bytes.low = nextAddress.bytes.low; | ||||||
| 						if(address_.bytes.high != nextAddress.bytes.high) {		 | 						if(address_.bytes.high != nextAddress.bytes.high) {		 | ||||||
| 							throwaway_read(address_.full); | 							page_crossing_stall_read(); | ||||||
| 							break; | 							break; | ||||||
| 						} | 						} | ||||||
| 					continue; | 					continue; | ||||||
| 					case CycleAddXToAddressLowRead: | 					case CycleAddXToAddressLowRead: | ||||||
| 						nextAddress.full = address_.full + x_; | 						nextAddress.full = address_.full + x_; | ||||||
| 						address_.bytes.low = nextAddress.bytes.low; | 						address_.bytes.low = nextAddress.bytes.low; | ||||||
| 						throwaway_read(address_.full); | 						page_crossing_stall_read(); | ||||||
| 					break; | 					break; | ||||||
| 					case CycleAddYToAddressLow: | 					case CycleAddYToAddressLow: | ||||||
| 						nextAddress.full = address_.full + y_; | 						nextAddress.full = address_.full + y_; | ||||||
| 						address_.bytes.low = nextAddress.bytes.low; | 						address_.bytes.low = nextAddress.bytes.low; | ||||||
| 						if(address_.bytes.high != nextAddress.bytes.high) { | 						if(address_.bytes.high != nextAddress.bytes.high) { | ||||||
| 							throwaway_read(address_.full); | 							page_crossing_stall_read(); | ||||||
| 							break; | 							break; | ||||||
| 						} | 						} | ||||||
| 					continue; | 					continue; | ||||||
| 					case CycleAddYToAddressLowRead: | 					case CycleAddYToAddressLowRead: | ||||||
| 						nextAddress.full = address_.full + y_; | 						nextAddress.full = address_.full + y_; | ||||||
| 						address_.bytes.low = nextAddress.bytes.low; | 						address_.bytes.low = nextAddress.bytes.low; | ||||||
| 						throwaway_read(address_.full); | 						page_crossing_stall_read(); | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
|  | #undef page_crossing_stall_read | ||||||
|  |  | ||||||
| 					case OperationCorrectAddressHigh: | 					case OperationCorrectAddressHigh: | ||||||
| 						address_.full = nextAddress.full; | 						address_.full = nextAddress.full; | ||||||
| 					continue; | 					continue; | ||||||
| @@ -405,6 +513,9 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 						operand_ += x_; | 						operand_ += x_; | ||||||
| 						read_mem(address_.bytes.low, operand_); | 						read_mem(address_.bytes.low, operand_); | ||||||
| 					break; | 					break; | ||||||
|  | 					case CycleFetchAddressLowFromOperand: | ||||||
|  | 						read_mem(address_.bytes.low, operand_); | ||||||
|  | 					break; | ||||||
| 					case CycleIncrementOperandFetchAddressHigh: | 					case CycleIncrementOperandFetchAddressHigh: | ||||||
| 						operand_++; | 						operand_++; | ||||||
| 						read_mem(address_.bytes.high, operand_); | 						read_mem(address_.bytes.high, operand_); | ||||||
| @@ -449,12 +560,14 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					case OperationIncrementPC:			pc_.full++;						continue; | 					case OperationIncrementPC:			pc_.full++;						continue; | ||||||
| 					case CycleFetchOperandFromAddress:	read_mem(operand_, address_.full);	break; | 					case CycleFetchOperandFromAddress:	read_mem(operand_, address_.full);	break; | ||||||
| 					case CycleWriteOperandToAddress:	write_mem(operand_, address_.full);	break; | 					case CycleWriteOperandToAddress:	write_mem(operand_, address_.full);	break; | ||||||
| 					case OperationCopyOperandFromA:		operand_ = a_;					continue; |  | ||||||
| 					case OperationCopyOperandToA:		a_ = operand_;					continue; |  | ||||||
|  |  | ||||||
| // MARK: - Branching | // MARK: - Branching | ||||||
|  |  | ||||||
| #define BRA(condition)	pc_.full++; if(condition) scheduled_program_counter_ = doBranch | #define BRA(condition)	\ | ||||||
|  | 	pc_.full++; \ | ||||||
|  | 	if(condition) {	\ | ||||||
|  | 		scheduled_program_counter_ = do_branch;	\ | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 					case OperationBPL: BRA(!(negative_result_&0x80));				continue; | 					case OperationBPL: BRA(!(negative_result_&0x80));				continue; | ||||||
| 					case OperationBMI: BRA(negative_result_&0x80);					continue; | 					case OperationBMI: BRA(negative_result_&0x80);					continue; | ||||||
| @@ -464,6 +577,9 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					case OperationBCS: BRA(carry_flag_);							continue; | 					case OperationBCS: BRA(carry_flag_);							continue; | ||||||
| 					case OperationBNE: BRA(zero_result_);							continue; | 					case OperationBNE: BRA(zero_result_);							continue; | ||||||
| 					case OperationBEQ: BRA(!zero_result_);							continue; | 					case OperationBEQ: BRA(!zero_result_);							continue; | ||||||
|  | 					case OperationBRA: BRA(true);									continue; | ||||||
|  |  | ||||||
|  | #undef BRA | ||||||
|  |  | ||||||
| 					case CycleAddSignedOperandToPC: | 					case CycleAddSignedOperandToPC: | ||||||
| 						nextAddress.full = static_cast<uint16_t>(pc_.full + (int8_t)operand_); | 						nextAddress.full = static_cast<uint16_t>(pc_.full + (int8_t)operand_); | ||||||
| @@ -473,10 +589,46 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 							pc_.full = nextAddress.full; | 							pc_.full = nextAddress.full; | ||||||
| 							throwaway_read(halfUpdatedPc); | 							throwaway_read(halfUpdatedPc); | ||||||
| 							break; | 							break; | ||||||
|  | 						} else if(is_65c02(personality)) { | ||||||
|  | 							// 65C02 modification to all branches: a branch that is taken but requires only a single cycle | ||||||
|  | 							// to target its destination skips any pending interrupts. | ||||||
|  | 							// Cf. http://forum.6502.org/viewtopic.php?f=4&t=1634 | ||||||
|  | 							scheduled_program_counter_ = fetch_decode_execute; | ||||||
| 						} | 						} | ||||||
| 					continue; | 					continue; | ||||||
|  |  | ||||||
| #undef BRA | 					case CycleFetchFromHalfUpdatedPC: { | ||||||
|  | 						uint16_t halfUpdatedPc = static_cast<uint16_t>(((pc_.bytes.low + (int8_t)operand_) & 0xff) | (pc_.bytes.high << 8)); | ||||||
|  | 						throwaway_read(halfUpdatedPc); | ||||||
|  | 					} break; | ||||||
|  |  | ||||||
|  | 					case OperationAddSignedOperandToPC16: | ||||||
|  | 						pc_.full = static_cast<uint16_t>(pc_.full + (int8_t)operand_); | ||||||
|  | 					continue; | ||||||
|  |  | ||||||
|  | 					case OperationBBRBBS: { | ||||||
|  | 						// To reach here, the 6502 has (i) read the operation; (ii) read the first operand; | ||||||
|  | 						// and (iii) read from the corresponding zero page. | ||||||
|  | 						const uint8_t mask = static_cast<uint8_t>(1 << ((operation_ >> 4)&7)); | ||||||
|  | 						if((operand_ & mask) == ((operation_ & 0x80) ? mask : 0)) { | ||||||
|  | 							static const MicroOp do_branch[] = { | ||||||
|  | 								CycleFetchOperand,			// Fetch offset. | ||||||
|  | 								OperationIncrementPC, | ||||||
|  | 								CycleFetchFromHalfUpdatedPC, | ||||||
|  | 								OperationAddSignedOperandToPC16, | ||||||
|  | 								OperationMoveToNextProgram | ||||||
|  | 							}; | ||||||
|  | 							scheduled_program_counter_ = do_branch; | ||||||
|  | 						} else { | ||||||
|  | 							static const MicroOp do_not_branch[] = { | ||||||
|  | 								CycleFetchOperand, | ||||||
|  | 								OperationIncrementPC, | ||||||
|  | 								CycleFetchFromHalfUpdatedPC, | ||||||
|  | 								OperationMoveToNextProgram | ||||||
|  | 							}; | ||||||
|  | 							scheduled_program_counter_ = do_not_branch; | ||||||
|  | 						} | ||||||
|  | 					} break; | ||||||
|  |  | ||||||
| // MARK: - Transfers | // MARK: - Transfers | ||||||
|  |  | ||||||
| @@ -517,7 +669,10 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 					continue; | 					continue; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if(uses_ready_line && ready_line_is_enabled_ && isReadOperation(nextBusOperation)) { | 				if(has_stpwai(personality) && (stop_is_active_ || wait_is_active_)) { | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 				if(uses_ready_line && ready_line_is_enabled_ && (is_65c02(personality) || isReadOperation(nextBusOperation))) { | ||||||
| 					ready_is_active_ = true; | 					ready_is_active_ = true; | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| @@ -535,7 +690,7 @@ if(number_of_cycles <= Cycles(0)) break; | |||||||
| 	bus_handler_.flush(); | 	bus_handler_.flush(); | ||||||
| } | } | ||||||
|  |  | ||||||
| template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>::set_ready_line(bool active) { | template <Personality personality, typename T, bool uses_ready_line> void Processor<personality, T, uses_ready_line>::set_ready_line(bool active) { | ||||||
| 	assert(uses_ready_line); | 	assert(uses_ready_line); | ||||||
| 	if(active) { | 	if(active) { | ||||||
| 		ready_line_is_enabled_ = true; | 		ready_line_is_enabled_ = true; | ||||||
| @@ -583,6 +738,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_reset_program() { | |||||||
| 		CycleNoWritePush, | 		CycleNoWritePush, | ||||||
| 		OperationRSTPickVector, | 		OperationRSTPickVector, | ||||||
| 		CycleNoWritePush, | 		CycleNoWritePush, | ||||||
|  | 		OperationSetNMIRSTFlags, | ||||||
| 		CycleReadVectorLow, | 		CycleReadVectorLow, | ||||||
| 		CycleReadVectorHigh, | 		CycleReadVectorHigh, | ||||||
| 		OperationMoveToNextProgram | 		OperationMoveToNextProgram | ||||||
| @@ -599,7 +755,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_irq_program() { | |||||||
| 		OperationBRKPickVector, | 		OperationBRKPickVector, | ||||||
| 		OperationSetOperandFromFlags, | 		OperationSetOperandFromFlags, | ||||||
| 		CyclePushOperand, | 		CyclePushOperand, | ||||||
| 		OperationSetI, | 		OperationSetIRQFlags, | ||||||
| 		CycleReadVectorLow, | 		CycleReadVectorLow, | ||||||
| 		CycleReadVectorHigh, | 		CycleReadVectorHigh, | ||||||
| 		OperationMoveToNextProgram | 		OperationMoveToNextProgram | ||||||
| @@ -616,6 +772,7 @@ inline const ProcessorStorage::MicroOp *ProcessorStorage::get_nmi_program() { | |||||||
| 		OperationNMIPickVector, | 		OperationNMIPickVector, | ||||||
| 		OperationSetOperandFromFlags, | 		OperationSetOperandFromFlags, | ||||||
| 		CyclePushOperand, | 		CyclePushOperand, | ||||||
|  | 		OperationSetNMIRSTFlags, | ||||||
| 		CycleReadVectorLow, | 		CycleReadVectorLow, | ||||||
| 		CycleReadVectorHigh, | 		CycleReadVectorHigh, | ||||||
| 		OperationMoveToNextProgram | 		OperationMoveToNextProgram | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
|  |  | ||||||
| #include "../6502.hpp" | #include "../6502.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
| using namespace CPU::MOS6502; | using namespace CPU::MOS6502; | ||||||
|  |  | ||||||
| #define Program(...)						{__VA_ARGS__, OperationMoveToNextProgram} | #define Program(...)						{__VA_ARGS__, OperationMoveToNextProgram} | ||||||
| @@ -20,13 +22,14 @@ using namespace CPU::MOS6502; | |||||||
| #define Zero								OperationLoadAddressZeroPage | #define Zero								OperationLoadAddressZeroPage | ||||||
| #define ZeroX								CycleLoadAddessZeroX | #define ZeroX								CycleLoadAddessZeroX | ||||||
| #define ZeroY								CycleLoadAddessZeroY | #define ZeroY								CycleLoadAddessZeroY | ||||||
|  | #define ZeroIndirect						OperationLoadAddressZeroPage,				CycleFetchAddressLowFromOperand,		CycleIncrementOperandFetchAddressHigh | ||||||
| #define IndexedIndirect						CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow,		CycleIncrementOperandFetchAddressHigh | #define IndexedIndirect						CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow,		CycleIncrementOperandFetchAddressHigh | ||||||
| #define IndirectIndexedr					CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh,	CycleAddYToAddressLow,					OperationCorrectAddressHigh | #define IndirectIndexedr					CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh,	CycleAddYToAddressLow,					OperationCorrectAddressHigh | ||||||
| #define IndirectIndexed						CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh,	CycleAddYToAddressLowRead,				OperationCorrectAddressHigh | #define IndirectIndexed						CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh,	CycleAddYToAddressLowRead,				OperationCorrectAddressHigh | ||||||
|  |  | ||||||
| #define Read(...)							CycleFetchOperandFromAddress,	__VA_ARGS__ | #define Read(...)							CycleFetchOperandFromAddress,	__VA_ARGS__ | ||||||
| #define Write(...)							__VA_ARGS__,					CycleWriteOperandToAddress | #define Write(...)							__VA_ARGS__,					CycleWriteOperandToAddress | ||||||
| #define ReadModifyWrite(...)				CycleFetchOperandFromAddress,	CycleWriteOperandToAddress,			__VA_ARGS__,							CycleWriteOperandToAddress | #define ReadModifyWrite(...)				CycleFetchOperandFromAddress,	is_65c02(personality) ? CycleFetchOperandFromAddress : CycleWriteOperandToAddress,			__VA_ARGS__,							CycleWriteOperandToAddress | ||||||
|  |  | ||||||
| #define AbsoluteRead(op)					Program(Absolute,			Read(op)) | #define AbsoluteRead(op)					Program(Absolute,			Read(op)) | ||||||
| #define AbsoluteXRead(op)					Program(AbsoluteXr,			Read(op)) | #define AbsoluteXRead(op)					Program(AbsoluteXr,			Read(op)) | ||||||
| @@ -34,6 +37,7 @@ using namespace CPU::MOS6502; | |||||||
| #define ZeroRead(...)						Program(Zero,				Read(__VA_ARGS__)) | #define ZeroRead(...)						Program(Zero,				Read(__VA_ARGS__)) | ||||||
| #define ZeroXRead(op)						Program(ZeroX,				Read(op)) | #define ZeroXRead(op)						Program(ZeroX,				Read(op)) | ||||||
| #define ZeroYRead(op)						Program(ZeroY,				Read(op)) | #define ZeroYRead(op)						Program(ZeroY,				Read(op)) | ||||||
|  | #define ZeroIndirectRead(op)				Program(ZeroIndirect,		Read(op)) | ||||||
| #define IndexedIndirectRead(op)				Program(IndexedIndirect,	Read(op)) | #define IndexedIndirectRead(op)				Program(IndexedIndirect,	Read(op)) | ||||||
| #define IndirectIndexedRead(op)				Program(IndirectIndexedr,	Read(op)) | #define IndirectIndexedRead(op)				Program(IndirectIndexedr,	Read(op)) | ||||||
|  |  | ||||||
| @@ -43,6 +47,7 @@ using namespace CPU::MOS6502; | |||||||
| #define ZeroWrite(op)						Program(Zero,				Write(op)) | #define ZeroWrite(op)						Program(Zero,				Write(op)) | ||||||
| #define ZeroXWrite(op)						Program(ZeroX,				Write(op)) | #define ZeroXWrite(op)						Program(ZeroX,				Write(op)) | ||||||
| #define ZeroYWrite(op)						Program(ZeroY,				Write(op)) | #define ZeroYWrite(op)						Program(ZeroY,				Write(op)) | ||||||
|  | #define ZeroIndirectWrite(op)				Program(ZeroIndirect,		Write(op)) | ||||||
| #define IndexedIndirectWrite(op)			Program(IndexedIndirect,	Write(op)) | #define IndexedIndirectWrite(op)			Program(IndexedIndirect,	Write(op)) | ||||||
| #define IndirectIndexedWrite(op)			Program(IndirectIndexed,	Write(op)) | #define IndirectIndexedWrite(op)			Program(IndirectIndexed,	Write(op)) | ||||||
|  |  | ||||||
| @@ -55,8 +60,11 @@ using namespace CPU::MOS6502; | |||||||
| #define IndexedIndirectReadModifyWrite(...)	Program(IndexedIndirect,	ReadModifyWrite(__VA_ARGS__)) | #define IndexedIndirectReadModifyWrite(...)	Program(IndexedIndirect,	ReadModifyWrite(__VA_ARGS__)) | ||||||
| #define IndirectIndexedReadModifyWrite(...)	Program(IndirectIndexed,	ReadModifyWrite(__VA_ARGS__)) | #define IndirectIndexedReadModifyWrite(...)	Program(IndirectIndexed,	ReadModifyWrite(__VA_ARGS__)) | ||||||
|  |  | ||||||
|  | #define FastAbsoluteXReadModifyWrite(...)		Program(AbsoluteXr,			ReadModifyWrite(__VA_ARGS__)) | ||||||
|  | #define FastAbsoluteYReadModifyWrite(...)		Program(AbsoluteYr,			ReadModifyWrite(__VA_ARGS__)) | ||||||
|  |  | ||||||
| #define Immediate(op)						Program(OperationIncrementPC,		op) | #define Immediate(op)						Program(OperationIncrementPC,		op) | ||||||
| #define Implied(op)							Program(OperationCopyOperandFromA,	op,	OperationCopyOperandToA) | #define Implied(op)							Program(OperationSTA,				op,	OperationCopyOperandToA) | ||||||
|  |  | ||||||
| #define ZeroNop()							Program(Zero, CycleFetchOperandFromAddress) | #define ZeroNop()							Program(Zero, CycleFetchOperandFromAddress) | ||||||
| #define ZeroXNop()							Program(ZeroX, CycleFetchOperandFromAddress) | #define ZeroXNop()							Program(ZeroX, CycleFetchOperandFromAddress) | ||||||
| @@ -65,10 +73,17 @@ using namespace CPU::MOS6502; | |||||||
| #define ImpliedNop()						{OperationMoveToNextProgram} | #define ImpliedNop()						{OperationMoveToNextProgram} | ||||||
| #define ImmediateNop()						Program(OperationIncrementPC) | #define ImmediateNop()						Program(OperationIncrementPC) | ||||||
|  |  | ||||||
| #define JAM									{CycleFetchOperand, CycleScheduleJam} | #define JAM									{CycleFetchOperand, OperationScheduleJam} | ||||||
|  |  | ||||||
| const ProcessorStorage::MicroOp ProcessorStorage::operations[256][10] = { | ProcessorStorage::ProcessorStorage(Personality personality) { | ||||||
| 	/* 0x00 BRK */			Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetI, CycleReadVectorLow, CycleReadVectorHigh), | 	// only the interrupt flag is defined upon reset but get_flags isn't going to | ||||||
|  | 	// mask the other flags so we need to do that, at least | ||||||
|  | 	carry_flag_ &= Flag::Carry; | ||||||
|  | 	decimal_flag_ &= Flag::Decimal; | ||||||
|  | 	overflow_flag_ &= Flag::Overflow; | ||||||
|  |  | ||||||
|  | 	const InstructionList operations_6502[256] = { | ||||||
|  | 		/* 0x00 BRK */			Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh), | ||||||
| 		/* 0x01 ORA x, ind */	IndexedIndirectRead(OperationORA), | 		/* 0x01 ORA x, ind */	IndexedIndirectRead(OperationORA), | ||||||
| 		/* 0x02 JAM */			JAM,																	/* 0x03 ASO x, ind */	IndexedIndirectReadModifyWrite(OperationASO), | 		/* 0x02 JAM */			JAM,																	/* 0x03 ASO x, ind */	IndexedIndirectReadModifyWrite(OperationASO), | ||||||
| 		/* 0x04 NOP zpg */		ZeroNop(),																/* 0x05 ORA zpg */		ZeroRead(OperationORA), | 		/* 0x04 NOP zpg */		ZeroNop(),																/* 0x05 ORA zpg */		ZeroRead(OperationORA), | ||||||
| @@ -128,7 +143,7 @@ const ProcessorStorage::MicroOp ProcessorStorage::operations[256][10] = { | |||||||
| 		/* 0x66 ROR zpg */		ZeroReadModifyWrite(OperationROR),										/* 0x67 RRA zpg */		ZeroReadModifyWrite(OperationRRA, OperationADC), | 		/* 0x66 ROR zpg */		ZeroReadModifyWrite(OperationROR),										/* 0x67 RRA zpg */		ZeroReadModifyWrite(OperationRRA, OperationADC), | ||||||
| 		/* 0x68 PLA */			Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA),			/* 0x69 ADC # */		Immediate(OperationADC), | 		/* 0x68 PLA */			Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA),			/* 0x69 ADC # */		Immediate(OperationADC), | ||||||
| 		/* 0x6a ROR A */		Implied(OperationROR),													/* 0x6b ARR # */		Immediate(OperationARR), | 		/* 0x6a ROR A */		Implied(OperationROR),													/* 0x6b ARR # */		Immediate(OperationARR), | ||||||
| 	/* 0x6c JMP (abs) */	Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress), | 		/* 0x6c JMP (abs) */	Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddressLowInc), | ||||||
| 		/* 0x6d ADC abs */		AbsoluteRead(OperationADC), | 		/* 0x6d ADC abs */		AbsoluteRead(OperationADC), | ||||||
| 		/* 0x6e ROR abs */		AbsoluteReadModifyWrite(OperationROR),									/* 0x6f RRA abs */		AbsoluteReadModifyWrite(OperationRRA, OperationADC), | 		/* 0x6e ROR abs */		AbsoluteReadModifyWrite(OperationROR),									/* 0x6f RRA abs */		AbsoluteReadModifyWrite(OperationRRA, OperationADC), | ||||||
| 		/* 0x70 BVS */			Program(OperationBVS),													/* 0x71 ADC ind, y */	IndirectIndexedRead(OperationADC), | 		/* 0x70 BVS */			Program(OperationBVS),													/* 0x71 ADC ind, y */	IndirectIndexedRead(OperationADC), | ||||||
| @@ -205,49 +220,168 @@ const ProcessorStorage::MicroOp ProcessorStorage::operations[256][10] = { | |||||||
| 		/* 0xfe INC abs, x */	AbsoluteXReadModifyWrite(OperationINC),									/* 0xff INS abs, x */	AbsoluteXReadModifyWrite(OperationINS), | 		/* 0xfe INC abs, x */	AbsoluteXReadModifyWrite(OperationINC),									/* 0xff INS abs, x */	AbsoluteXReadModifyWrite(OperationINS), | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| #undef Program | 	// Install the basic 6502 table. | ||||||
| #undef Absolute | 	memcpy(operations_, operations_6502, sizeof(operations_)); | ||||||
| #undef AbsoluteX |  | ||||||
| #undef AbsoluteY |  | ||||||
| #undef Zero |  | ||||||
| #undef ZeroX |  | ||||||
| #undef ZeroY |  | ||||||
| #undef IndexedIndirect |  | ||||||
| #undef IndirectIndexed |  | ||||||
| #undef Read |  | ||||||
| #undef Write |  | ||||||
| #undef ReadModifyWrite |  | ||||||
| #undef AbsoluteRead |  | ||||||
| #undef AbsoluteXRead |  | ||||||
| #undef AbsoluteYRead |  | ||||||
| #undef ZeroRead |  | ||||||
| #undef ZeroXRead |  | ||||||
| #undef ZeroYRead |  | ||||||
| #undef IndexedIndirectRead |  | ||||||
| #undef IndirectIndexedRead |  | ||||||
| #undef AbsoluteWrite |  | ||||||
| #undef AbsoluteXWrite |  | ||||||
| #undef AbsoluteYWrite |  | ||||||
| #undef ZeroWrite |  | ||||||
| #undef ZeroXWrite |  | ||||||
| #undef ZeroYWrite |  | ||||||
| #undef IndexedIndirectWrite |  | ||||||
| #undef IndirectIndexedWrite |  | ||||||
| #undef AbsoluteReadModifyWrite |  | ||||||
| #undef AbsoluteXReadModifyWrite |  | ||||||
| #undef AbsoluteYReadModifyWrite |  | ||||||
| #undef ZeroReadModifyWrite |  | ||||||
| #undef ZeroXReadModifyWrite |  | ||||||
| #undef ZeroYReadModify |  | ||||||
| #undef IndexedIndirectReadModify |  | ||||||
| #undef IndirectIndexedReadModify |  | ||||||
| #undef Immediate |  | ||||||
| #undef Implied |  | ||||||
|  |  | ||||||
| ProcessorStorage::ProcessorStorage() { | 	// Patch the table according to the chip's personality. | ||||||
| 	// only the interrupt flag is defined upon reset but get_flags isn't going to | 	// | ||||||
| 	// mask the other flags so we need to do that, at least | 	// The 6502 and NES 6502 both have the same mapping of operation codes to actions | ||||||
| 	carry_flag_ &= Flag::Carry; | 	// (respect for the decimal mode flag aside); included in that are 'unofficial' | ||||||
| 	decimal_flag_ &= Flag::Decimal; | 	// operations — spots that are not formally defined to do anything but which the | ||||||
| 	overflow_flag_ &= Flag::Overflow; | 	// processor makes no particular effort to react to in a well-defined way. | ||||||
|  | 	// | ||||||
|  | 	// The 65C02s add some official instructions but also ensure that all of the | ||||||
|  | 	// undefined ones act as no-ops of various addressing modes. | ||||||
|  | 	// | ||||||
|  | 	// So the branch below has to add a bunch of new actions but also removes various | ||||||
|  | 	// others by dint of replacing them with NOPs. | ||||||
|  | 	// | ||||||
|  | 	// Those 6502 opcodes that need redefining, one way or the other, are: | ||||||
|  | 	// | ||||||
|  | 	// 0x02, 0x03, 0x04, 0x07, 0x0b, 0x0c, 0x0f, 0x12, 0x13, 0x14, 0x17, 0x1a, 0x1b, 0x1c, 0x1f, | ||||||
|  | 	// 0x22, 0x23, 0x27, 0x2b, 0x2f, 0x32, 0x33, 0x34, 0x37, 0x3a, 0x3b, 0x3c, 0x3f, | ||||||
|  | 	// 0x42, 0x43, 0x47, 0x4b, 0x4f, 0x52, 0x53, 0x57, 0x5a, 0x5b, 0x5f, | ||||||
|  | 	// 0x62, 0x63, 0x64, 0x67, 0x6b, 0x6f, 0x72, 0x73, 0x74, 0x77, 0x7b, 0x7a, 0x7c, 0x7f, | ||||||
|  | 	// 0x80, 0x82, 0x83, 0x87, 0x89, 0x8b, 0x8f, 0x92, 0x93, 0x97, 0x9b, 0x9e, 0x9c, 0x9f, | ||||||
|  | 	// 0xa3, 0xa7, 0xab, 0xaf, 0xb2, 0xb3, 0xb7, 0xbb, 0xbf, | ||||||
|  | 	// 0xc3, 0xc7, 0xcb, 0xcf, 0xd2, 0xd3, 0xd7, 0xda, 0xdb, 0xdf, | ||||||
|  | 	// 0xe3, 0xe7, 0xeb, 0xef, 0xf2, 0xf3, 0xf7, 0xfa, 0xfb, 0xff | ||||||
|  | 	// | ||||||
|  | 	// ... not including those that aren't defined on the 6502 but perform NOPs exactly like they | ||||||
|  | 	// would on a 65C02. | ||||||
|  |  | ||||||
|  | #define Install(location, instructions) {\ | ||||||
|  | 		const InstructionList code = instructions;	\ | ||||||
|  | 		memcpy(&operations_[location], code, sizeof(InstructionList));	\ | ||||||
|  | 	} | ||||||
|  | 	if(is_65c02(personality)) { | ||||||
|  | 		// Add P[L/H][X/Y]. | ||||||
|  | 		Install(0x5a, Program(CyclePushY)); | ||||||
|  | 		Install(0xda, Program(CyclePushX)); | ||||||
|  | 		Install(0x7a, Program(CycleReadFromS, CyclePullY, OperationSetFlagsFromY)); | ||||||
|  | 		Install(0xfa, Program(CycleReadFromS, CyclePullX, OperationSetFlagsFromX)); | ||||||
|  |  | ||||||
|  | 		// Add BRA. | ||||||
|  | 		Install(0x80, Program(OperationBRA)); | ||||||
|  |  | ||||||
|  | 		// The 1-byte, 1-cycle (!) NOPs. | ||||||
|  | 		for(int c = 0x03; c <= 0xf3; c += 0x10) { | ||||||
|  | 			Install(c, ImpliedNop()); | ||||||
|  | 		} | ||||||
|  | 		for(int c = 0x0b; c <= 0xbb; c += 0x10) { | ||||||
|  | 			Install(c, ImpliedNop()); | ||||||
|  | 		} | ||||||
|  | 		for(int c = 0xeb; c <= 0xfb; c += 0x10) { | ||||||
|  | 			Install(c, ImpliedNop()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// The 2-byte, 2-cycle NOPs that the 6502 doesn't have. | ||||||
|  | 		for(int c = 0x02; c <= 0x62; c += 0x10) { | ||||||
|  | 			Install(c, ImmediateNop()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Correct JMP (abs) and install JMP (abs, x). | ||||||
|  | 		Install(0x6c, Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddressLowInc, CycleReadPCHFromAddressFixed)); | ||||||
|  | 		Install(0x7c, Program( | ||||||
|  | 			CycleReadAddressHLoadAddressL,	// (3) read second byte of (addr) | ||||||
|  | 			CycleAddXToAddressLowRead,		// (4) calculate addr+x, read from (addr+x) with high byte not yet calculated | ||||||
|  | 			OperationCorrectAddressHigh, CycleReadPCLFromAddress,	// (5) read from real (addr+x) | ||||||
|  | 			CycleReadPCHFromAddressInc		// (6) read from addr+x+1 | ||||||
|  | 		)); | ||||||
|  |  | ||||||
|  | 		// Add INA and DEA. | ||||||
|  | 		Install(0x1a, Program(OperationINA)); | ||||||
|  | 		Install(0x3a, Program(OperationDEA)); | ||||||
|  |  | ||||||
|  | 		// Add (zp) operations. | ||||||
|  | 		Install(0x12, ZeroIndirectRead(OperationORA)); | ||||||
|  | 		Install(0x32, ZeroIndirectRead(OperationAND)); | ||||||
|  | 		Install(0x52, ZeroIndirectRead(OperationEOR)); | ||||||
|  | 		Install(0x72, ZeroIndirectRead(OperationADC)); | ||||||
|  | 		Install(0x92, ZeroIndirectWrite(OperationSTA)); | ||||||
|  | 		Install(0xb2, ZeroIndirectRead(OperationLDA)); | ||||||
|  | 		Install(0xd2, ZeroIndirectRead(OperationCMP)); | ||||||
|  | 		Install(0xf2, ZeroIndirectRead(OperationSBC)); | ||||||
|  |  | ||||||
|  | 		// Add STZ. | ||||||
|  | 		Install(0x9c, AbsoluteWrite(OperationSTZ)); | ||||||
|  | 		Install(0x9e, AbsoluteXWrite(OperationSTZ)); | ||||||
|  | 		Install(0x64, ZeroWrite(OperationSTZ)); | ||||||
|  | 		Install(0x74, ZeroXWrite(OperationSTZ)); | ||||||
|  |  | ||||||
|  | 		// Add the extra BITs. | ||||||
|  | 		Install(0x34, ZeroXRead(OperationBIT)); | ||||||
|  | 		Install(0x3c, AbsoluteXRead(OperationBIT)); | ||||||
|  | 		Install(0x89, Immediate(OperationBITNoNV)); | ||||||
|  |  | ||||||
|  | 		// Add TRB and TSB. | ||||||
|  | 		Install(0x04, ZeroReadModifyWrite(OperationTSB)); | ||||||
|  | 		Install(0x0c, AbsoluteReadModifyWrite(OperationTSB)); | ||||||
|  | 		Install(0x14, ZeroReadModifyWrite(OperationTRB)); | ||||||
|  | 		Install(0x1c, AbsoluteReadModifyWrite(OperationTRB)); | ||||||
|  |  | ||||||
|  | 		// Install faster ASL, LSR, ROL, ROR abs,[x/y]. Note: INC, DEC deliberately not improved. | ||||||
|  | 		Install(0x1e, FastAbsoluteXReadModifyWrite(OperationASL)); | ||||||
|  | 		Install(0x1f, FastAbsoluteXReadModifyWrite(OperationASO)); | ||||||
|  | 		Install(0x3e, FastAbsoluteXReadModifyWrite(OperationROL)); | ||||||
|  | 		Install(0x3f, FastAbsoluteXReadModifyWrite(OperationRLA)); | ||||||
|  | 		Install(0x5e, FastAbsoluteXReadModifyWrite(OperationLSR)); | ||||||
|  | 		Install(0x5f, FastAbsoluteXReadModifyWrite(OperationLSE)); | ||||||
|  | 		Install(0x7e, FastAbsoluteXReadModifyWrite(OperationROR)); | ||||||
|  | 		Install(0x7f, FastAbsoluteXReadModifyWrite(OperationRRA, OperationADC)); | ||||||
|  |  | ||||||
|  | 		// Outstanding: | ||||||
|  | 		// 0x07, 0x0f, 0x17, 0x1f, | ||||||
|  | 		// 0x27, 0x2f, 0x37, 0x3f, | ||||||
|  | 		// 0x47, 0x4f, 0x57, 0x5f, | ||||||
|  | 		// 0x67, 0x6f, 0x77, 0x7f, | ||||||
|  | 		// 0x87, 0x8f, 0x97, 0x9f, | ||||||
|  | 		// 0xa7, 0xaf, 0xb7, 0xbf, | ||||||
|  | 		// 0xc7, 0xcb, 0xcf, 0xd7, 0xdb, 0xdf, | ||||||
|  | 		// 0xe7, 0xef, 0xf7, 0xff | ||||||
|  | 		if(has_bbrbbsrmbsmb(personality)) { | ||||||
|  | 			// Add BBS and BBR. These take five cycles. My guessed breakdown is: | ||||||
|  | 			// 1. read opcode | ||||||
|  | 			// 2. read operand | ||||||
|  | 			// 3. read zero page | ||||||
|  | 			// 4. read second operand | ||||||
|  | 			// 5. read from PC without top byte fixed yet | ||||||
|  | 			// ... with the caveat that (3) and (4) could be the other way around. | ||||||
|  | 			for(int location = 0x0f; location <= 0xff; location += 0x10) { | ||||||
|  | 				Install(location, Program(OperationLoadAddressZeroPage, CycleFetchOperandFromAddress, OperationBBRBBS)); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Add RMB and SMB. | ||||||
|  | 			for(int c = 0x07; c <= 0x77; c += 0x10) { | ||||||
|  | 				Install(c, ZeroReadModifyWrite(OperationRMB)); | ||||||
|  | 			} | ||||||
|  | 			for(int c = 0x87; c <= 0xf7; c += 0x10) { | ||||||
|  | 				Install(c, ZeroReadModifyWrite(OperationSMB)); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			for(int location = 0x0f; location <= 0xef; location += 0x20) { | ||||||
|  | 				Install(location, AbsoluteNop()); | ||||||
|  | 			} | ||||||
|  | 			for(int location = 0x1f; location <= 0xff; location += 0x20) { | ||||||
|  | 				Install(location, AbsoluteXNop()); | ||||||
|  | 			} | ||||||
|  | 			for(int c = 0x07; c <= 0xe7; c += 0x20) { | ||||||
|  | 				Install(c, ZeroNop()); | ||||||
|  | 			} | ||||||
|  | 			for(int c = 0x17; c <= 0xf7; c += 0x20) { | ||||||
|  | 				Install(c, ZeroXNop()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Outstanding: | ||||||
|  | 		// 0xcb, 0xdb, | ||||||
|  | 		if(has_stpwai(personality)) { | ||||||
|  | 			Install(0xcb, Program(OperationScheduleWait)); | ||||||
|  | 			Install(0xdb, Program(OperationScheduleStop)); | ||||||
|  | 		} else { | ||||||
|  | 			Install(0xcb, ImpliedNop()); | ||||||
|  | 			Install(0xdb, ZeroXNop()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | #undef Install | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,54 +15,190 @@ | |||||||
| */ | */ | ||||||
| class ProcessorStorage { | class ProcessorStorage { | ||||||
| 	protected: | 	protected: | ||||||
| 		ProcessorStorage(); | 		ProcessorStorage(Personality); | ||||||
|  |  | ||||||
| 		/* | 		/*! | ||||||
| 			This emulation functions by decomposing instructions into micro programs, consisting of the micro operations | 			This emulation functions by decomposing instructions into micro programs, consisting of the micro operations | ||||||
| 			as per the enum below. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle | 			defined by MicroOp. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle | ||||||
| 			to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle). | 			to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle). | ||||||
|  |  | ||||||
|  | 			This micro-instruction set was put together in a fairly ad hoc fashion, I'm afraid, so is unlikely to be optimal. | ||||||
| 		*/ | 		*/ | ||||||
| 		enum MicroOp { | 		enum MicroOp { | ||||||
| 			CycleFetchOperation,						CycleFetchOperand,					OperationDecodeOperation,				CycleIncPCPushPCH, | 			CycleFetchOperation,		// fetches (PC) to operation_, storing PC to last_operation_pc_ before incrementing it | ||||||
| 			CyclePushPCH,								CyclePushPCL,						CyclePushA,								CyclePushOperand, | 			CycleFetchOperand,			// 6502: fetches from (PC) to operand_; 65C02: as 6502 unless operation_ indicates a one-cycle NOP, in which case this is a no0op | ||||||
| 			OperationSetI, | 			OperationDecodeOperation,	// schedules the microprogram associated with operation_ | ||||||
|  | 			OperationMoveToNextProgram,	// either schedules the next fetch-decode-execute or an interrupt response if a request has been pending for at least one cycle | ||||||
|  |  | ||||||
| 			OperationBRKPickVector,						OperationNMIPickVector,				OperationRSTPickVector, | 			CycleIncPCPushPCH,			// increments the PC and pushes PC.h to the stack | ||||||
| 			CycleReadVectorLow,							CycleReadVectorHigh, | 			CyclePushPCL,				// pushes PC.l to the stack | ||||||
|  | 			CyclePushPCH,				// pushes PC.h to the stack | ||||||
|  | 			CyclePushA,					// pushes A to the stack | ||||||
|  | 			CyclePushX,					// pushes X to the stack | ||||||
|  | 			CyclePushY,					// pushes Y to the stack | ||||||
|  | 			CyclePushOperand,			// pushes operand_ to the stack | ||||||
|  |  | ||||||
| 			CycleReadFromS,								CycleReadFromPC, | 			OperationSetIRQFlags,		// 6502: sets I; 65C02: sets I and resets D | ||||||
| 			CyclePullOperand,							CyclePullPCL,						CyclePullPCH,							CyclePullA, | 			OperationSetNMIRSTFlags,	// 6502: no-op. 65C02: resets D | ||||||
| 			CycleNoWritePush, |  | ||||||
| 			CycleReadAndIncrementPC,					CycleIncrementPCAndReadStack,		CycleIncrementPCReadPCHLoadPCL,			CycleReadPCHLoadPCL, | 			OperationBRKPickVector,		// 65C02: sets next_address_ to the BRK vector location; 6502: as 65C02 if no NMI is pending; otherwise sets next_address_ to the NMI address and resets the internal NMI-pending flag | ||||||
| 			CycleReadAddressHLoadAddressL,				CycleReadPCLFromAddress,			CycleReadPCHFromAddress,				CycleLoadAddressAbsolute, | 			OperationNMIPickVector,		// sets next_address_ to the NMI vector | ||||||
| 			OperationLoadAddressZeroPage,				CycleLoadAddessZeroX,				CycleLoadAddessZeroY,					CycleAddXToAddressLow, | 			OperationRSTPickVector,		// sets next_address_ to the RST vector | ||||||
| 			CycleAddYToAddressLow,						CycleAddXToAddressLowRead,			OperationCorrectAddressHigh,			CycleAddYToAddressLowRead, | 			CycleReadVectorLow,			// reads PC.l from next_address_ | ||||||
| 			OperationMoveToNextProgram,					OperationIncrementPC, | 			CycleReadVectorHigh,		// reads PC.h from (next_address_+1) | ||||||
| 			CycleFetchOperandFromAddress,				CycleWriteOperandToAddress,			OperationCopyOperandFromA,				OperationCopyOperandToA, |  | ||||||
| 			CycleIncrementPCFetchAddressLowFromOperand,	CycleAddXToOperandFetchAddressLow,	CycleIncrementOperandFetchAddressHigh,	OperationDecrementOperand, | 			CycleReadFromS,				// performs a read from the stack pointer, throwing the result away | ||||||
| 			OperationIncrementOperand,					OperationORA,						OperationAND,							OperationEOR, | 			CycleReadFromPC,			// performs a read from the program counter, throwing the result away | ||||||
| 			OperationINS,								OperationADC,						OperationSBC,							OperationLDA, |  | ||||||
| 			OperationLDX,								OperationLDY,						OperationLAX,							OperationSTA, | 			CyclePullPCL,				// pulls PC.l from the stack | ||||||
| 			OperationSTX,								OperationSTY,						OperationSAX,							OperationSHA, | 			CyclePullPCH,				// pulls PC.h from the stack | ||||||
| 			OperationSHX,								OperationSHY,						OperationSHS,							OperationCMP, | 			CyclePullA,					// pulls A from the stack | ||||||
| 			OperationCPX,								OperationCPY,						OperationBIT,							OperationASL, | 			CyclePullX,					// pulls X from the stack | ||||||
| 			OperationASO,								OperationROL,						OperationRLA,							OperationLSR, | 			CyclePullY,					// pulls Y from the stack | ||||||
| 			OperationLSE,								OperationASR,						OperationROR,							OperationRRA, | 			CyclePullOperand,			// pulls operand_ from the stack | ||||||
| 			OperationCLC,								OperationCLI,						OperationCLV,							OperationCLD, |  | ||||||
| 			OperationSEC,								OperationSEI,						OperationSED,							OperationINC, | 			CycleNoWritePush,				// decrements S as though it were a push, but reads from the new stack address instead of writing | ||||||
| 			OperationDEC,								OperationINX,						OperationDEX,							OperationINY, | 			CycleReadAndIncrementPC,		// reads from the PC, throwing away the result, and increments the PC | ||||||
| 			OperationDEY,								OperationBPL,						OperationBMI,							OperationBVC, | 			CycleIncrementPCAndReadStack,	// increments the PC and reads from the stack pointer, throwing away the result | ||||||
| 			OperationBVS,								OperationBCC,						OperationBCS,							OperationBNE, | 			CycleIncrementPCReadPCHLoadPCL,	// increments the PC, schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l | ||||||
| 			OperationBEQ,								OperationTXA,						OperationTYA,							OperationTXS, | 			CycleReadPCHLoadPCL,			// schedules a read of PC.h from the post-incremented PC, then copies operand_ to PC.l | ||||||
| 			OperationTAY,								OperationTAX,						OperationTSX,							OperationARR, | 			CycleReadAddressHLoadAddressL,	// increments the PC; copies operand_ to address_.l; reads address_.h from the new PC | ||||||
| 			OperationSBX,								OperationLXA,						OperationANE,							OperationANC, |  | ||||||
| 			OperationLAS,								CycleAddSignedOperandToPC,			OperationSetFlagsFromOperand,			OperationSetOperandFromFlagsWithBRKSet, | 			CycleReadPCLFromAddress,		// reads PC.l from address_ | ||||||
| 			OperationSetOperandFromFlags, | 			CycleReadPCHFromAddressLowInc,	// increments address_.l and reads PC.h from address_ | ||||||
| 			OperationSetFlagsFromA, | 			CycleReadPCHFromAddressFixed,	// if address_.l is 0, increments address_.h; and reads PC.h from address_ | ||||||
| 			CycleScheduleJam | 			CycleReadPCHFromAddressInc,		// increments address_ and reads PC.h from it | ||||||
|  |  | ||||||
|  | 			CycleLoadAddressAbsolute,		// copies operand_ to address_.l, increments the PC, reads address_.h from PC, increments the PC again | ||||||
|  | 			OperationLoadAddressZeroPage,	// copies operand_ to address_ and increments the PC | ||||||
|  | 			CycleLoadAddessZeroX,			// copies (operand_+x)&0xff to address_, increments the PC, and reads from operand_, throwing away the result | ||||||
|  | 			CycleLoadAddessZeroY,			// copies (operand_+y)&0xff to address_, increments the PC, and reads from operand_, throwing away the result | ||||||
|  |  | ||||||
|  | 			CycleAddXToAddressLow,			// calculates address_ + x and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1 | ||||||
|  | 			CycleAddYToAddressLow,			// calculates address_ + y and stores it to next_address_; copies next_address_.l back to address_.l; 6502: if address_ now does not equal next_address_, schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1 | ||||||
|  | 			CycleAddXToAddressLowRead,		// calculates address_ + x and stores it to next_address; copies next_address.l back to address_.l; 6502: schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1 | ||||||
|  | 			CycleAddYToAddressLowRead,		// calculates address_ + y and stores it to next_address; copies next_address.l back to address_.l; 6502: schedules a throwaway read from address_; 65C02: schedules a throaway read from PC-1 | ||||||
|  | 			OperationCorrectAddressHigh,	// copies next_address_ to address_ | ||||||
|  |  | ||||||
|  | 			OperationIncrementPC,			// increments the PC | ||||||
|  | 			CycleFetchOperandFromAddress,	// fetches operand_ from address_ | ||||||
|  | 			CycleWriteOperandToAddress,		// writes operand_ to address_ | ||||||
|  |  | ||||||
|  | 			CycleIncrementPCFetchAddressLowFromOperand,	// increments the PC and loads address_.l from (operand_) | ||||||
|  | 			CycleAddXToOperandFetchAddressLow,			// adds x [in]to operand_, producing an 8-bit result, and reads address_.l from (operand_) | ||||||
|  | 			CycleIncrementOperandFetchAddressHigh,		// increments operand_, producing an 8-bit result, and reads address_.h from (operand_) | ||||||
|  | 			OperationDecrementOperand,					// decrements operand_ | ||||||
|  | 			OperationIncrementOperand,					// increments operand_ | ||||||
|  | 			CycleFetchAddressLowFromOperand,			// reads address_.l from (operand_) | ||||||
|  |  | ||||||
|  | 			OperationORA,	// ORs operand_ into a, setting the negative and zero flags | ||||||
|  | 			OperationAND,	// ANDs operand_ into a, setting the negative and zero flags | ||||||
|  | 			OperationEOR,	// EORs operand_ into a, setting the negative and zero flags | ||||||
|  |  | ||||||
|  | 			OperationINS,	// increments operand_, then performs an SBC of operand_ from a | ||||||
|  | 			OperationADC,	// performs an ADC of operand_ into a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_ | ||||||
|  | 			OperationSBC,	// performs an SBC of operand_ from a_; if this is a 65C02 and decimal mode is set, performs an extra read to operand_ from address_ | ||||||
|  |  | ||||||
|  | 			OperationCMP,	// CMPs a and operand_, setting negative, zero and carry flags | ||||||
|  | 			OperationCPX,	// CMPs x and operand_, setting negative, zero and carry flags | ||||||
|  | 			OperationCPY,	// CMPs y and operand_, setting negative, zero and carry flags | ||||||
|  | 			OperationBIT,	// sets the zero, negative and overflow flags as per a BIT of operand_ against a | ||||||
|  | 			OperationBITNoNV,	// sets the zero flag as per a BIT of operand_ against a | ||||||
|  |  | ||||||
|  | 			OperationLDA,	// loads a with operand_, setting the negative and zero flags | ||||||
|  | 			OperationLDX,	// loads x with operand_, setting the negative and zero flags | ||||||
|  | 			OperationLDY,	// loads y with operand_, setting the negative and zero flags | ||||||
|  | 			OperationLAX,	// loads a and x with operand_, setting the negative and zero flags | ||||||
|  | 			OperationCopyOperandToA,		// sets a_ = operand_, not setting any flags | ||||||
|  |  | ||||||
|  | 			OperationSTA,	// loads operand_ with a | ||||||
|  | 			OperationSTX,	// loads operand_ with x | ||||||
|  | 			OperationSTY,	// loads operand_ with y | ||||||
|  | 			OperationSTZ,	// loads operand_ with 0 | ||||||
|  | 			OperationSAX,	// loads operand_ with a & x | ||||||
|  | 			OperationSHA,	// loads operand_ with a & x & (address.h+1) | ||||||
|  | 			OperationSHX,	// loads operand_ with x & (address.h+1) | ||||||
|  | 			OperationSHY,	// loads operand_ with y & (address.h+1) | ||||||
|  | 			OperationSHS,	// loads s with a & x, then loads operand_ with s & (address.h+1) | ||||||
|  |  | ||||||
|  | 			OperationASL,	// shifts operand_ left, moving the top bit into carry and setting the negative and zero flags | ||||||
|  | 			OperationASO,	// performs an ASL of operand and ORs it into a | ||||||
|  | 			OperationROL,	// performs a ROL of operand_ | ||||||
|  | 			OperationRLA,	// performs a ROL of operand_ and ANDs it into a | ||||||
|  | 			OperationLSR,	// shifts operand_ right, setting carry, negative and zero flags | ||||||
|  | 			OperationLSE,	// performs an LSR and EORs the result into a | ||||||
|  | 			OperationASR,	// ANDs operand_ into a, then performs an LSR | ||||||
|  | 			OperationROR,	// performs a ROR of operand_, setting carry, negative and zero flags | ||||||
|  | 			OperationRRA,	// performs a ROR of operand_ but sets only the carry flag | ||||||
|  |  | ||||||
|  | 			OperationCLC,	// resets the carry flag | ||||||
|  | 			OperationCLI,	// resets I | ||||||
|  | 			OperationCLV,	// resets the overflow flag | ||||||
|  | 			OperationCLD,	// resets the decimal flag | ||||||
|  | 			OperationSEC,	// sets the carry flag | ||||||
|  | 			OperationSEI,	// sets I | ||||||
|  | 			OperationSED,	// sets the decimal flag | ||||||
|  |  | ||||||
|  | 			OperationRMB,	// resets the bit in operand_ implied by operatiopn_ | ||||||
|  | 			OperationSMB,	// sets the bit in operand_ implied by operatiopn_ | ||||||
|  | 			OperationTRB,	// sets zero according to operand_ & a, then resets any bits in operand_ that are set in a | ||||||
|  | 			OperationTSB,	// sets zero according to operand_ & a, then sets any bits in operand_ that are set in a | ||||||
|  |  | ||||||
|  | 			OperationINC,	// increments operand_, setting the negative and zero flags | ||||||
|  | 			OperationDEC,	// decrements operand_, setting the negative and zero flags | ||||||
|  | 			OperationINX,	// increments x, setting the negative and zero flags | ||||||
|  | 			OperationDEX,	// decrements x, setting the negative and zero flags | ||||||
|  | 			OperationINY,	// increments y, setting the negative and zero flags | ||||||
|  | 			OperationDEY,	// decrements y, setting the negative and zero flags | ||||||
|  | 			OperationINA,	// increments a, setting the negative and zero flags | ||||||
|  | 			OperationDEA,	// decrements a, setting the negative and zero flags | ||||||
|  |  | ||||||
|  | 			OperationBPL,	// schedules the branch program if the negative flag is clear | ||||||
|  | 			OperationBMI,	// schedules the branch program if the negative flag is set | ||||||
|  | 			OperationBVC,	// schedules the branch program if the overflow flag is clear | ||||||
|  | 			OperationBVS,	// schedules the branch program if the overflow flag is set | ||||||
|  | 			OperationBCC,	// schedules the branch program if the carry flag is clear | ||||||
|  | 			OperationBCS,	// schedules the branch program if the carry flag is set | ||||||
|  | 			OperationBNE,	// schedules the branch program if the zero flag is clear | ||||||
|  | 			OperationBEQ,	// schedules the branch program if the zero flag is set; 65C02: otherwise jumps straight into a fetch-decode-execute without considering whether to take an interrupt | ||||||
|  | 			OperationBRA,	// schedules the branch program | ||||||
|  |  | ||||||
|  | 			OperationBBRBBS,	// inspecting the operation_, if the appropriate bit of operand_ is set or clear schedules a program to read and act upon the second operand; otherwise schedule a program to read and discard it | ||||||
|  |  | ||||||
|  | 			OperationTXA,	// copies x to a, setting the zero and negative flags | ||||||
|  | 			OperationTYA,	// copies y to a, setting the zero and negative flags | ||||||
|  | 			OperationTXS,	// copies x to s | ||||||
|  | 			OperationTAY,	// copies a to y, setting the zero and negative flags | ||||||
|  | 			OperationTAX,	// copies a to x, setting the zero and negative flags | ||||||
|  | 			OperationTSX,	// copies s to x, setting the zero and negative flags | ||||||
|  |  | ||||||
|  | 			/* The following are amongst the 6502's undocumented (/unintended) operations */ | ||||||
|  | 			OperationARR,	// performs a mixture of ANDing operand_ into a, and shifting the result right | ||||||
|  | 			OperationSBX,	// performs a mixture of an SBC of x&a and operand_, mutating x | ||||||
|  | 			OperationLXA,	// loads a and x with (a | 0xee) & operand, setting the negative and zero flags | ||||||
|  | 			OperationANE,	// loads a_ with (a | 0xee) & operand & x, setting the negative and zero flags | ||||||
|  | 			OperationANC,	// ANDs operand_ into a, setting the negative and zero flags, and loading carry as if the result were shifted right | ||||||
|  | 			OperationLAS,	// loads a, x and s with s & operand, setting the negative and zero flags | ||||||
|  |  | ||||||
|  | 			CycleFetchFromHalfUpdatedPC,		// performs a throwaway read from (PC + (signed)operand).l combined with PC.h | ||||||
|  | 			CycleAddSignedOperandToPC,			// sets next_address to PC + (signed)operand. If the high byte of next_address differs from the PC, schedules a throwaway read from the half-updated PC. 65C02 specific: if the top two bytes are the same, proceeds directly to fetch-decode-execute, ignoring any pending interrupts. | ||||||
|  | 			OperationAddSignedOperandToPC16,	// adds (signed)operand into the PC | ||||||
|  |  | ||||||
|  | 			OperationSetFlagsFromOperand,			// sets all flags based on operand_ | ||||||
|  | 			OperationSetOperandFromFlagsWithBRKSet,	// sets operand_ to the value of all flags, with the break flag set | ||||||
|  | 			OperationSetOperandFromFlags,			// sets operand_ to the value of all flags | ||||||
|  |  | ||||||
|  | 			OperationSetFlagsFromA,		// sets the zero and negative flags based on the value of a | ||||||
|  | 			OperationSetFlagsFromX,		// sets the zero and negative flags based on the value of x | ||||||
|  | 			OperationSetFlagsFromY,		// sets the zero and negative flags based on the value of y | ||||||
|  |  | ||||||
|  | 			OperationScheduleJam,		// schedules the program for operation F2 | ||||||
|  | 			OperationScheduleWait,		// puts the processor into WAI mode (i.e. it'll do nothing until an interrupt is received) | ||||||
|  | 			OperationScheduleStop,		// puts the processor into STP mode (i.e. it'll do nothing until a reset is received) | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		static const MicroOp operations[256][10]; | 		using InstructionList = MicroOp[10]; | ||||||
|  | 		InstructionList operations_[256]; | ||||||
|  |  | ||||||
| 		const MicroOp *scheduled_program_counter_ = nullptr; | 		const MicroOp *scheduled_program_counter_ = nullptr; | ||||||
|  |  | ||||||
| @@ -119,6 +255,8 @@ class ProcessorStorage { | |||||||
|  |  | ||||||
| 		bool ready_is_active_ = false; | 		bool ready_is_active_ = false; | ||||||
| 		bool ready_line_is_enabled_ = false; | 		bool ready_line_is_enabled_ = false; | ||||||
|  | 		bool stop_is_active_ = false; | ||||||
|  | 		bool wait_is_active_ = false; | ||||||
|  |  | ||||||
| 		uint8_t irq_line_ = 0, irq_request_history_ = 0; | 		uint8_t irq_line_ = 0, irq_request_history_ = 0; | ||||||
| 		bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false; | 		bool nmi_line_is_enabled_ = false, set_overflow_line_is_enabled_ = false; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| # Clock Signal | # Clock Signal | ||||||
| Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve. | Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve. | ||||||
|  |  | ||||||
| [Releases](https://github.com/TomHarte/CLK/releases) are hosted on Github. | [Releases](https://github.com/TomHarte/CLK/releases) are hosted on GitHub. | ||||||
|  |  | ||||||
| On the Mac it is a native Cocoa application. Under Linux, BSD and other UNIXes and UNIX-alikes it relies upon SDL 2. | On the Mac it is a native Cocoa application. Under Linux, BSD and other UNIXes and UNIX-alikes it relies upon SDL 2. | ||||||
|  |  | ||||||
| @@ -14,7 +14,7 @@ So its aims are: | |||||||
| It currently contains emulations of the: | It currently contains emulations of the: | ||||||
| * Acorn Electron; | * Acorn Electron; | ||||||
| * Amstrad CPC; | * Amstrad CPC; | ||||||
| * Apple II/II+; | * Apple II/II+ and IIe; | ||||||
| * Atari 2600; | * Atari 2600; | ||||||
| * ColecoVision; | * ColecoVision; | ||||||
| * Commodore Vic-20 (and Commodore 1540/1); | * Commodore Vic-20 (and Commodore 1540/1); | ||||||
| @@ -54,7 +54,7 @@ If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k | |||||||
| ||| | ||| | ||||||
| ||| | ||| | ||||||
|  |  | ||||||
| <img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced"> | <img src="READMEImages/ReptonInterlaced.gif" height=400 alt="Repton title screen, interlaced"> <img src="READMEImages/AppleIIPrinceOfPersia.png" height=400 alt="Apple IIe Prince of Persia"> | ||||||
|  |  | ||||||
| ## Low Latency | ## Low Latency | ||||||
|  |  | ||||||
| @@ -71,7 +71,7 @@ Cycle-accurate emulation for the supported target machines is fairly trite; this | |||||||
| Self-ratings: | Self-ratings: | ||||||
| * the Electron, Oric and Vic-20 are pretty much perfect; | * the Electron, Oric and Vic-20 are pretty much perfect; | ||||||
| * the ZX80, ZX81, ColecoVision and MSX 1 are very strong; | * the ZX80, ZX81, ColecoVision and MSX 1 are very strong; | ||||||
| * the Apple II/II+ should be strong by design, but is currently largely untested; | * the Apple II/II+ and IIe should be strong by design, but are relatively new; | ||||||
| * the Amstrad CPC has known accuracy deficiencies in its 8272 and 6845; | * the Amstrad CPC has known accuracy deficiencies in its 8272 and 6845; | ||||||
| * the Atari 2600 has some known accuracy deficiencies in its TIA; | * the Atari 2600 has some known accuracy deficiencies in its TIA; | ||||||
| * the C-1540(/1) is locked in reading mode and doesn't yet support writing. | * the C-1540(/1) is locked in reading mode and doesn't yet support writing. | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								READMEImages/AppleIIPrinceOfPersia.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								READMEImages/AppleIIPrinceOfPersia.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 451 KiB | 
| @@ -2,5 +2,13 @@ ROM files would ordinarily go here; they are copyright Apple so are not included | |||||||
|  |  | ||||||
| Expected files: | Expected files: | ||||||
|  |  | ||||||
| apple2o.rom — a 12kb image of the original Apple II's ROMs. | apple2o.rom — an image at least 12kb big, in which the final 12kb is the original Apple II's ROM. | ||||||
| apple2-character.rom — a 2kb image of the Apple II+'s character ROM. | apple2.rom — an image at least 12kb big, in which the final 12kb is the Apple II+'s ROM. | ||||||
|  | apple2e.rom — a file at least 15.75kb big, in which the final 12kb is the main portion of the Enhanced IIe ROM, that is visible from $D000, and the 3.75kb before that is the portion that can be paged in from $C100. | ||||||
|  | apple2eu.rom — as per apple2e.rom, but for the Unenhanced Apple II. | ||||||
|  |  | ||||||
|  | apple2-character.rom — a 2kb image of the Apple IIe's character ROM. | ||||||
|  | apple2eu-character.rom — a 4kb image of the Unenhanced IIe's character ROM. | ||||||
|  | apple2e-character.rom — a 4kb image of the Enhanced IIe's character ROM. | ||||||
|  |  | ||||||
|  | Apologies for the wackiness around "at least xkb big", it's to allow for use of files such as those on ftp.apple.asimov.net, which tend to be a bunch of other things, then the system ROM. | ||||||
| @@ -75,19 +75,24 @@ std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) { | |||||||
| 	// In either case below, the code aims for exactly 50,000 bits per track. | 	// In either case below, the code aims for exactly 50,000 bits per track. | ||||||
| 	if(sectors_per_track_ == 16) { | 	if(sectors_per_track_ == 16) { | ||||||
| 		// Write gap 1. | 		// Write gap 1. | ||||||
| 		segment += Encodings::AppleGCR::six_and_two_sync(16); | 		segment += Encodings::AppleGCR::six_and_two_sync(24); | ||||||
|  |  | ||||||
| 		// Write the sectors. | 		// Write the sectors. | ||||||
| 		for(uint8_t c = 0; c < 16; ++c) { | 		for(uint8_t c = 0; c < 16; ++c) { | ||||||
| 			segment += Encodings::AppleGCR::header(254, track, c); | 			segment += Encodings::AppleGCR::header(is_prodos_ ? 0x01 : 0xfe, track, c);	// Volume number is 0xfe for DOS 3.3, 0x01 for Pro-DOS. | ||||||
| 			segment += Encodings::AppleGCR::six_and_two_sync(7);	// Gap 2: 7 sync words. | 			segment += Encodings::AppleGCR::six_and_two_sync(7);	// Gap 2: 7 sync words. | ||||||
| 			segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]); | 			segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]); | ||||||
| 			segment += Encodings::AppleGCR::six_and_two_sync(16);	// Gap 3: 16 sync words. | 			segment += Encodings::AppleGCR::six_and_two_sync(20);	// Gap 3: 20 sync words. | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		// TODO: 5 and 3, 13-sector format. If DSK actually supports it? | 		// TODO: 5 and 3, 13-sector format. If DSK actually supports it? | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Apply inter-track skew; skew is about 40ms between each track; assuming 300RPM that's | ||||||
|  | 	// 1/5th of a revolution. | ||||||
|  | 	const size_t offset_in_fifths = address.position.as_int() % 5; | ||||||
|  | 	segment.rotate_right(offset_in_fifths * segment.data.size() / 5); | ||||||
|  |  | ||||||
| 	return std::make_shared<PCMTrack>(segment); | 	return std::make_shared<PCMTrack>(segment); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ std::shared_ptr<Track> Storage::Disk::track_for_sectors(uint8_t *const source, i | |||||||
| 		source_pointer += byte_size; | 		source_pointer += byte_size; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(sectors.size()) { | 	if(!sectors.empty()) { | ||||||
| 		return is_double_density ? Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) : Storage::Encodings::MFM::GetFMTrackWithSectors(sectors); | 		return is_double_density ? Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) : Storage::Encodings::MFM::GetFMTrackWithSectors(sectors); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -117,6 +117,7 @@ bool Drive::get_is_ready() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Drive::set_motor_on(bool motor_is_on) { | void Drive::set_motor_on(bool motor_is_on) { | ||||||
|  | 	if(motor_is_on_ != motor_is_on) { | ||||||
| 		motor_is_on_ = motor_is_on; | 		motor_is_on_ = motor_is_on; | ||||||
|  |  | ||||||
| 		if(observer_) { | 		if(observer_) { | ||||||
| @@ -132,6 +133,7 @@ void Drive::set_motor_on(bool motor_is_on) { | |||||||
| 		} | 		} | ||||||
| 		update_clocking_observer(); | 		update_clocking_observer(); | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| bool Drive::get_motor_on() { | bool Drive::get_motor_on() { | ||||||
| 	return motor_is_on_; | 	return motor_is_on_; | ||||||
|   | |||||||
| @@ -50,6 +50,25 @@ PCMSegment &PCMSegment::operator +=(const PCMSegment &rhs) { | |||||||
| 	return *this; | 	return *this; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void PCMSegment::rotate_right(size_t length) { | ||||||
|  | 	length %= data.size(); | ||||||
|  | 	if(!length) return; | ||||||
|  |  | ||||||
|  | 	// To rotate to the right, front-insert the proper number | ||||||
|  | 	// of bits from the end and then resize. To rotate to | ||||||
|  | 	// the left, do the opposite. | ||||||
|  | 	std::vector<uint8_t> data_copy; | ||||||
|  | 	if(length > 0) { | ||||||
|  | 		data_copy.insert(data_copy.end(), data.end() - static_cast<off_t>(length), data.end()); | ||||||
|  | 		data.erase(data.end() - static_cast<off_t>(length), data.end()); | ||||||
|  | 		data.insert(data.begin(), data_copy.begin(), data_copy.end()); | ||||||
|  | 	} else { | ||||||
|  | 		data_copy.insert(data_copy.end(), data.begin(), data.begin() - static_cast<off_t>(length)); | ||||||
|  | 		data.erase(data.begin(), data.begin() - static_cast<off_t>(length)); | ||||||
|  | 		data.insert(data.end(), data_copy.begin(), data_copy.end()); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() { | Storage::Disk::Track::Event PCMSegmentEventSource::get_next_event() { | ||||||
| 	// track the initial bit pointer for potentially considering whether this was an | 	// track the initial bit pointer for potentially considering whether this was an | ||||||
| 	// initial index hole or a subsequent one later on | 	// initial index hole or a subsequent one later on | ||||||
|   | |||||||
| @@ -105,6 +105,13 @@ struct PCMSegment { | |||||||
| 		data.clear(); | 		data.clear(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/*! | ||||||
|  | 		Rotates all bits in this segment by @c length bits. | ||||||
|  |  | ||||||
|  | 		@c length is signed; to rotate left provide a negative number. | ||||||
|  | 	*/ | ||||||
|  | 	void rotate_right(size_t length); | ||||||
|  |  | ||||||
| 	/*! | 	/*! | ||||||
| 		Produces a byte buffer where the contents of @c data are serialised into bytes | 		Produces a byte buffer where the contents of @c data are serialised into bytes | ||||||
|  |  | ||||||
|   | |||||||
| @@ -455,8 +455,7 @@ void TZX::ignore_stop_tape_if_in_48kb_mode() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void TZX::ignore_custom_info_block() { | void TZX::ignore_custom_info_block() { | ||||||
| 	// TODO: enquire about this; the TZX documentation is ambiguous as to whether this is really 10, or 0x10. | 	file_.seek(0x10, SEEK_CUR); | ||||||
| 	file_.seek(10, SEEK_CUR); |  | ||||||
| 	uint32_t length = file_.get32le(); | 	uint32_t length = file_.get32le(); | ||||||
| 	file_.seek(length, SEEK_CUR); | 	file_.seek(length, SEEK_CUR); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ namespace { | |||||||
| const int PLLClockRate = 1920000; | const int PLLClockRate = 1920000; | ||||||
| } | } | ||||||
|  |  | ||||||
| Parser::Parser() { | Parser::Parser(): crc_(0x1021) { | ||||||
| 	shifter_.set_delegate(this); | 	shifter_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ class Parser: public Storage::Tape::Parser<SymbolType>, public Shifter::Delegate | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		bool did_update_shifter(int new_value, int length); | 		bool did_update_shifter(int new_value, int length); | ||||||
| 		CRC::CCITT crc_; | 		CRC::Generator<uint16_t, 0x0000, 0x0000, false, false> crc_; | ||||||
| 		Shifter shifter_; | 		Shifter shifter_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user