mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-30 14:16:04 +00:00 
			
		
		
		
	Compare commits
	
		
			84 Commits
		
	
	
		
			2018-08-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 | ||
|  | bcd0479074 | ||
|  | d72dd8c4ff | ||
|  | c939a274be | 
| @@ -19,7 +19,8 @@ struct Target: public ::Analyser::Static::Target { | |||||||
| 	enum class Model { | 	enum class Model { | ||||||
| 		II, | 		II, | ||||||
| 		IIplus, | 		IIplus, | ||||||
| 		IIe | 		IIe, | ||||||
|  | 		EnhancedIIe | ||||||
| 	}; | 	}; | ||||||
| 	enum class DiskController { | 	enum class DiskController { | ||||||
| 		None, | 		None, | ||||||
|   | |||||||
							
								
								
									
										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 */ | ||||||
| @@ -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" | ||||||
| @@ -34,7 +35,9 @@ | |||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| template <bool is_iie> 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 MediaTarget::Machine, | 	public MediaTarget::Machine, | ||||||
| 	public KeyboardMachine::Machine, | 	public KeyboardMachine::Machine, | ||||||
| @@ -49,20 +52,18 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 			public: | 			public: | ||||||
| 				VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_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); | ||||||
| 				uint16_t perform_aux_read(uint16_t address) { |  | ||||||
| 					return static_cast<uint16_t>(ram_[address] | (aux_ram_[address] << 8)); |  | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			private: | 			private: | ||||||
| 				uint8_t *ram_, *aux_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, is_iie>> 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_; | ||||||
|  |  | ||||||
| @@ -87,6 +88,14 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 		uint8_t keyboard_input_ = 0x00; | 		uint8_t keyboard_input_ = 0x00; | ||||||
| 		bool key_is_down_ = false; | 		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_; | ||||||
| @@ -179,7 +188,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 		bool has_language_card_ = true; | 		bool has_language_card_ = true; | ||||||
| 		void set_language_card_paging() { | 		void set_language_card_paging() { | ||||||
| 			uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; | 			uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_; | ||||||
| 			uint8_t *const rom = is_iie ? &rom_[3840] : rom_.data(); | 			uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data(); | ||||||
|  |  | ||||||
| 			page(0xd0, 0xe0, | 			page(0xd0, 0xe0, | ||||||
| 				language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom, | 				language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom, | ||||||
| @@ -298,7 +307,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | 		ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): | ||||||
| 		 	m6502_(*this), | 			m6502_(*this), | ||||||
| 		 	video_bus_handler_(ram_, aux_ram_), | 		 	video_bus_handler_(ram_, aux_ram_), | ||||||
| 		 	audio_toggle_(audio_queue_), | 		 	audio_toggle_(audio_queue_), | ||||||
| 		 	speaker_(audio_toggle_) { | 		 	speaker_(audio_toggle_) { | ||||||
| @@ -345,6 +354,11 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 					rom_names.push_back("apple2eu-character.rom"); | 					rom_names.push_back("apple2eu-character.rom"); | ||||||
| 					rom_names.push_back("apple2eu.rom"); | 					rom_names.push_back("apple2eu.rom"); | ||||||
| 				break; | 				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); | 			const auto roms = rom_fetcher("AppleII", rom_names); | ||||||
|  |  | ||||||
| @@ -383,7 +397,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void setup_output(float aspect_ratio) override { | 		void setup_output(float aspect_ratio) override { | ||||||
| 			video_.reset(new AppleII::Video::Video<VideoBusHandler, is_iie>(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_); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -420,9 +434,12 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 			bool has_updated_cards = false; | 			bool has_updated_cards = false; | ||||||
| 			if(read_pages_[address >> 8]) { | 			if(read_pages_[address >> 8]) { | ||||||
| 				if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; | 				if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; | ||||||
| 				else if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; | 				else { | ||||||
|  | 					if(address >= 0x200 && address < 0x6000) update_video(); | ||||||
|  | 					if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				if(is_iie && address >= 0xc300 && address < 0xd000) { | 				if(is_iie() && address >= 0xc300 && address < 0xd000) { | ||||||
| 					bool internal_c8_rom = internal_c8_rom_; | 					bool internal_c8_rom = internal_c8_rom_; | ||||||
| 					internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_; | 					internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_; | ||||||
| 					internal_c8_rom &= (address != 0xcfff); | 					internal_c8_rom &= (address != 0xcfff); | ||||||
| @@ -457,18 +474,18 @@ template <bool is_iie> 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( | 									if( | ||||||
| 										static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] || | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] || | ||||||
| 										(is_iie && open_apple_is_pressed_) | 										(is_iie() && open_apple_is_pressed_) | ||||||
| 									) | 									) | ||||||
| 										*value |= 0x80; | 										*value |= 0x80; | ||||||
| 								break; | 								break; | ||||||
| @@ -476,7 +493,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 									*value &= 0x7f; | 									*value &= 0x7f; | ||||||
| 									if( | 									if( | ||||||
| 										static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] || | 										static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] || | ||||||
| 										(is_iie && closed_apple_is_pressed_) | 										(is_iie() && closed_apple_is_pressed_) | ||||||
| 									) | 									) | ||||||
| 										*value |= 0x80; | 										*value |= 0x80; | ||||||
| 								break; | 								break; | ||||||
| @@ -498,28 +515,33 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 								} break; | 								} break; | ||||||
|  |  | ||||||
| 								// The IIe-only state reads follow... | 								// The IIe-only state reads follow... | ||||||
| 								case 0xc011:	if(is_iie) *value = (*value & 0x7f) | (language_card_.bank1 ? 0x80 : 0x00);											break; | #define IIeSwitchRead(s)	*value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); | ||||||
| 								case 0xc012:	if(is_iie) *value = (*value & 0x7f) | (language_card_.read ? 0x80 : 0x00);											break; | 								case 0xc011:	IIeSwitchRead(language_card_.bank1);										break; | ||||||
| 								case 0xc013:	if(is_iie) *value = (*value & 0x7f) | (read_auxiliary_memory_ ? 0x80 : 0x00);										break; | 								case 0xc012:	IIeSwitchRead(language_card_.read);											break; | ||||||
| 								case 0xc014:	if(is_iie) *value = (*value & 0x7f) | (write_auxiliary_memory_ ? 0x80 : 0x00);										break; | 								case 0xc013:	IIeSwitchRead(read_auxiliary_memory_);										break; | ||||||
| 								case 0xc015:	if(is_iie) *value = (*value & 0x7f) | (internal_CX_rom_ ? 0x80 : 0x00);												break; | 								case 0xc014:	IIeSwitchRead(write_auxiliary_memory_);										break; | ||||||
| 								case 0xc016:	if(is_iie) *value = (*value & 0x7f) | (alternative_zero_page_ ? 0x80 : 0x00);										break; | 								case 0xc015:	IIeSwitchRead(internal_CX_rom_);											break; | ||||||
| 								case 0xc017:	if(is_iie) *value = (*value & 0x7f) | (slot_C3_rom_ ? 0x80 : 0x00);													break; | 								case 0xc016:	IIeSwitchRead(alternative_zero_page_);										break; | ||||||
| 								case 0xc018:	if(is_iie) *value = (*value & 0x7f) | (video_->get_80_store() ? 0x80 : 0x00);										break; | 								case 0xc017:	IIeSwitchRead(slot_C3_rom_);												break; | ||||||
| 								case 0xc019:	if(is_iie) *value = (*value & 0x7f) | (video_->get_is_vertical_blank(cycles_since_video_update_) ? 0x00 : 0x80);	break; | 								case 0xc018:	IIeSwitchRead(video_->get_80_store());										break; | ||||||
| 								case 0xc01a:	if(is_iie) *value = (*value & 0x7f) | (video_->get_text() ? 0x80 : 0x00);											break; | 								case 0xc019:	IIeSwitchRead(video_->get_is_vertical_blank(cycles_since_video_update_));	break; | ||||||
| 								case 0xc01b:	if(is_iie) *value = (*value & 0x7f) | (video_->get_mixed() ? 0x80 : 0x00);											break; | 								case 0xc01a:	IIeSwitchRead(video_->get_text());											break; | ||||||
| 								case 0xc01c:	if(is_iie) *value = (*value & 0x7f) | (video_->get_page2() ? 0x80 : 0x00);											break; | 								case 0xc01b:	IIeSwitchRead(video_->get_mixed());											break; | ||||||
| 								case 0xc01d:	if(is_iie) *value = (*value & 0x7f) | (video_->get_high_resolution() ? 0x80 : 0x00);								break; | 								case 0xc01c:	IIeSwitchRead(video_->get_page2());											break; | ||||||
| 								case 0xc01e:	if(is_iie) *value = (*value & 0x7f) | (video_->get_alternative_character_set() ? 0x80 : 0x00);						break; | 								case 0xc01d:	IIeSwitchRead(video_->get_high_resolution());								break; | ||||||
| 								case 0xc01f:	if(is_iie) *value = (*value & 0x7f) | (video_->get_80_columns() ? 0x80 : 0x00);										break; | 								case 0xc01e:	IIeSwitchRead(video_->get_alternative_character_set());						break; | ||||||
| 								case 0xc07f:	if(is_iie) *value = (*value & 0x7f) | (video_->get_double_high_resolution() ? 0x80 : 0x00);							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. All IIe as currently implemented. | 							// Write-only switches. All IIe as currently implemented. | ||||||
| 							if(is_iie) { | 							if(is_iie()) { | ||||||
| 								switch(address) { | 								switch(address) { | ||||||
| 									default: printf("Write %04x?\n", address); break; | 									default: break; | ||||||
|  |  | ||||||
| 									case 0xc000: | 									case 0xc000: | ||||||
| 									case 0xc001: | 									case 0xc001: | ||||||
| @@ -589,7 +611,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 						analogue_charge_ = 0.0f; | 						analogue_charge_ = 0.0f; | ||||||
| 					} break; | 					} break; | ||||||
|  |  | ||||||
| 					/* Read-write switches. */ | 					/* Switches triggered by reading or writing. */ | ||||||
| 					case 0xc050: | 					case 0xc050: | ||||||
| 					case 0xc051: | 					case 0xc051: | ||||||
| 						update_video(); | 						update_video(); | ||||||
| @@ -612,9 +634,9 @@ template <bool is_iie> class ConcreteMachine: | |||||||
|  |  | ||||||
| 					case 0xc05e: | 					case 0xc05e: | ||||||
| 					case 0xc05f: | 					case 0xc05f: | ||||||
| 						if(is_iie) { | 						if(is_iie()) { | ||||||
| 							update_video(); | 							update_video(); | ||||||
| 							video_->set_double_high_resolution(!(address&1)); | 							video_->set_annunciator_3(!(address&1)); | ||||||
| 						} | 						} | ||||||
| 					break; | 					break; | ||||||
|  |  | ||||||
| @@ -626,12 +648,13 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						// On the IIe, reading C010 returns additional key info. | 						// On the IIe, reading C010 returns additional key info. | ||||||
| 						if(is_iie && isReadOperation(operation)) { | 						if(is_iie() && isReadOperation(operation)) { | ||||||
| 							*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f); | 							*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; | ||||||
| @@ -769,7 +792,7 @@ template <bool is_iie> class ConcreteMachine: | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Prior to the IIe, the keyboard could produce uppercase only. | 			// Prior to the IIe, the keyboard could produce uppercase only. | ||||||
| 			if(!is_iie) value = static_cast<char>(toupper(value)); | 			if(!is_iie()) value = static_cast<char>(toupper(value)); | ||||||
|  |  | ||||||
| 			if(is_pressed) { | 			if(is_pressed) { | ||||||
| 				keyboard_input_ = static_cast<uint8_t>(value | 0x80); | 				keyboard_input_ = static_cast<uint8_t>(value | 0x80); | ||||||
| @@ -818,10 +841,12 @@ using namespace AppleII; | |||||||
| Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { | ||||||
| 	using Target = Analyser::Static::AppleII::Target; | 	using Target = Analyser::Static::AppleII::Target; | ||||||
| 	const Target *const appleii_target = dynamic_cast<const Target *>(target); | 	const Target *const appleii_target = dynamic_cast<const Target *>(target); | ||||||
| 	if(appleii_target->model == Target::Model::IIe) { | 	switch(appleii_target->model) { | ||||||
| 		return new ConcreteMachine<true>(*appleii_target, rom_fetcher); | 		default: return nullptr; | ||||||
| 	} else { | 		case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher); | ||||||
| 		return new ConcreteMachine<false>(*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); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,8 +10,10 @@ | |||||||
|  |  | ||||||
| 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. | ||||||
| @@ -23,8 +25,25 @@ VideoBase::VideoBase() : | |||||||
|  |  | ||||||
| 	// 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() { | ||||||
| @@ -35,67 +54,93 @@ Outputs::CRT::CRT *VideoBase::get_crt() { | |||||||
| 	Rote setters and getters. | 	Rote setters and getters. | ||||||
| */ | */ | ||||||
| void VideoBase::set_alternative_character_set(bool alternative_character_set) { | void VideoBase::set_alternative_character_set(bool alternative_character_set) { | ||||||
| 	alternative_character_set_ = 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(); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_alternative_character_set() { | bool VideoBase::get_alternative_character_set() { | ||||||
| 	return alternative_character_set_; | 	return set_alternative_character_set_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_80_columns(bool columns_80) { | void VideoBase::set_80_columns(bool columns_80) { | ||||||
| 	columns_80_ = columns_80; | 	set_columns_80_ = columns_80; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		columns_80_ = columns_80; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_80_columns() { | bool VideoBase::get_80_columns() { | ||||||
| 	return columns_80_; | 	return set_columns_80_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_80_store(bool store_80) { | void VideoBase::set_80_store(bool store_80) { | ||||||
| 	store_80_ = store_80; | 	set_store_80_ = store_80_ = store_80; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_80_store() { | bool VideoBase::get_80_store() { | ||||||
| 	return store_80_; | 	return set_store_80_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_page2(bool page2) { | void VideoBase::set_page2(bool page2) { | ||||||
| 	page2_ = page2; | 	set_page2_ = page2_ = page2; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_page2() { | bool VideoBase::get_page2() { | ||||||
| 	return page2_; | 	return set_page2_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_text(bool text) { | void VideoBase::set_text(bool text) { | ||||||
| 	text_ = text; | 	set_text_ = text; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		text_ = text; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_text() { | bool VideoBase::get_text() { | ||||||
| 	return text_; | 	return set_text_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_mixed(bool mixed) { | void VideoBase::set_mixed(bool mixed) { | ||||||
| 	mixed_ = mixed; | 	set_mixed_ = mixed; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		mixed_ = mixed; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_mixed() { | bool VideoBase::get_mixed() { | ||||||
| 	return mixed_; | 	return set_mixed_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_high_resolution(bool high_resolution) { | void VideoBase::set_high_resolution(bool high_resolution) { | ||||||
| 	high_resolution_ = high_resolution; | 	set_high_resolution_ = high_resolution; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		high_resolution_ = high_resolution; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_high_resolution() { | bool VideoBase::get_high_resolution() { | ||||||
| 	return high_resolution_; | 	return set_high_resolution_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VideoBase::set_double_high_resolution(bool double_high_resolution) { | void VideoBase::set_annunciator_3(bool annunciator_3) { | ||||||
| 	double_high_resolution_ = double_high_resolution; | 	set_annunciator_3_ = annunciator_3; | ||||||
|  | 	deferrer_.defer(Cycles(2), [=] { | ||||||
|  | 		annunciator_3_ = annunciator_3; | ||||||
|  | 		high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff; | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VideoBase::get_double_high_resolution() { | bool VideoBase::get_annunciator_3() { | ||||||
| 	return double_high_resolution_; | 	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) { | ||||||
| @@ -115,3 +160,180 @@ void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | 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 { | ||||||
| @@ -20,25 +22,19 @@ namespace Video { | |||||||
| class BusHandler { | class BusHandler { | ||||||
| 	public: | 	public: | ||||||
| 		/*! | 		/*! | ||||||
| 			Reads an 8-bit value from the ordinary II/II+ memory pool. | 			Requests fetching of the @c count bytes starting from @c address. | ||||||
| 		*/ |  | ||||||
| 		uint8_t perform_read(uint16_t address) { |  | ||||||
| 			return 0xff; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/*! | 			The handler should write the values from base memory to @c base_target, and those | ||||||
| 			Reads two 8-bit values, from the same address — one from | 			from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory, | ||||||
| 			main RAM, one from auxiliary. Should return as | 			it needn't write anything to auxiliary_target. | ||||||
| 			(main) | (aux << 8). |  | ||||||
| 		*/ | 		*/ | ||||||
| 		uint16_t perform_aux_read(uint16_t address) { | 		void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { | ||||||
| 			return 0xffff; |  | ||||||
| 		} | 		} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| 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(); | ||||||
| @@ -132,18 +128,19 @@ class VideoBase { | |||||||
| 		bool get_high_resolution(); | 		bool get_high_resolution(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Setter for DHIRES ($C05E/$C05F; triggers on write only). | 			Setter for annunciator 3. | ||||||
|  |  | ||||||
| 			* On: turn on double-high resolution. | 			* On: turn on annunciator 3. | ||||||
| 			* Off: turn off double-high resolution. | 			* Off: turn off annunciator 3. | ||||||
|  |  | ||||||
| 			DHIRES doesn't exist on a II/II+. On the IIe there is another | 			This exists on both the II/II+ and the IIe, but has no effect on | ||||||
| 			register usually grouped with the graphics setters called IOUDIS | 			video on the older machines. It's intended to be used on the IIe | ||||||
| 			that affects visibility of this switch. But it has no effect on | 			to confirm double-high resolution mode but has side effects in | ||||||
| 			video, so it's not modelled by this class. | 			selecting mixed mode output and discarding high-resolution | ||||||
|  | 			delay bits. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_double_high_resolution(bool); | 		void set_annunciator_3(bool); | ||||||
| 		bool get_double_high_resolution(); | 		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> &); | ||||||
| @@ -153,353 +150,119 @@ class VideoBase { | |||||||
|  |  | ||||||
| 		// State affecting output video stream generation. | 		// 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; |  | ||||||
|  |  | ||||||
| 		// State affecting logical state. | 		// 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. | 		// Enumerates all Apple II and IIe display modes. | ||||||
| 		enum class GraphicsMode { | 		enum class GraphicsMode { | ||||||
| 			LowRes, | 			Text = 0, | ||||||
| 			DoubleLowRes, | 			DoubleText, | ||||||
| 			HighRes, | 			HighRes, | ||||||
| 			DoubleHighRes, | 			DoubleHighRes, | ||||||
| 			Text, | 			LowRes, | ||||||
| 			DoubleText | 			DoubleLowRes, | ||||||
|  | 			FatLowRes | ||||||
| 		}; | 		}; | ||||||
| 		bool is_text_mode(GraphicsMode m) { return m >= GraphicsMode::Text; } | 		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. | 		// Various soft-switch values. | ||||||
| 		bool alternative_character_set_ = false; | 		bool alternative_character_set_ = false, set_alternative_character_set_ = false; | ||||||
| 		bool columns_80_ = false; | 		bool columns_80_ = false, set_columns_80_ = false; | ||||||
| 		bool store_80_ = false; | 		bool store_80_ = false, set_store_80_ = false; | ||||||
| 		bool page2_ = false; | 		bool page2_ = false, set_page2_ = false; | ||||||
| 		bool text_ = true; | 		bool text_ = true, set_text_ = true; | ||||||
| 		bool mixed_ = false; | 		bool mixed_ = false, set_mixed_ = false; | ||||||
| 		bool high_resolution_ = false; | 		bool high_resolution_ = false, set_high_resolution_ = false; | ||||||
| 		bool double_high_resolution_ = false; | 		bool annunciator_3_ = false, set_annunciator_3_ = false; | ||||||
|  |  | ||||||
| 		// Graphics carry is the final level output in a fetch window; | 		// Graphics carry is the final level output in a fetch window; | ||||||
| 		// it carries on into the next if it's high resolution with | 		// it carries on into the next if it's high resolution with | ||||||
| 		// the delay bit set. | 		// the delay bit set. | ||||||
| 		uint8_t graphics_carry_ = 0; | 		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 | 		// This holds a copy of the character ROM. The regular character | ||||||
| 		// set is assumed to be in the first 64*8 bytes; the alternative | 		// set is assumed to be in the first 64*8 bytes; the alternative | ||||||
| 		// is in the 128*8 bytes after that. | 		// is in the 128*8 bytes after that. | ||||||
| 		std::vector<uint8_t> character_rom_; | 		std::vector<uint8_t> character_rom_; | ||||||
|  |  | ||||||
|  | 		// Memory is fetched ahead of time into this array; | ||||||
|  | 		// this permits the correct delay between fetching | ||||||
|  | 		// without having to worry about a rolling buffer. | ||||||
|  | 		std::array<uint8_t, 40> base_stream_; | ||||||
|  | 		std::array<uint8_t, 40> auxiliary_stream_; | ||||||
|  |  | ||||||
|  | 		bool is_iie_ = false; | ||||||
|  | 		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, bool is_iie> 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 = graphics_mode(row_); |  | ||||||
|  |  | ||||||
| 					// 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) { |  | ||||||
| 							const bool requires_high_density = line_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(line_mode) { |  | ||||||
| 								case GraphicsMode::Text: { |  | ||||||
| 									const uint8_t inverses[] = { |  | ||||||
| 										0xff, |  | ||||||
| 										alternative_character_set_ ? static_cast<uint8_t>(0xff) : static_cast<uint8_t>((flash_ / flash_length) * 0xff), |  | ||||||
| 										0x00, |  | ||||||
| 										0x00 |  | ||||||
| 									}; |  | ||||||
| 									const uint8_t masks[] = { |  | ||||||
| 										alternative_character_set_ ? static_cast<uint8_t>(0x7f) : static_cast<uint8_t>(0x3f), |  | ||||||
| 										is_iie ? 0x7f : 0x3f, |  | ||||||
| 									}; |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)); |  | ||||||
| 										const uint8_t xor_mask = inverses[character >> 6]; |  | ||||||
| 										const std::size_t character_address = static_cast<std::size_t>(((character & masks[character >> 7]) << 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. |  | ||||||
| 										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 & 0x01; |  | ||||||
| 										pixel_pointer_ += 7; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::DoubleText: { |  | ||||||
| 									const uint8_t inverses[] = { |  | ||||||
| 										0xff, |  | ||||||
| 										alternative_character_set_ ? static_cast<uint8_t>(0xff) : static_cast<uint8_t>((flash_ / flash_length) * 0xff), |  | ||||||
| 										0x00, |  | ||||||
| 										0x00 |  | ||||||
| 									}; |  | ||||||
| 									const uint8_t masks[] = { |  | ||||||
| 										alternative_character_set_ ? static_cast<uint8_t>(0x7f) : static_cast<uint8_t>(0x3f), |  | ||||||
| 										is_iie ? 0x7f : 0x3f, |  | ||||||
| 									}; |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint16_t characters = bus_handler_.perform_aux_read(static_cast<uint16_t>(text_address + c)); |  | ||||||
| 										const std::size_t character_addresses[2] = { |  | ||||||
| 											static_cast<std::size_t>((((characters >> 8) & masks[characters >> 15]) << 3) + pixel_row), |  | ||||||
| 											static_cast<std::size_t>(((characters & masks[(characters >> 7)&1]) << 3) + pixel_row), |  | ||||||
| 										}; |  | ||||||
|  |  | ||||||
| 										const uint8_t character_patterns[2] = { |  | ||||||
| 											static_cast<uint8_t>(character_rom_[character_addresses[0]] ^ inverses[(characters >> 14) & 3]), |  | ||||||
| 											static_cast<uint8_t>(character_rom_[character_addresses[1]] ^ inverses[(characters >> 6) & 3]), |  | ||||||
| 										}; |  | ||||||
|  |  | ||||||
| 										// The character ROM is output MSB to LSB rather than LSB to MSB. |  | ||||||
| 										pixel_pointer_[0] = character_patterns[0] & 0x40; |  | ||||||
| 										pixel_pointer_[1] = character_patterns[0] & 0x20; |  | ||||||
| 										pixel_pointer_[2] = character_patterns[0] & 0x10; |  | ||||||
| 										pixel_pointer_[3] = character_patterns[0] & 0x08; |  | ||||||
| 										pixel_pointer_[4] = character_patterns[0] & 0x04; |  | ||||||
| 										pixel_pointer_[5] = character_patterns[0] & 0x02; |  | ||||||
| 										pixel_pointer_[6] = character_patterns[0] & 0x01; |  | ||||||
| 										pixel_pointer_[7] = character_patterns[1] & 0x40; |  | ||||||
| 										pixel_pointer_[8] = character_patterns[1] & 0x20; |  | ||||||
| 										pixel_pointer_[9] = character_patterns[1] & 0x10; |  | ||||||
| 										pixel_pointer_[10] = character_patterns[1] & 0x08; |  | ||||||
| 										pixel_pointer_[11] = character_patterns[1] & 0x04; |  | ||||||
| 										pixel_pointer_[12] = character_patterns[1] & 0x02; |  | ||||||
| 										pixel_pointer_[13] = character_patterns[1] & 0x01; |  | ||||||
| 										graphics_carry_ = character_patterns[1] & 0x01; |  | ||||||
| 										pixel_pointer_ += 14; |  | ||||||
| 									} |  | ||||||
| 								} break; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::DoubleLowRes: { |  | ||||||
| 									const int row_shift = (row_&4); |  | ||||||
| 									for(int c = column_; c < pixel_end; ++c) { |  | ||||||
| 										const uint16_t nibble = (bus_handler_.perform_aux_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0xf0f; |  | ||||||
|  |  | ||||||
| 										if(c&1) { |  | ||||||
| 											pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 4; |  | ||||||
| 											pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 8; |  | ||||||
| 											pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 1; |  | ||||||
| 											pixel_pointer_[3] = (nibble >> 8) & 2; |  | ||||||
|  |  | ||||||
| 											pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4; |  | ||||||
| 											pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8; |  | ||||||
| 											pixel_pointer_[10] = nibble & 1; |  | ||||||
| 											pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2; |  | ||||||
| 											graphics_carry_ = nibble & 8; |  | ||||||
| 										} else { |  | ||||||
| 											pixel_pointer_[0] = pixel_pointer_[4] = (nibble >> 8) & 1; |  | ||||||
| 											pixel_pointer_[1] = pixel_pointer_[5] = (nibble >> 8) & 2; |  | ||||||
| 											pixel_pointer_[2] = pixel_pointer_[6] = (nibble >> 8) & 4; |  | ||||||
| 											pixel_pointer_[3] = (nibble >> 8) & 8; |  | ||||||
|  |  | ||||||
| 											pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1; |  | ||||||
| 											pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2; |  | ||||||
| 											pixel_pointer_[10] = nibble & 4; |  | ||||||
| 											pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8; |  | ||||||
| 											graphics_carry_ = nibble & 2; |  | ||||||
| 										} |  | ||||||
| 										pixel_pointer_ += 14; |  | ||||||
| 									} |  | ||||||
| 								} 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; |  | ||||||
|  |  | ||||||
| 								case GraphicsMode::DoubleHighRes: { |  | ||||||
| 									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 uint16_t graphic = bus_handler_.perform_aux_read(static_cast<uint16_t>(graphics_address + c)); |  | ||||||
|  |  | ||||||
| 										pixel_pointer_[0] = graphics_carry_; |  | ||||||
| 										pixel_pointer_[1] = (graphic >> 8) & 0x01; |  | ||||||
| 										pixel_pointer_[2] = (graphic >> 8) & 0x02; |  | ||||||
| 										pixel_pointer_[3] = (graphic >> 8) & 0x04; |  | ||||||
| 										pixel_pointer_[4] = (graphic >> 8) & 0x08; |  | ||||||
| 										pixel_pointer_[5] = (graphic >> 8) & 0x10; |  | ||||||
| 										pixel_pointer_[6] = (graphic >> 8) & 0x20; |  | ||||||
| 										pixel_pointer_[7] = (graphic >> 8) & 0x40; |  | ||||||
| 										pixel_pointer_[8] = graphic & 0x01; |  | ||||||
| 										pixel_pointer_[9] = graphic & 0x02; |  | ||||||
| 										pixel_pointer_[10] = graphic & 0x04; |  | ||||||
| 										pixel_pointer_[11] = graphic & 0x08; |  | ||||||
| 										pixel_pointer_[12] = graphic & 0x10; |  | ||||||
| 										pixel_pointer_[13] = graphic & 0x20; |  | ||||||
| 										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(!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, 128); |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						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); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -537,7 +300,9 @@ template <class BusHandler, bool is_iie> 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; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| @@ -558,15 +323,253 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		GraphicsMode graphics_mode(int row) { | 		/*! | ||||||
| 			if(text_) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; | 			Advances time by @c cycles; expects to be fed by the CPU clock. | ||||||
| 			if(mixed_ && row >= 160 && row < 192) { | 			Implicitly adds an extra half a colour clock at the end of | ||||||
| 				return (columns_80_ || double_high_resolution_) ? GraphicsMode::DoubleText : GraphicsMode::Text; | 			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_) { | 			if(high_resolution_) { | ||||||
| 				return double_high_resolution_ ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; | 				return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; | ||||||
| 			} else { | 			} else { | ||||||
| 				return double_high_resolution_ ? GraphicsMode::DoubleLowRes : GraphicsMode::LowRes; | 				if(columns_80_) return GraphicsMode::DoubleLowRes; | ||||||
|  | 				if(annunciator_3_) return GraphicsMode::FatLowRes; | ||||||
|  | 				return GraphicsMode::LowRes; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -585,13 +588,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase { | |||||||
| 				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; |  | ||||||
| 		} |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -143,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]; | ||||||
|   | |||||||
| @@ -703,7 +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()); | ||||||
| 		} | 		} | ||||||
| 		CPU::MOS6502::Processor<ConcreteMachine, false> m6502_; | 		CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; | ||||||
|  |  | ||||||
| 		std::vector<uint8_t>  character_rom_; | 		std::vector<uint8_t>  character_rom_; | ||||||
| 		std::vector<uint8_t>  basic_rom_; | 		std::vector<uint8_t>  basic_rom_; | ||||||
|   | |||||||
| @@ -541,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]; | ||||||
|   | |||||||
| @@ -575,7 +575,7 @@ 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 | ||||||
| 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | 		std::vector<uint8_t> rom_, microdisc_rom_, colour_rom_; | ||||||
|   | |||||||
| @@ -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 */; }; | ||||||
| @@ -690,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>"; }; | ||||||
| @@ -1005,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>"; }; | ||||||
| @@ -1545,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>"; | ||||||
| @@ -3146,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; | ||||||
| @@ -3330,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 */, | ||||||
|   | |||||||
| @@ -116,6 +116,11 @@ | |||||||
|                                     <action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/> |                                     <action selector="saveScreenshot:" target="-1" id="7ky-xD-tip"/> | ||||||
|                                 </connections> |                                 </connections> | ||||||
|                             </menuItem> |                             </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 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"/> | ||||||
|   | |||||||
| @@ -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) | ||||||
| @@ -266,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?) { | ||||||
| @@ -287,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 | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -295,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 | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -304,6 +321,9 @@ 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 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -39,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. | ||||||
|  |  | ||||||
| @@ -70,6 +71,8 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| @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. | ||||||
|   | |||||||
| @@ -516,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 { | ||||||
|   | |||||||
| @@ -13,7 +13,8 @@ | |||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) { | ||||||
| 	CSMachineAppleIIModelAppleII, | 	CSMachineAppleIIModelAppleII, | ||||||
| 	CSMachineAppleIIModelAppleIIPlus, | 	CSMachineAppleIIModelAppleIIPlus, | ||||||
| 	CSMachineAppleIIModelAppleIIe | 	CSMachineAppleIIModelAppleIIe, | ||||||
|  | 	CSMachineAppleIIModelAppleEnhancedIIe | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | typedef NS_ENUM(NSInteger, CSMachineAppleIIDiskController) { | ||||||
|   | |||||||
| @@ -169,9 +169,10 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| 		std::unique_ptr<Target> target(new Target); | 		std::unique_ptr<Target> target(new Target); | ||||||
| 		target->machine = Analyser::Machine::AppleII; | 		target->machine = Analyser::Machine::AppleII; | ||||||
| 		switch(model) { | 		switch(model) { | ||||||
| 			default: 								target->model = Target::Model::II; 		break; | 			default: 									target->model = Target::Model::II; 				break; | ||||||
| 			case CSMachineAppleIIModelAppleIIPlus:	target->model = Target::Model::IIplus;	break; | 			case CSMachineAppleIIModelAppleIIPlus:		target->model = Target::Model::IIplus;			break; | ||||||
| 			case CSMachineAppleIIModelAppleIIe:		target->model = Target::Model::IIe;		break; | 			case CSMachineAppleIIModelAppleIIe:			target->model = Target::Model::IIe;				break; | ||||||
|  | 			case CSMachineAppleIIModelAppleEnhancedIIe:	target->model = Target::Model::EnhancedIIe;		break; | ||||||
| 		} | 		} | ||||||
| 		switch(diskController) { | 		switch(diskController) { | ||||||
| 			default: | 			default: | ||||||
|   | |||||||
| @@ -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="67" 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"/> | ||||||
| @@ -93,6 +93,7 @@ Gw | |||||||
|                                                         <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="Apple IIe" tag="2" id="AMt-WU-a0H"/> | ||||||
|  |                                                         <menuItem title="Enhanced IIe" tag="3" id="kUz-FG-lqW"/> | ||||||
|                                                     </items> |                                                     </items> | ||||||
|                                                 </menu> |                                                 </menu> | ||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|   | |||||||
| @@ -131,6 +131,7 @@ class MachinePicker: NSObject { | |||||||
| 				switch appleIIModelButton!.selectedTag() { | 				switch appleIIModelButton!.selectedTag() { | ||||||
| 					case 1:		model = .appleIIPlus | 					case 1:		model = .appleIIPlus | ||||||
| 					case 2:		model = .appleIIe | 					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() | 		XCTAssert(error == nil, "Failed with error \(error!)") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 				machine.setData(functionalTest, atAddress: 0) | 	/// Runs Klaus Dorman's 65C02 tests. | ||||||
| 				machine.setValue(0x400, for: .programCounter) | 	func test65C02() { | ||||||
|  | 		func errorForTrapAddress(_ address: UInt16) -> String? { | ||||||
|  | 			switch address { | ||||||
|  | 				case 0x24f1: return nil // success! | ||||||
|  |  | ||||||
| 				while true { | 				case 0x0423: return "PHX: value of X not on stack page" | ||||||
| 					let oldPC = machine.value(for: .lastOperationAddress) | 				case 0x0428: return "PHX: stack pointer not decremented" | ||||||
| 					machine.runForNumber(ofCycles: 1000) | 				case 0x042d: return "PLY: didn't acquire value 0xaa from stack" | ||||||
| 					let newPC = machine.value(for: .lastOperationAddress) | 				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" | ||||||
|  |  | ||||||
| 					if newPC == oldPC { | 				case 0x066a: return "BRA: branch not taken" | ||||||
| 						let error = errorForTrapAddress(oldPC) | 				case 0x0730: return "BBS: branch not taken" | ||||||
| 						XCTAssert(error == nil, "Failed with error \(error!)") | 				case 0x0733: return "BBR: branch taken" | ||||||
| 						return |  | ||||||
| 					} | 				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 = "" | ||||||
|   | |||||||
| @@ -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 | ||||||
| @@ -34,40 +34,62 @@ template <typename T, bool uses_ready_line> void Processor<T, uses_ready_line>:: | |||||||
| 	uint8_t *busValue = bus_value_; | 	uint8_t *busValue = bus_value_; | ||||||
|  |  | ||||||
| #define checkSchedule(op) \ | #define checkSchedule(op) \ | ||||||
| if(!scheduled_program_counter_) {\ | 	if(!scheduled_program_counter_) {\ | ||||||
| if(interrupt_requests_) {\ | 	if(interrupt_requests_) {\ | ||||||
| 	if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\ | 		if(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)) {\ | ||||||
| 		interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\ | 			interrupt_requests_ &= ~InterruptRequestFlags::PowerOn;\ | ||||||
| 		scheduled_program_counter_ = get_reset_program();\ | 			scheduled_program_counter_ = get_reset_program();\ | ||||||
| 	} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ | 		} else if(interrupt_requests_ & InterruptRequestFlags::NMI) {\ | ||||||
| 		interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ | 			interrupt_requests_ &= ~InterruptRequestFlags::NMI;\ | ||||||
| 		scheduled_program_counter_ = get_nmi_program();\ | 			scheduled_program_counter_ = get_nmi_program();\ | ||||||
| 	} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ | 		} else if(interrupt_requests_ & InterruptRequestFlags::IRQ) {\ | ||||||
| 		scheduled_program_counter_ = get_irq_program();\ | 			scheduled_program_counter_ = get_irq_program();\ | ||||||
| 	} \ | 		} \ | ||||||
| } else {\ | 	} else {\ | ||||||
| 	scheduled_program_counter_ = fetch_decode_execute;\ | 		scheduled_program_counter_ = fetch_decode_execute;\ | ||||||
| }\ | 	}\ | ||||||
| op;\ | 		op;\ | ||||||
| } | 	} | ||||||
|  |  | ||||||
| #define bus_access() \ | #define bus_access() \ | ||||||
| interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_;	\ | 	interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_;	\ | ||||||
| irq_request_history_ = irq_line_ & inverse_interrupt_flag_;	\ | 	irq_request_history_ = irq_line_ & inverse_interrupt_flag_;	\ | ||||||
| number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue);	\ | 	number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue);	\ | ||||||
| nextBusOperation = BusOperation::None;	\ | 	nextBusOperation = BusOperation::None;	\ | ||||||
| if(number_of_cycles <= Cycles(0)) break; | 	if(number_of_cycles <= Cycles(0)) break; | ||||||
|  |  | ||||||
| 	checkSchedule(); | 	checkSchedule(); | ||||||
| 	Cycles number_of_cycles = cycles + cycles_left_to_run_; | 	Cycles number_of_cycles = cycles + cycles_left_to_run_; | ||||||
|  |  | ||||||
| 	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: | ||||||
| 						read_mem(operand_, pc_.full); | 						// 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); | ||||||
|  | 							break; | ||||||
|  | 						} else { | ||||||
|  | 							continue; | ||||||
|  | 						} | ||||||
| 					break; | 					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 = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; | 							nextAddress.full = 0xfffe; | ||||||
| 						interrupt_requests_ &= ~InterruptRequestFlags::NMI;	// TODO: this probably doesn't happen now? | 						} else { | ||||||
|  | 							// NMI can usurp BRK-vector operations on the pre-C 6502s. | ||||||
|  | 							nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; | ||||||
|  | 							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,189 +73,315 @@ 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), |  | ||||||
| 	/* 0x01 ORA x, ind */	IndexedIndirectRead(OperationORA), |  | ||||||
| 	/* 0x02 JAM */			JAM,																	/* 0x03 ASO x, ind */	IndexedIndirectReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x04 NOP zpg */		ZeroNop(),																/* 0x05 ORA zpg */		ZeroRead(OperationORA), |  | ||||||
| 	/* 0x06 ASL zpg */		ZeroReadModifyWrite(OperationASL),										/* 0x07 ASO zpg */		ZeroReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x08 PHP */			Program(OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand), |  | ||||||
| 	/* 0x09 ORA # */		Immediate(OperationORA), |  | ||||||
| 	/* 0x0a ASL A */		Implied(OperationASL),													/* 0x0b ANC # */		Immediate(OperationANC), |  | ||||||
| 	/* 0x0c NOP abs */		AbsoluteNop(),															/* 0x0d ORA abs */		AbsoluteRead(OperationORA), |  | ||||||
| 	/* 0x0e ASL abs */		AbsoluteReadModifyWrite(OperationASL),									/* 0x0f ASO abs */		AbsoluteReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x10 BPL */			Program(OperationBPL),													/* 0x11 ORA ind, y */	IndirectIndexedRead(OperationORA), |  | ||||||
| 	/* 0x12 JAM */			JAM,																	/* 0x13 ASO ind, y */	IndirectIndexedReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x14 NOP zpg, x */	ZeroXNop(),																/* 0x15 ORA zpg, x */	ZeroXRead(OperationORA), |  | ||||||
| 	/* 0x16 ASL zpg, x */	ZeroXReadModifyWrite(OperationASL),										/* 0x17 ASO zpg, x */	ZeroXReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x18 CLC */			Program(OperationCLC),													/* 0x19 ORA abs, y */	AbsoluteYRead(OperationORA), |  | ||||||
| 	/* 0x1a NOP # */		ImpliedNop(),															/* 0x1b ASO abs, y */	AbsoluteYReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x1c NOP abs, x */	AbsoluteXNop(),															/* 0x1d ORA abs, x */	AbsoluteXRead(OperationORA), |  | ||||||
| 	/* 0x1e ASL abs, x */	AbsoluteXReadModifyWrite(OperationASL),									/* 0x1f ASO abs, x */	AbsoluteXReadModifyWrite(OperationASO), |  | ||||||
| 	/* 0x20 JSR abs */		Program(CycleIncrementPCAndReadStack, CyclePushPCH, CyclePushPCL, CycleReadPCHLoadPCL), |  | ||||||
| 	/* 0x21 AND x, ind */	IndexedIndirectRead(OperationAND), |  | ||||||
| 	/* 0x22 JAM */			JAM,																	/* 0x23 RLA x, ind */	IndexedIndirectReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x24 BIT zpg */		ZeroRead(OperationBIT),													/* 0x25 AND zpg */		ZeroRead(OperationAND), |  | ||||||
| 	/* 0x26 ROL zpg */		ZeroReadModifyWrite(OperationROL),										/* 0x27 RLA zpg */		ZeroReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x28 PLP */			Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand), |  | ||||||
| 	/* 0x29 AND A # */		Immediate(OperationAND), |  | ||||||
| 	/* 0x2a ROL A */		Implied(OperationROL),													/* 0x2b ANC # */		Immediate(OperationANC), |  | ||||||
| 	/* 0x2c BIT abs */		AbsoluteRead(OperationBIT),												/* 0x2d AND abs */		AbsoluteRead(OperationAND), |  | ||||||
| 	/* 0x2e ROL abs */		AbsoluteReadModifyWrite(OperationROL),									/* 0x2f RLA abs */		AbsoluteReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x30 BMI */			Program(OperationBMI),													/* 0x31 AND ind, y */	IndirectIndexedRead(OperationAND), |  | ||||||
| 	/* 0x32 JAM */			JAM,																	/* 0x33 RLA ind, y */	IndirectIndexedReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x34 NOP zpg, x */	ZeroXNop(),																/* 0x35 AND zpg, x */	ZeroXRead(OperationAND), |  | ||||||
| 	/* 0x36 ROL zpg, x */	ZeroXReadModifyWrite(OperationROL),										/* 0x37 RLA zpg, x */	ZeroXReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x38 SEC */			Program(OperationSEC),													/* 0x39 AND abs, y */	AbsoluteYRead(OperationAND), |  | ||||||
| 	/* 0x3a NOP # */		ImpliedNop(),															/* 0x3b RLA abs, y */	AbsoluteYReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x3c NOP abs, x */	AbsoluteXNop(),															/* 0x3d AND abs, x */	AbsoluteXRead(OperationAND), |  | ||||||
| 	/* 0x3e ROL abs, x */	AbsoluteXReadModifyWrite(OperationROL),									/* 0x3f RLA abs, x */	AbsoluteXReadModifyWrite(OperationRLA), |  | ||||||
| 	/* 0x40 RTI */			Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand, CyclePullPCL, CyclePullPCH), |  | ||||||
| 	/* 0x41 EOR x, ind */	IndexedIndirectRead(OperationEOR), |  | ||||||
| 	/* 0x42 JAM */			JAM,																	/* 0x43 LSE x, ind */	IndexedIndirectReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x44 NOP zpg */		ZeroNop(),																/* 0x45 EOR zpg */		ZeroRead(OperationEOR), |  | ||||||
| 	/* 0x46 LSR zpg */		ZeroReadModifyWrite(OperationLSR),										/* 0x47 LSE zpg */		ZeroReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x48 PHA */			Program(CyclePushA),													/* 0x49 EOR # */		Immediate(OperationEOR), |  | ||||||
| 	/* 0x4a LSR A */		Implied(OperationLSR),													/* 0x4b ASR A */		Immediate(OperationASR), |  | ||||||
| 	/* 0x4c JMP abs */		Program(CycleIncrementPCReadPCHLoadPCL),								/* 0x4d EOR abs */		AbsoluteRead(OperationEOR), |  | ||||||
| 	/* 0x4e LSR abs */		AbsoluteReadModifyWrite(OperationLSR),									/* 0x4f LSE abs */		AbsoluteReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x50 BVC */			Program(OperationBVC),													/* 0x51 EOR ind, y */	IndirectIndexedRead(OperationEOR), |  | ||||||
| 	/* 0x52 JAM */			JAM,																	/* 0x53 LSE ind, y */	IndirectIndexedReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x54 NOP zpg, x */	ZeroXNop(),																/* 0x55 EOR zpg, x */	ZeroXRead(OperationEOR), |  | ||||||
| 	/* 0x56 LSR zpg, x */	ZeroXReadModifyWrite(OperationLSR),										/* 0x57 LSE zpg, x */	ZeroXReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x58 CLI */			Program(OperationCLI),													/* 0x59 EOR abs, y */	AbsoluteYRead(OperationEOR), |  | ||||||
| 	/* 0x5a NOP # */		ImpliedNop(),															/* 0x5b LSE abs, y */	AbsoluteYReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x5c NOP abs, x */	AbsoluteXNop(),															/* 0x5d EOR abs, x */	AbsoluteXRead(OperationEOR), |  | ||||||
| 	/* 0x5e LSR abs, x */	AbsoluteXReadModifyWrite(OperationLSR),									/* 0x5f LSE abs, x */	AbsoluteXReadModifyWrite(OperationLSE), |  | ||||||
| 	/* 0x60 RTS */			Program(CycleReadFromS, CyclePullPCL, CyclePullPCH, CycleReadAndIncrementPC), |  | ||||||
| 	/* 0x61 ADC x, ind */	IndexedIndirectRead(OperationADC), |  | ||||||
| 	/* 0x62 JAM */			JAM,																	/* 0x63 RRA x, ind */	IndexedIndirectReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x64 NOP zpg */		ZeroNop(),																/* 0x65 ADC zpg */		ZeroRead(OperationADC), |  | ||||||
| 	/* 0x66 ROR zpg */		ZeroReadModifyWrite(OperationROR),										/* 0x67 RRA zpg */		ZeroReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x68 PLA */			Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA),			/* 0x69 ADC # */		Immediate(OperationADC), |  | ||||||
| 	/* 0x6a ROR A */		Implied(OperationROR),													/* 0x6b ARR # */		Immediate(OperationARR), |  | ||||||
| 	/* 0x6c JMP (abs) */	Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress), |  | ||||||
| 	/* 0x6d ADC abs */		AbsoluteRead(OperationADC), |  | ||||||
| 	/* 0x6e ROR abs */		AbsoluteReadModifyWrite(OperationROR),									/* 0x6f RRA abs */		AbsoluteReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x70 BVS */			Program(OperationBVS),													/* 0x71 ADC ind, y */	IndirectIndexedRead(OperationADC), |  | ||||||
| 	/* 0x72 JAM */			JAM,																	/* 0x73 RRA ind, y */	IndirectIndexedReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x74 NOP zpg, x */	ZeroXNop(),																/* 0x75 ADC zpg, x */	ZeroXRead(OperationADC), |  | ||||||
| 	/* 0x76 ROR zpg, x */	ZeroXReadModifyWrite(OperationROR),										/* 0x77 RRA zpg, x */	ZeroXReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x78 SEI */			Program(OperationSEI),													/* 0x79 ADC abs, y */	AbsoluteYRead(OperationADC), |  | ||||||
| 	/* 0x7a NOP # */		ImpliedNop(),															/* 0x7b RRA abs, y */	AbsoluteYReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x7c NOP abs, x */	AbsoluteXNop(),															/* 0x7d ADC abs, x */	AbsoluteXRead(OperationADC), |  | ||||||
| 	/* 0x7e ROR abs, x */	AbsoluteXReadModifyWrite(OperationROR),									/* 0x7f RRA abs, x */	AbsoluteXReadModifyWrite(OperationRRA, OperationADC), |  | ||||||
| 	/* 0x80 NOP # */		ImmediateNop(),															/* 0x81 STA x, ind */	IndexedIndirectWrite(OperationSTA), |  | ||||||
| 	/* 0x82 NOP # */		ImmediateNop(),															/* 0x83 SAX x, ind */	IndexedIndirectWrite(OperationSAX), |  | ||||||
| 	/* 0x84 STY zpg */		ZeroWrite(OperationSTY),												/* 0x85 STA zpg */		ZeroWrite(OperationSTA), |  | ||||||
| 	/* 0x86 STX zpg */		ZeroWrite(OperationSTX),												/* 0x87 SAX zpg */		ZeroWrite(OperationSAX), |  | ||||||
| 	/* 0x88 DEY */			Program(OperationDEY),													/* 0x89 NOP # */		ImmediateNop(), |  | ||||||
| 	/* 0x8a TXA */			Program(OperationTXA),													/* 0x8b ANE # */		Immediate(OperationANE), |  | ||||||
| 	/* 0x8c STY abs */		AbsoluteWrite(OperationSTY),											/* 0x8d STA abs */		AbsoluteWrite(OperationSTA), |  | ||||||
| 	/* 0x8e STX abs */		AbsoluteWrite(OperationSTX),											/* 0x8f SAX abs */		AbsoluteWrite(OperationSAX), |  | ||||||
| 	/* 0x90 BCC */			Program(OperationBCC),													/* 0x91 STA ind, y */	IndirectIndexedWrite(OperationSTA), |  | ||||||
| 	/* 0x92 JAM */			JAM,																	/* 0x93 SHA ind, y */	IndirectIndexedWrite(OperationSHA), |  | ||||||
| 	/* 0x94 STY zpg, x */	ZeroXWrite(OperationSTY),												/* 0x95 STA zpg, x */	ZeroXWrite(OperationSTA), |  | ||||||
| 	/* 0x96 STX zpg, y */	ZeroYWrite(OperationSTX),												/* 0x97 SAX zpg, y */	ZeroYWrite(OperationSAX), |  | ||||||
| 	/* 0x98 TYA */			Program(OperationTYA),													/* 0x99 STA abs, y */	AbsoluteYWrite(OperationSTA), |  | ||||||
| 	/* 0x9a TXS */			Program(OperationTXS),													/* 0x9b SHS abs, y */	AbsoluteYWrite(OperationSHS), |  | ||||||
| 	/* 0x9c SHY abs, x */	AbsoluteXWrite(OperationSHY),											/* 0x9d STA abs, x */	AbsoluteXWrite(OperationSTA), |  | ||||||
| 	/* 0x9e SHX abs, y */	AbsoluteYWrite(OperationSHX),											/* 0x9f SHA abs, y */	AbsoluteYWrite(OperationSHA), |  | ||||||
| 	/* 0xa0 LDY # */		Immediate(OperationLDY),												/* 0xa1 LDA x, ind */	IndexedIndirectRead(OperationLDA), |  | ||||||
| 	/* 0xa2 LDX # */		Immediate(OperationLDX),												/* 0xa3 LAX x, ind */	IndexedIndirectRead(OperationLAX), |  | ||||||
| 	/* 0xa4 LDY zpg */		ZeroRead(OperationLDY),													/* 0xa5 LDA zpg */		ZeroRead(OperationLDA), |  | ||||||
| 	/* 0xa6 LDX zpg */		ZeroRead(OperationLDX),													/* 0xa7 LAX zpg */		ZeroRead(OperationLAX), |  | ||||||
| 	/* 0xa8 TAY */			Program(OperationTAY),													/* 0xa9 LDA # */		Immediate(OperationLDA), |  | ||||||
| 	/* 0xaa TAX */			Program(OperationTAX),													/* 0xab LXA # */		Immediate(OperationLXA), |  | ||||||
| 	/* 0xac LDY abs */		AbsoluteRead(OperationLDY),												/* 0xad LDA abs */		AbsoluteRead(OperationLDA), |  | ||||||
| 	/* 0xae LDX abs */		AbsoluteRead(OperationLDX),												/* 0xaf LAX abs */		AbsoluteRead(OperationLAX), |  | ||||||
| 	/* 0xb0 BCS */			Program(OperationBCS),													/* 0xb1 LDA ind, y */	IndirectIndexedRead(OperationLDA), |  | ||||||
| 	/* 0xb2 JAM */			JAM,																	/* 0xb3 LAX ind, y */	IndirectIndexedRead(OperationLAX), |  | ||||||
| 	/* 0xb4 LDY zpg, x */	ZeroXRead(OperationLDY),												/* 0xb5 LDA zpg, x */	ZeroXRead(OperationLDA), |  | ||||||
| 	/* 0xb6 LDX zpg, y */	ZeroYRead(OperationLDX),												/* 0xb7 LAX zpg, x */	ZeroYRead(OperationLAX), |  | ||||||
| 	/* 0xb8 CLV */			Program(OperationCLV),													/* 0xb9 LDA abs, y */	AbsoluteYRead(OperationLDA), |  | ||||||
| 	/* 0xba TSX */			Program(OperationTSX),													/* 0xbb LAS abs, y */	AbsoluteYRead(OperationLAS), |  | ||||||
| 	/* 0xbc LDY abs, x */	AbsoluteXRead(OperationLDY),											/* 0xbd LDA abs, x */	AbsoluteXRead(OperationLDA), |  | ||||||
| 	/* 0xbe LDX abs, y */	AbsoluteYRead(OperationLDX),											/* 0xbf LAX abs, y */	AbsoluteYRead(OperationLAX), |  | ||||||
| 	/* 0xc0 CPY # */		Immediate(OperationCPY),												/* 0xc1 CMP x, ind */	IndexedIndirectRead(OperationCMP), |  | ||||||
| 	/* 0xc2 NOP # */		ImmediateNop(),															/* 0xc3 DCP x, ind */	IndexedIndirectReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xc4 CPY zpg */		ZeroRead(OperationCPY),													/* 0xc5 CMP zpg */		ZeroRead(OperationCMP), |  | ||||||
| 	/* 0xc6 DEC zpg */		ZeroReadModifyWrite(OperationDEC),										/* 0xc7 DCP zpg */		ZeroReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xc8 INY */			Program(OperationINY),													/* 0xc9 CMP # */		Immediate(OperationCMP), |  | ||||||
| 	/* 0xca DEX */			Program(OperationDEX),													/* 0xcb ARR # */		Immediate(OperationSBX), |  | ||||||
| 	/* 0xcc CPY abs */		AbsoluteRead(OperationCPY),												/* 0xcd CMP abs */		AbsoluteRead(OperationCMP), |  | ||||||
| 	/* 0xce DEC abs */		AbsoluteReadModifyWrite(OperationDEC),									/* 0xcf DCP abs */		AbsoluteReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xd0 BNE */			Program(OperationBNE),													/* 0xd1 CMP ind, y */	IndirectIndexedRead(OperationCMP), |  | ||||||
| 	/* 0xd2 JAM */			JAM,																	/* 0xd3 DCP ind, y */	IndirectIndexedReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xd4 NOP zpg, x */	ZeroXNop(),																/* 0xd5 CMP zpg, x */	ZeroXRead(OperationCMP), |  | ||||||
| 	/* 0xd6 DEC zpg, x */	ZeroXReadModifyWrite(OperationDEC),										/* 0xd7 DCP zpg, x */	ZeroXReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xd8 CLD */			Program(OperationCLD),													/* 0xd9 CMP abs, y */	AbsoluteYRead(OperationCMP), |  | ||||||
| 	/* 0xda NOP # */		ImpliedNop(),															/* 0xdb DCP abs, y */	AbsoluteYReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xdc NOP abs, x */	AbsoluteXNop(),															/* 0xdd CMP abs, x */	AbsoluteXRead(OperationCMP), |  | ||||||
| 	/* 0xde DEC abs, x */	AbsoluteXReadModifyWrite(OperationDEC),									/* 0xdf DCP abs, x */	AbsoluteXReadModifyWrite(OperationDecrementOperand, OperationCMP), |  | ||||||
| 	/* 0xe0 CPX # */		Immediate(OperationCPX),												/* 0xe1 SBC x, ind */	IndexedIndirectRead(OperationSBC), |  | ||||||
| 	/* 0xe2 NOP # */		ImmediateNop(),															/* 0xe3 INS x, ind */	IndexedIndirectReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xe4 CPX zpg */		ZeroRead(OperationCPX),													/* 0xe5 SBC zpg */		ZeroRead(OperationSBC), |  | ||||||
| 	/* 0xe6 INC zpg */		ZeroReadModifyWrite(OperationINC),										/* 0xe7 INS zpg */		ZeroReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xe8 INX */			Program(OperationINX),													/* 0xe9 SBC # */		Immediate(OperationSBC), |  | ||||||
| 	/* 0xea NOP # */		ImpliedNop(),															/* 0xeb SBC # */		Immediate(OperationSBC), |  | ||||||
| 	/* 0xec CPX abs */		AbsoluteRead(OperationCPX),												/* 0xed SBC abs */		AbsoluteRead(OperationSBC), |  | ||||||
| 	/* 0xee INC abs */		AbsoluteReadModifyWrite(OperationINC),									/* 0xef INS abs */		AbsoluteReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xf0 BEQ */			Program(OperationBEQ),													/* 0xf1 SBC ind, y */	IndirectIndexedRead(OperationSBC), |  | ||||||
| 	/* 0xf2 JAM */			JAM,																	/* 0xf3 INS ind, y */	IndirectIndexedReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xf4 NOP zpg, x */	ZeroXNop(),																/* 0xf5 SBC zpg, x */	ZeroXRead(OperationSBC), |  | ||||||
| 	/* 0xf6 INC zpg, x */	ZeroXReadModifyWrite(OperationINC),										/* 0xf7 INS zpg, x */	ZeroXReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xf8 SED */			Program(OperationSED),													/* 0xf9 SBC abs, y */	AbsoluteYRead(OperationSBC), |  | ||||||
| 	/* 0xfa NOP # */		ImpliedNop(),															/* 0xfb INS abs, y */	AbsoluteYReadModifyWrite(OperationINS), |  | ||||||
| 	/* 0xfc NOP abs, x */	AbsoluteXNop(),															/* 0xfd SBC abs, x */	AbsoluteXRead(OperationSBC), |  | ||||||
| 	/* 0xfe INC abs, x */	AbsoluteXReadModifyWrite(OperationINC),									/* 0xff INS abs, x */	AbsoluteXReadModifyWrite(OperationINS), |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #undef Program |  | ||||||
| #undef Absolute |  | ||||||
| #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() { |  | ||||||
| 	// only the interrupt flag is defined upon reset but get_flags isn't going to | 	// 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 | 	// mask the other flags so we need to do that, at least | ||||||
| 	carry_flag_ &= Flag::Carry; | 	carry_flag_ &= Flag::Carry; | ||||||
| 	decimal_flag_ &= Flag::Decimal; | 	decimal_flag_ &= Flag::Decimal; | ||||||
| 	overflow_flag_ &= Flag::Overflow; | 	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), | ||||||
|  | 		/* 0x02 JAM */			JAM,																	/* 0x03 ASO x, ind */	IndexedIndirectReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x04 NOP zpg */		ZeroNop(),																/* 0x05 ORA zpg */		ZeroRead(OperationORA), | ||||||
|  | 		/* 0x06 ASL zpg */		ZeroReadModifyWrite(OperationASL),										/* 0x07 ASO zpg */		ZeroReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x08 PHP */			Program(OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand), | ||||||
|  | 		/* 0x09 ORA # */		Immediate(OperationORA), | ||||||
|  | 		/* 0x0a ASL A */		Implied(OperationASL),													/* 0x0b ANC # */		Immediate(OperationANC), | ||||||
|  | 		/* 0x0c NOP abs */		AbsoluteNop(),															/* 0x0d ORA abs */		AbsoluteRead(OperationORA), | ||||||
|  | 		/* 0x0e ASL abs */		AbsoluteReadModifyWrite(OperationASL),									/* 0x0f ASO abs */		AbsoluteReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x10 BPL */			Program(OperationBPL),													/* 0x11 ORA ind, y */	IndirectIndexedRead(OperationORA), | ||||||
|  | 		/* 0x12 JAM */			JAM,																	/* 0x13 ASO ind, y */	IndirectIndexedReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x14 NOP zpg, x */	ZeroXNop(),																/* 0x15 ORA zpg, x */	ZeroXRead(OperationORA), | ||||||
|  | 		/* 0x16 ASL zpg, x */	ZeroXReadModifyWrite(OperationASL),										/* 0x17 ASO zpg, x */	ZeroXReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x18 CLC */			Program(OperationCLC),													/* 0x19 ORA abs, y */	AbsoluteYRead(OperationORA), | ||||||
|  | 		/* 0x1a NOP # */		ImpliedNop(),															/* 0x1b ASO abs, y */	AbsoluteYReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x1c NOP abs, x */	AbsoluteXNop(),															/* 0x1d ORA abs, x */	AbsoluteXRead(OperationORA), | ||||||
|  | 		/* 0x1e ASL abs, x */	AbsoluteXReadModifyWrite(OperationASL),									/* 0x1f ASO abs, x */	AbsoluteXReadModifyWrite(OperationASO), | ||||||
|  | 		/* 0x20 JSR abs */		Program(CycleIncrementPCAndReadStack, CyclePushPCH, CyclePushPCL, CycleReadPCHLoadPCL), | ||||||
|  | 		/* 0x21 AND x, ind */	IndexedIndirectRead(OperationAND), | ||||||
|  | 		/* 0x22 JAM */			JAM,																	/* 0x23 RLA x, ind */	IndexedIndirectReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x24 BIT zpg */		ZeroRead(OperationBIT),													/* 0x25 AND zpg */		ZeroRead(OperationAND), | ||||||
|  | 		/* 0x26 ROL zpg */		ZeroReadModifyWrite(OperationROL),										/* 0x27 RLA zpg */		ZeroReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x28 PLP */			Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand), | ||||||
|  | 		/* 0x29 AND A # */		Immediate(OperationAND), | ||||||
|  | 		/* 0x2a ROL A */		Implied(OperationROL),													/* 0x2b ANC # */		Immediate(OperationANC), | ||||||
|  | 		/* 0x2c BIT abs */		AbsoluteRead(OperationBIT),												/* 0x2d AND abs */		AbsoluteRead(OperationAND), | ||||||
|  | 		/* 0x2e ROL abs */		AbsoluteReadModifyWrite(OperationROL),									/* 0x2f RLA abs */		AbsoluteReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x30 BMI */			Program(OperationBMI),													/* 0x31 AND ind, y */	IndirectIndexedRead(OperationAND), | ||||||
|  | 		/* 0x32 JAM */			JAM,																	/* 0x33 RLA ind, y */	IndirectIndexedReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x34 NOP zpg, x */	ZeroXNop(),																/* 0x35 AND zpg, x */	ZeroXRead(OperationAND), | ||||||
|  | 		/* 0x36 ROL zpg, x */	ZeroXReadModifyWrite(OperationROL),										/* 0x37 RLA zpg, x */	ZeroXReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x38 SEC */			Program(OperationSEC),													/* 0x39 AND abs, y */	AbsoluteYRead(OperationAND), | ||||||
|  | 		/* 0x3a NOP # */		ImpliedNop(),															/* 0x3b RLA abs, y */	AbsoluteYReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x3c NOP abs, x */	AbsoluteXNop(),															/* 0x3d AND abs, x */	AbsoluteXRead(OperationAND), | ||||||
|  | 		/* 0x3e ROL abs, x */	AbsoluteXReadModifyWrite(OperationROL),									/* 0x3f RLA abs, x */	AbsoluteXReadModifyWrite(OperationRLA), | ||||||
|  | 		/* 0x40 RTI */			Program(CycleReadFromS, CyclePullOperand, OperationSetFlagsFromOperand, CyclePullPCL, CyclePullPCH), | ||||||
|  | 		/* 0x41 EOR x, ind */	IndexedIndirectRead(OperationEOR), | ||||||
|  | 		/* 0x42 JAM */			JAM,																	/* 0x43 LSE x, ind */	IndexedIndirectReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x44 NOP zpg */		ZeroNop(),																/* 0x45 EOR zpg */		ZeroRead(OperationEOR), | ||||||
|  | 		/* 0x46 LSR zpg */		ZeroReadModifyWrite(OperationLSR),										/* 0x47 LSE zpg */		ZeroReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x48 PHA */			Program(CyclePushA),													/* 0x49 EOR # */		Immediate(OperationEOR), | ||||||
|  | 		/* 0x4a LSR A */		Implied(OperationLSR),													/* 0x4b ASR A */		Immediate(OperationASR), | ||||||
|  | 		/* 0x4c JMP abs */		Program(CycleIncrementPCReadPCHLoadPCL),								/* 0x4d EOR abs */		AbsoluteRead(OperationEOR), | ||||||
|  | 		/* 0x4e LSR abs */		AbsoluteReadModifyWrite(OperationLSR),									/* 0x4f LSE abs */		AbsoluteReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x50 BVC */			Program(OperationBVC),													/* 0x51 EOR ind, y */	IndirectIndexedRead(OperationEOR), | ||||||
|  | 		/* 0x52 JAM */			JAM,																	/* 0x53 LSE ind, y */	IndirectIndexedReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x54 NOP zpg, x */	ZeroXNop(),																/* 0x55 EOR zpg, x */	ZeroXRead(OperationEOR), | ||||||
|  | 		/* 0x56 LSR zpg, x */	ZeroXReadModifyWrite(OperationLSR),										/* 0x57 LSE zpg, x */	ZeroXReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x58 CLI */			Program(OperationCLI),													/* 0x59 EOR abs, y */	AbsoluteYRead(OperationEOR), | ||||||
|  | 		/* 0x5a NOP # */		ImpliedNop(),															/* 0x5b LSE abs, y */	AbsoluteYReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x5c NOP abs, x */	AbsoluteXNop(),															/* 0x5d EOR abs, x */	AbsoluteXRead(OperationEOR), | ||||||
|  | 		/* 0x5e LSR abs, x */	AbsoluteXReadModifyWrite(OperationLSR),									/* 0x5f LSE abs, x */	AbsoluteXReadModifyWrite(OperationLSE), | ||||||
|  | 		/* 0x60 RTS */			Program(CycleReadFromS, CyclePullPCL, CyclePullPCH, CycleReadAndIncrementPC), | ||||||
|  | 		/* 0x61 ADC x, ind */	IndexedIndirectRead(OperationADC), | ||||||
|  | 		/* 0x62 JAM */			JAM,																	/* 0x63 RRA x, ind */	IndexedIndirectReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x64 NOP zpg */		ZeroNop(),																/* 0x65 ADC zpg */		ZeroRead(OperationADC), | ||||||
|  | 		/* 0x66 ROR zpg */		ZeroReadModifyWrite(OperationROR),										/* 0x67 RRA zpg */		ZeroReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x68 PLA */			Program(CycleReadFromS, CyclePullA, OperationSetFlagsFromA),			/* 0x69 ADC # */		Immediate(OperationADC), | ||||||
|  | 		/* 0x6a ROR A */		Implied(OperationROR),													/* 0x6b ARR # */		Immediate(OperationARR), | ||||||
|  | 		/* 0x6c JMP (abs) */	Program(CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddressLowInc), | ||||||
|  | 		/* 0x6d ADC abs */		AbsoluteRead(OperationADC), | ||||||
|  | 		/* 0x6e ROR abs */		AbsoluteReadModifyWrite(OperationROR),									/* 0x6f RRA abs */		AbsoluteReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x70 BVS */			Program(OperationBVS),													/* 0x71 ADC ind, y */	IndirectIndexedRead(OperationADC), | ||||||
|  | 		/* 0x72 JAM */			JAM,																	/* 0x73 RRA ind, y */	IndirectIndexedReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x74 NOP zpg, x */	ZeroXNop(),																/* 0x75 ADC zpg, x */	ZeroXRead(OperationADC), | ||||||
|  | 		/* 0x76 ROR zpg, x */	ZeroXReadModifyWrite(OperationROR),										/* 0x77 RRA zpg, x */	ZeroXReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x78 SEI */			Program(OperationSEI),													/* 0x79 ADC abs, y */	AbsoluteYRead(OperationADC), | ||||||
|  | 		/* 0x7a NOP # */		ImpliedNop(),															/* 0x7b RRA abs, y */	AbsoluteYReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x7c NOP abs, x */	AbsoluteXNop(),															/* 0x7d ADC abs, x */	AbsoluteXRead(OperationADC), | ||||||
|  | 		/* 0x7e ROR abs, x */	AbsoluteXReadModifyWrite(OperationROR),									/* 0x7f RRA abs, x */	AbsoluteXReadModifyWrite(OperationRRA, OperationADC), | ||||||
|  | 		/* 0x80 NOP # */		ImmediateNop(),															/* 0x81 STA x, ind */	IndexedIndirectWrite(OperationSTA), | ||||||
|  | 		/* 0x82 NOP # */		ImmediateNop(),															/* 0x83 SAX x, ind */	IndexedIndirectWrite(OperationSAX), | ||||||
|  | 		/* 0x84 STY zpg */		ZeroWrite(OperationSTY),												/* 0x85 STA zpg */		ZeroWrite(OperationSTA), | ||||||
|  | 		/* 0x86 STX zpg */		ZeroWrite(OperationSTX),												/* 0x87 SAX zpg */		ZeroWrite(OperationSAX), | ||||||
|  | 		/* 0x88 DEY */			Program(OperationDEY),													/* 0x89 NOP # */		ImmediateNop(), | ||||||
|  | 		/* 0x8a TXA */			Program(OperationTXA),													/* 0x8b ANE # */		Immediate(OperationANE), | ||||||
|  | 		/* 0x8c STY abs */		AbsoluteWrite(OperationSTY),											/* 0x8d STA abs */		AbsoluteWrite(OperationSTA), | ||||||
|  | 		/* 0x8e STX abs */		AbsoluteWrite(OperationSTX),											/* 0x8f SAX abs */		AbsoluteWrite(OperationSAX), | ||||||
|  | 		/* 0x90 BCC */			Program(OperationBCC),													/* 0x91 STA ind, y */	IndirectIndexedWrite(OperationSTA), | ||||||
|  | 		/* 0x92 JAM */			JAM,																	/* 0x93 SHA ind, y */	IndirectIndexedWrite(OperationSHA), | ||||||
|  | 		/* 0x94 STY zpg, x */	ZeroXWrite(OperationSTY),												/* 0x95 STA zpg, x */	ZeroXWrite(OperationSTA), | ||||||
|  | 		/* 0x96 STX zpg, y */	ZeroYWrite(OperationSTX),												/* 0x97 SAX zpg, y */	ZeroYWrite(OperationSAX), | ||||||
|  | 		/* 0x98 TYA */			Program(OperationTYA),													/* 0x99 STA abs, y */	AbsoluteYWrite(OperationSTA), | ||||||
|  | 		/* 0x9a TXS */			Program(OperationTXS),													/* 0x9b SHS abs, y */	AbsoluteYWrite(OperationSHS), | ||||||
|  | 		/* 0x9c SHY abs, x */	AbsoluteXWrite(OperationSHY),											/* 0x9d STA abs, x */	AbsoluteXWrite(OperationSTA), | ||||||
|  | 		/* 0x9e SHX abs, y */	AbsoluteYWrite(OperationSHX),											/* 0x9f SHA abs, y */	AbsoluteYWrite(OperationSHA), | ||||||
|  | 		/* 0xa0 LDY # */		Immediate(OperationLDY),												/* 0xa1 LDA x, ind */	IndexedIndirectRead(OperationLDA), | ||||||
|  | 		/* 0xa2 LDX # */		Immediate(OperationLDX),												/* 0xa3 LAX x, ind */	IndexedIndirectRead(OperationLAX), | ||||||
|  | 		/* 0xa4 LDY zpg */		ZeroRead(OperationLDY),													/* 0xa5 LDA zpg */		ZeroRead(OperationLDA), | ||||||
|  | 		/* 0xa6 LDX zpg */		ZeroRead(OperationLDX),													/* 0xa7 LAX zpg */		ZeroRead(OperationLAX), | ||||||
|  | 		/* 0xa8 TAY */			Program(OperationTAY),													/* 0xa9 LDA # */		Immediate(OperationLDA), | ||||||
|  | 		/* 0xaa TAX */			Program(OperationTAX),													/* 0xab LXA # */		Immediate(OperationLXA), | ||||||
|  | 		/* 0xac LDY abs */		AbsoluteRead(OperationLDY),												/* 0xad LDA abs */		AbsoluteRead(OperationLDA), | ||||||
|  | 		/* 0xae LDX abs */		AbsoluteRead(OperationLDX),												/* 0xaf LAX abs */		AbsoluteRead(OperationLAX), | ||||||
|  | 		/* 0xb0 BCS */			Program(OperationBCS),													/* 0xb1 LDA ind, y */	IndirectIndexedRead(OperationLDA), | ||||||
|  | 		/* 0xb2 JAM */			JAM,																	/* 0xb3 LAX ind, y */	IndirectIndexedRead(OperationLAX), | ||||||
|  | 		/* 0xb4 LDY zpg, x */	ZeroXRead(OperationLDY),												/* 0xb5 LDA zpg, x */	ZeroXRead(OperationLDA), | ||||||
|  | 		/* 0xb6 LDX zpg, y */	ZeroYRead(OperationLDX),												/* 0xb7 LAX zpg, x */	ZeroYRead(OperationLAX), | ||||||
|  | 		/* 0xb8 CLV */			Program(OperationCLV),													/* 0xb9 LDA abs, y */	AbsoluteYRead(OperationLDA), | ||||||
|  | 		/* 0xba TSX */			Program(OperationTSX),													/* 0xbb LAS abs, y */	AbsoluteYRead(OperationLAS), | ||||||
|  | 		/* 0xbc LDY abs, x */	AbsoluteXRead(OperationLDY),											/* 0xbd LDA abs, x */	AbsoluteXRead(OperationLDA), | ||||||
|  | 		/* 0xbe LDX abs, y */	AbsoluteYRead(OperationLDX),											/* 0xbf LAX abs, y */	AbsoluteYRead(OperationLAX), | ||||||
|  | 		/* 0xc0 CPY # */		Immediate(OperationCPY),												/* 0xc1 CMP x, ind */	IndexedIndirectRead(OperationCMP), | ||||||
|  | 		/* 0xc2 NOP # */		ImmediateNop(),															/* 0xc3 DCP x, ind */	IndexedIndirectReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xc4 CPY zpg */		ZeroRead(OperationCPY),													/* 0xc5 CMP zpg */		ZeroRead(OperationCMP), | ||||||
|  | 		/* 0xc6 DEC zpg */		ZeroReadModifyWrite(OperationDEC),										/* 0xc7 DCP zpg */		ZeroReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xc8 INY */			Program(OperationINY),													/* 0xc9 CMP # */		Immediate(OperationCMP), | ||||||
|  | 		/* 0xca DEX */			Program(OperationDEX),													/* 0xcb ARR # */		Immediate(OperationSBX), | ||||||
|  | 		/* 0xcc CPY abs */		AbsoluteRead(OperationCPY),												/* 0xcd CMP abs */		AbsoluteRead(OperationCMP), | ||||||
|  | 		/* 0xce DEC abs */		AbsoluteReadModifyWrite(OperationDEC),									/* 0xcf DCP abs */		AbsoluteReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xd0 BNE */			Program(OperationBNE),													/* 0xd1 CMP ind, y */	IndirectIndexedRead(OperationCMP), | ||||||
|  | 		/* 0xd2 JAM */			JAM,																	/* 0xd3 DCP ind, y */	IndirectIndexedReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xd4 NOP zpg, x */	ZeroXNop(),																/* 0xd5 CMP zpg, x */	ZeroXRead(OperationCMP), | ||||||
|  | 		/* 0xd6 DEC zpg, x */	ZeroXReadModifyWrite(OperationDEC),										/* 0xd7 DCP zpg, x */	ZeroXReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xd8 CLD */			Program(OperationCLD),													/* 0xd9 CMP abs, y */	AbsoluteYRead(OperationCMP), | ||||||
|  | 		/* 0xda NOP # */		ImpliedNop(),															/* 0xdb DCP abs, y */	AbsoluteYReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xdc NOP abs, x */	AbsoluteXNop(),															/* 0xdd CMP abs, x */	AbsoluteXRead(OperationCMP), | ||||||
|  | 		/* 0xde DEC abs, x */	AbsoluteXReadModifyWrite(OperationDEC),									/* 0xdf DCP abs, x */	AbsoluteXReadModifyWrite(OperationDecrementOperand, OperationCMP), | ||||||
|  | 		/* 0xe0 CPX # */		Immediate(OperationCPX),												/* 0xe1 SBC x, ind */	IndexedIndirectRead(OperationSBC), | ||||||
|  | 		/* 0xe2 NOP # */		ImmediateNop(),															/* 0xe3 INS x, ind */	IndexedIndirectReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xe4 CPX zpg */		ZeroRead(OperationCPX),													/* 0xe5 SBC zpg */		ZeroRead(OperationSBC), | ||||||
|  | 		/* 0xe6 INC zpg */		ZeroReadModifyWrite(OperationINC),										/* 0xe7 INS zpg */		ZeroReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xe8 INX */			Program(OperationINX),													/* 0xe9 SBC # */		Immediate(OperationSBC), | ||||||
|  | 		/* 0xea NOP # */		ImpliedNop(),															/* 0xeb SBC # */		Immediate(OperationSBC), | ||||||
|  | 		/* 0xec CPX abs */		AbsoluteRead(OperationCPX),												/* 0xed SBC abs */		AbsoluteRead(OperationSBC), | ||||||
|  | 		/* 0xee INC abs */		AbsoluteReadModifyWrite(OperationINC),									/* 0xef INS abs */		AbsoluteReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xf0 BEQ */			Program(OperationBEQ),													/* 0xf1 SBC ind, y */	IndirectIndexedRead(OperationSBC), | ||||||
|  | 		/* 0xf2 JAM */			JAM,																	/* 0xf3 INS ind, y */	IndirectIndexedReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xf4 NOP zpg, x */	ZeroXNop(),																/* 0xf5 SBC zpg, x */	ZeroXRead(OperationSBC), | ||||||
|  | 		/* 0xf6 INC zpg, x */	ZeroXReadModifyWrite(OperationINC),										/* 0xf7 INS zpg, x */	ZeroXReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xf8 SED */			Program(OperationSED),													/* 0xf9 SBC abs, y */	AbsoluteYRead(OperationSBC), | ||||||
|  | 		/* 0xfa NOP # */		ImpliedNop(),															/* 0xfb INS abs, y */	AbsoluteYReadModifyWrite(OperationINS), | ||||||
|  | 		/* 0xfc NOP abs, x */	AbsoluteXNop(),															/* 0xfd SBC abs, x */	AbsoluteXRead(OperationSBC), | ||||||
|  | 		/* 0xfe INC abs, x */	AbsoluteXReadModifyWrite(OperationINC),									/* 0xff INS abs, x */	AbsoluteXReadModifyWrite(OperationINS), | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Install the basic 6502 table. | ||||||
|  | 	memcpy(operations_, operations_6502, sizeof(operations_)); | ||||||
|  |  | ||||||
|  | 	// Patch the table according to the chip's personality. | ||||||
|  | 	// | ||||||
|  | 	// The 6502 and NES 6502 both have the same mapping of operation codes to actions | ||||||
|  | 	// (respect for the decimal mode flag aside); included in that are 'unofficial' | ||||||
|  | 	// operations — spots that are not formally defined to do anything but which the | ||||||
|  | 	// 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; | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -9,5 +9,6 @@ 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. | 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. | 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. | 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); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user