mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 20:16:07 +00:00 
			
		
		
		
	Compare commits
	
		
			138 Commits
		
	
	
		
			2019-08-01
			...
			2019-09-28
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b609ce6fcb | ||
|  | 929475d31e | ||
|  | f14d98452e | ||
|  | 9d17d48bca | ||
|  | 4ac3839185 | ||
|  | c089d1cd09 | ||
|  | cb85ec25cc | ||
|  | fbf95ec2b8 | ||
|  | 6adca98f34 | ||
|  | 48f4d8b875 | ||
|  | 7758f9d0a9 | ||
|  | 7112f0336c | ||
|  | 298694a881 | ||
|  | 7ff4594f09 | ||
|  | e8bd538182 | ||
|  | 8489e8650f | ||
|  | 114f81941e | ||
|  | 077c7d767f | ||
|  | 8f88addf9f | ||
|  | f28c124039 | ||
|  | a416bc0058 | ||
|  | e78b1dcf3c | ||
|  | 8a14f5d814 | ||
|  | e5f983fbac | ||
|  | 3e639e96e7 | ||
|  | 61993f0687 | ||
|  | 5f16fa8c08 | ||
|  | dcea9c9ab2 | ||
|  | e7bf0799b6 | ||
|  | e760421f6f | ||
|  | 8ea4c17315 | ||
|  | 2e24da4614 | ||
|  | e46601872b | ||
|  | 6d0e41b760 | ||
|  | 5a82df837d | ||
|  | 776b819a5a | ||
|  | 1783f6c84b | ||
|  | 2ef2c73efe | ||
|  | 55e003ccc1 | ||
|  | 3d54d55dbb | ||
|  | 72c0a631f7 | ||
|  | 1608a90d5d | ||
|  | 4f8a45a6ce | ||
|  | 4f0f1dcf18 | ||
|  | 839e51d92d | ||
|  | e470cf23d8 | ||
|  | 8d4a96683a | ||
|  | f53411a319 | ||
|  | 128a1da626 | ||
|  | 962275c22a | ||
|  | 3002ac8a4a | ||
|  | ff43674638 | ||
|  | 2f6c366668 | ||
|  | 2ce1f0a3b1 | ||
|  | 210129c3a1 | ||
|  | 934901447a | ||
|  | 960b289e70 | ||
|  | 243e40cd79 | ||
|  | c849188016 | ||
|  | 87e8dade2f | ||
|  | 6fc5b4e825 | ||
|  | 00ce7f8ae0 | ||
|  | 6e0e9afe2f | ||
|  | cb0d994827 | ||
|  | bee782234a | ||
|  | 64dad35026 | ||
|  | cbd1a8cf78 | ||
|  | a4ab0afce3 | ||
|  | 1c7e0f3c9d | ||
|  | 318cdb41ea | ||
|  | 2f8e31bc8b | ||
|  | 310c722cc0 | ||
|  | 25956bd90f | ||
|  | 1a60ced61b | ||
|  | 081316c071 | ||
|  | eafbc12cc1 | ||
|  | ca08716c52 | ||
|  | 30cef1ee22 | ||
|  | 5598802439 | ||
|  | 1c6720b0db | ||
|  | 404b088199 | ||
|  | 7d61df238a | ||
|  | c86db12f1c | ||
|  | ce2e85af8b | ||
|  | 2d82855f26 | ||
|  | faec516a2c | ||
|  | 8e274ec5d0 | ||
|  | bb1a0a0b76 | ||
|  | 252650808d | ||
|  | e3d9254555 | ||
|  | 90cf99b626 | ||
|  | 955e909e61 | ||
|  | 8339e2044c | ||
|  | 0e0c789b02 | ||
|  | 7e001c1d03 | ||
|  | 9047932b81 | ||
|  | f668e4a54c | ||
|  | ce1c96d68c | ||
|  | 0f67e490e8 | ||
|  | 895c315fa5 | ||
|  | a90a74a512 | ||
|  | 3e1286cbef | ||
|  | 949c1e1668 | ||
|  | bbd4e4d3dc | ||
|  | 4c5f596533 | ||
|  | 4859d3781b | ||
|  | bac0461f7f | ||
|  | f26a200d78 | ||
|  | 28ccb7b54e | ||
|  | b6e4c8209b | ||
|  | 16548f0765 | ||
|  | 6a80832140 | ||
|  | c6cf0e914b | ||
|  | 35b1a55c12 | ||
|  | e3794c0c0e | ||
|  | f88dc23c71 | ||
|  | 0e293e4983 | ||
|  | e334abfe20 | ||
|  | fd2fbe0e59 | ||
|  | 330b27d085 | ||
|  | 478f2533b5 | ||
|  | b96972a4b9 | ||
|  | f2b083f4de | ||
|  | 80f6d665d9 | ||
|  | a07488cf1b | ||
|  | d67c5145c0 | ||
|  | 5e76d593af | ||
|  | 83393e8e91 | ||
|  | e08a64d455 | ||
|  | b93f9b3973 | ||
|  | 9c517d07d4 | ||
|  | f45de5b87a | ||
|  | 011d76175c | ||
|  | 96005261c7 | ||
|  | c8177af45a | ||
|  | 97eff5b16d | ||
|  | 917520fb1e | ||
|  | 335dda3d55 | 
| @@ -10,10 +10,10 @@ | |||||||
| #include "Target.hpp" | #include "Target.hpp" | ||||||
|  |  | ||||||
| Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { | ||||||
| 	// This analyser can comprehend disks only. | 	// This analyser can comprehend disks and mass-storage devices only. | ||||||
| 	if(media.disks.empty()) return {}; | 	if(media.disks.empty() && media.mass_storage_devices.empty()) return {}; | ||||||
|  |  | ||||||
| 	// If there is at least one disk, wave it through. | 	// As there is at least one usable media image, wave it through. | ||||||
| 	Analyser::Static::TargetList targets; | 	Analyser::Static::TargetList targets; | ||||||
|  |  | ||||||
| 	using Target = Analyser::Static::Macintosh::Target; | 	using Target = Analyser::Static::Macintosh::Target; | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target { | |||||||
| 		MacPlus | 		MacPlus | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Model model = Model::Mac512ke; | 	Model model = Model::MacPlus; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -46,6 +46,9 @@ | |||||||
| #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" | ||||||
| #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" | #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" | ||||||
|  |  | ||||||
|  | // Mass Storage Devices (i.e. usually, hard disks) | ||||||
|  | #include "../../Storage/MassStorage/Formats/HFV.hpp" | ||||||
|  |  | ||||||
| // Tapes | // Tapes | ||||||
| #include "../../Storage/Tape/Formats/CAS.hpp" | #include "../../Storage/Tape/Formats/CAS.hpp" | ||||||
| #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" | #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" | ||||||
| @@ -102,7 +105,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: | |||||||
| 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD | 	Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn)				// DSD | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC)		// DSK (Amstrad CPC) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII)			// DSK (Apple II) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh)	// DSK (Macintosh, floppy disk) | ||||||
|  | 	Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh)						// DSK (Macintosh, hard disk) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX)				// DSK (MSX) | ||||||
| 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric) | 	Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric)			// DSK (Oric) | ||||||
| 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64 | 	Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore)			// G64 | ||||||
| @@ -160,7 +164,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) { | |||||||
| TargetList Analyser::Static::GetTargets(const std::string &file_name) { | TargetList Analyser::Static::GetTargets(const std::string &file_name) { | ||||||
| 	TargetList targets; | 	TargetList targets; | ||||||
|  |  | ||||||
| 	// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the | 	// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the | ||||||
| 	// union of all platforms this file might be a target for. | 	// union of all platforms this file might be a target for. | ||||||
| 	TargetPlatform::IntType potential_platforms = 0; | 	TargetPlatform::IntType potential_platforms = 0; | ||||||
| 	Media media = GetMediaAndPlatforms(file_name, potential_platforms); | 	Media media = GetMediaAndPlatforms(file_name, potential_platforms); | ||||||
|   | |||||||
| @@ -11,9 +11,10 @@ | |||||||
|  |  | ||||||
| #include "../Machines.hpp" | #include "../Machines.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Tape/Tape.hpp" |  | ||||||
| #include "../../Storage/Disk/Disk.hpp" |  | ||||||
| #include "../../Storage/Cartridge/Cartridge.hpp" | #include "../../Storage/Cartridge/Cartridge.hpp" | ||||||
|  | #include "../../Storage/Disk/Disk.hpp" | ||||||
|  | #include "../../Storage/MassStorage/MassStorageDevice.hpp" | ||||||
|  | #include "../../Storage/Tape/Tape.hpp" | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| @@ -29,9 +30,10 @@ struct Media { | |||||||
| 	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks; | 	std::vector<std::shared_ptr<Storage::Disk::Disk>> disks; | ||||||
| 	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes; | 	std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes; | ||||||
| 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | 	std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges; | ||||||
|  | 	std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices; | ||||||
|  |  | ||||||
| 	bool empty() const { | 	bool empty() const { | ||||||
| 		return disks.empty() && tapes.empty() && cartridges.empty(); | 		return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ | |||||||
| 	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a | 	Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a | ||||||
| 	separate @c TargetTimeScale at template declaration. | 	separate @c TargetTimeScale at template declaration. | ||||||
| */ | */ | ||||||
| template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor { | template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs a new JustInTimeActor using the same construction arguments as the included object. | 		/// Constructs a new JustInTimeActor using the same construction arguments as the included object. | ||||||
| 		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {} | 		template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {} | ||||||
| @@ -60,7 +60,7 @@ template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> | |||||||
| 	Any time the amount of accumulated time crosses a threshold provided at construction time, | 	Any time the amount of accumulated time crosses a threshold provided at construction time, | ||||||
| 	the object will be updated on the AsyncTaskQueue. | 	the object will be updated on the AsyncTaskQueue. | ||||||
| */ | */ | ||||||
| template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor { | template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor { | ||||||
| 	public: | 	public: | ||||||
| 		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object. | 		/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object. | ||||||
| 		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) : | 		template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) : | ||||||
|   | |||||||
							
								
								
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								Components/5380/ncr5380.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | |||||||
|  | // | ||||||
|  | //  ncr5380.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "ncr5380.hpp" | ||||||
|  |  | ||||||
|  | #include "../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | using namespace NCR::NCR5380; | ||||||
|  | using SCSI::Line; | ||||||
|  |  | ||||||
|  | NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) : | ||||||
|  | 	bus_(bus), | ||||||
|  | 	clock_rate_(clock_rate) { | ||||||
|  | 	device_id_ = bus_.add_device(); | ||||||
|  | 	bus_.add_observer(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) { | ||||||
|  | 	switch(address & 7) { | ||||||
|  | 		case 0: | ||||||
|  | //			LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value)); | ||||||
|  | 			data_bus_ = value; | ||||||
|  |  | ||||||
|  | 			if(dma_request_ && dma_operation_ == DMAOperation::Send) { | ||||||
|  | //				printf("w %02x\n", value); | ||||||
|  | 				dma_acknowledge_ = true; | ||||||
|  | 				dma_request_ = false; | ||||||
|  | 				update_control_output(); | ||||||
|  | 				bus_.set_device_output(device_id_, bus_output_); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 1: { | ||||||
|  | //			LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value)); | ||||||
|  | 			initiator_command_ = value; | ||||||
|  |  | ||||||
|  | 			bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention); | ||||||
|  | 			if(value & 0x80) bus_output_ |= Line::Reset; | ||||||
|  | 			if(value & 0x08) bus_output_ |= Line::Busy; | ||||||
|  | 			if(value & 0x04) bus_output_ |= Line::SelectTarget; | ||||||
|  |  | ||||||
|  | 			/* bit 5 = differential enable if this were a 5381 */ | ||||||
|  |  | ||||||
|  | 			test_mode_ = value & 0x40; | ||||||
|  | 			assert_data_bus_ = value & 0x01; | ||||||
|  | 			update_control_output(); | ||||||
|  | 		} break; | ||||||
|  |  | ||||||
|  | 		case 2: | ||||||
|  | //			LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value)); | ||||||
|  | 			mode_ = value; | ||||||
|  |  | ||||||
|  | 			// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled) | ||||||
|  | 			// bit 6: 1 = be a SCSI target; 0 = be an initiator | ||||||
|  | 			// bit 5: 1 = check parity | ||||||
|  | 			// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found | ||||||
|  | 			// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller | ||||||
|  | 			// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs | ||||||
|  | 			// bit 1: 1 = use DMA mode | ||||||
|  | 			// bit 0: 1 = begin arbitration mode (device ID should be in register 0) | ||||||
|  | 			arbitration_in_progress_ = false; | ||||||
|  | 			switch(mode_ & 0x3) { | ||||||
|  | 				case 0x0: | ||||||
|  | 					bus_output_ &= ~SCSI::Line::Busy; | ||||||
|  | 					dma_request_ = false; | ||||||
|  | 					set_execution_state(ExecutionState::None); | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 0x1: | ||||||
|  | 					arbitration_in_progress_ = true; | ||||||
|  | 					set_execution_state(ExecutionState::WaitingForBusy); | ||||||
|  | 					lost_arbitration_ = false; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				default: | ||||||
|  | 					assert_data_bus_ = false; | ||||||
|  | 					set_execution_state(ExecutionState::PerformingDMA); | ||||||
|  | 					bus_.update_observers(); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			update_control_output(); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 3: { | ||||||
|  | //			LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value)); | ||||||
|  | 			target_command_ = value; | ||||||
|  | 			update_control_output(); | ||||||
|  | 		} break; | ||||||
|  |  | ||||||
|  | 		case 4: | ||||||
|  | //			LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value)); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 5: | ||||||
|  | //			LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value)); | ||||||
|  | 			dma_operation_ = DMAOperation::Send; | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 6: | ||||||
|  | //			LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value)); | ||||||
|  | 			dma_operation_ = DMAOperation::TargetReceive; | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case 7: | ||||||
|  | //			LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value)); | ||||||
|  | 			dma_operation_ = DMAOperation::InitiatorReceive; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Data is output only if the data bus is asserted. | ||||||
|  | 	if(assert_data_bus_) { | ||||||
|  | 		bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_; | ||||||
|  | 	} else { | ||||||
|  | 		bus_output_ &= ~SCSI::Line::Data; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// In test mode, still nothing is output. Otherwise throw out | ||||||
|  | 	// the current value of bus_output_. | ||||||
|  | 	if(test_mode_) { | ||||||
|  | 		bus_.set_device_output(device_id_, SCSI::DefaultBusState); | ||||||
|  | 	} else { | ||||||
|  | 		bus_.set_device_output(device_id_, bus_output_); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t NCR5380::read(int address, bool dma_acknowledge) { | ||||||
|  | 	switch(address & 7) { | ||||||
|  | 		case 0: | ||||||
|  | //			LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff)); | ||||||
|  |  | ||||||
|  | 			if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) { | ||||||
|  | 				dma_acknowledge_ = true; | ||||||
|  | 				dma_request_ = false; | ||||||
|  | 				update_control_output(); | ||||||
|  | 				bus_.set_device_output(device_id_, bus_output_); | ||||||
|  | 			} | ||||||
|  | 		return uint8_t(bus_.get_state()); | ||||||
|  |  | ||||||
|  | 		case 1: | ||||||
|  | //			LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') <<  (lost_arbitration_ ? 'l' : '-')); | ||||||
|  | 		return | ||||||
|  | 			// Bits repeated as they were set. | ||||||
|  | 			(initiator_command_ & ~0x60) | | ||||||
|  |  | ||||||
|  | 			// Arbitration in progress. | ||||||
|  | 			(arbitration_in_progress_ ? 0x40 : 0x00) | | ||||||
|  |  | ||||||
|  | 			// Lost arbitration. | ||||||
|  | 			(lost_arbitration_ ? 0x20 : 0x00); | ||||||
|  |  | ||||||
|  | 		case 2: | ||||||
|  | //			LOG("[SCSI 2] Get mode"); | ||||||
|  | 		return mode_; | ||||||
|  |  | ||||||
|  | 		case 3: | ||||||
|  | //			LOG("[SCSI 3] Get target command"); | ||||||
|  | 		return target_command_; | ||||||
|  |  | ||||||
|  | 		case 4: { | ||||||
|  | 			const auto bus_state = bus_.get_state(); | ||||||
|  | 			const uint8_t result = | ||||||
|  | 				((bus_state & Line::Reset)			? 0x80 : 0x00) | | ||||||
|  | 				((bus_state & Line::Busy)			? 0x40 : 0x00) | | ||||||
|  | 				((bus_state & Line::Request)		? 0x20 : 0x00) | | ||||||
|  | 				((bus_state & Line::Message)		? 0x10 : 0x00) | | ||||||
|  | 				((bus_state & Line::Control)		? 0x08 : 0x00) | | ||||||
|  | 				((bus_state & Line::Input)			? 0x04 : 0x00) | | ||||||
|  | 				((bus_state & Line::SelectTarget)	? 0x02 : 0x00) | | ||||||
|  | 				((bus_state & Line::Parity)			? 0x01 : 0x00); | ||||||
|  | //			LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result)); | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		case 5: { | ||||||
|  | 			const auto bus_state = bus_.get_state(); | ||||||
|  | 			const bool phase_matches = | ||||||
|  | 				(target_output() & (Line::Message | Line::Control | Line::Input)) == | ||||||
|  | 				(bus_state & (Line::Message | Line::Control | Line::Input)); | ||||||
|  |  | ||||||
|  | 			const uint8_t result = | ||||||
|  | 				/* b7 = end of DMA */ | ||||||
|  | 				((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00)	| | ||||||
|  | 				/* b5 = parity error */ | ||||||
|  | 				/* b4 = IRQ active */ | ||||||
|  | 				(phase_matches ? 0x08 : 0x00)	| | ||||||
|  | 				/* b2 = busy error */ | ||||||
|  | 				((bus_state & Line::Attention) ? 0x02 : 0x00) | | ||||||
|  | 				((bus_state & Line::Acknowledge) ? 0x01 : 0x00); | ||||||
|  | //			LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result)); | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		case 6: | ||||||
|  | //			LOG("[SCSI 6] Get input data"); | ||||||
|  | 		return 0xff; | ||||||
|  |  | ||||||
|  | 		case 7: | ||||||
|  | //			LOG("[SCSI 7] Reset parity/interrupt"); | ||||||
|  | 		return 0xff; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SCSI::BusState NCR5380::target_output() { | ||||||
|  | 	SCSI::BusState output = SCSI::DefaultBusState; | ||||||
|  | 	if(target_command_ & 0x08) output |= Line::Request; | ||||||
|  | 	if(target_command_ & 0x04) output |= Line::Message; | ||||||
|  | 	if(target_command_ & 0x02) output |= Line::Control; | ||||||
|  | 	if(target_command_ & 0x01) output |= Line::Input; | ||||||
|  | 	return output; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void NCR5380::update_control_output() { | ||||||
|  | 	bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention); | ||||||
|  | 	if(mode_ & 0x40) { | ||||||
|  | 		// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus. | ||||||
|  | 		bus_output_ |= target_output(); | ||||||
|  | 	} else { | ||||||
|  | 		// This is an initiator; /ATN and /ACK are signalled on the bus. | ||||||
|  | 		if( | ||||||
|  | 			(initiator_command_ & 0x10) || | ||||||
|  | 			(state_ == ExecutionState::PerformingDMA && dma_acknowledge_) | ||||||
|  | 		) bus_output_ |= Line::Acknowledge; | ||||||
|  | 		if(initiator_command_ & 0x02) bus_output_ |= Line::Attention; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) { | ||||||
|  | 	switch(state_) { | ||||||
|  | 		default: break; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			Official documentation: | ||||||
|  |  | ||||||
|  | 				Arbitration is accomplished using a bus-free filter to continuously monitor BSY. | ||||||
|  | 				If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free | ||||||
|  | 				and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive | ||||||
|  | 				and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun | ||||||
|  | 				(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus | ||||||
|  | 				can be examined to deter- mine if arbitration has been won. This delay must be | ||||||
|  | 				implemented in the controlling software driver. | ||||||
|  |  | ||||||
|  | 			Personal notes: | ||||||
|  |  | ||||||
|  | 				I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs | ||||||
|  | 				to happen is: | ||||||
|  |  | ||||||
|  | 					(i) wait for BSY to be inactive; | ||||||
|  | 					(ii) count 400 nsec; | ||||||
|  | 					(iii) check that BSY and SEL are inactive. | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
|  | 		case ExecutionState::WaitingForBusy: | ||||||
|  | 			if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return; | ||||||
|  | 			state_ = ExecutionState::WatchingBusy; | ||||||
|  |  | ||||||
|  | 		case ExecutionState::WatchingBusy: | ||||||
|  | 			if(!(new_state & SCSI::Line::Busy)) { | ||||||
|  | 				lost_arbitration_ = true; | ||||||
|  | 				set_execution_state(ExecutionState::None); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Check for having hit 400ns (more or less) since BSY was inactive. | ||||||
|  | 			if(time_since_change >= SCSI::BusSettleDelay) { | ||||||
|  | //				arbitration_in_progress_ = false; | ||||||
|  | 				if(new_state & SCSI::Line::SelectTarget) { | ||||||
|  | 					lost_arbitration_ = true; | ||||||
|  | 					set_execution_state(ExecutionState::None); | ||||||
|  | 				} else { | ||||||
|  | 					bus_output_ &= ~SCSI::Line::Busy; | ||||||
|  | 					set_execution_state(ExecutionState::None); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */ | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case ExecutionState::PerformingDMA: | ||||||
|  | 			if(time_since_change < SCSI::DeskewDelay) return; | ||||||
|  |  | ||||||
|  | 			// Signal a DMA request if the request line is active, i.e. meaningful data is | ||||||
|  | 			// on the bus, and this device hasn't yet acknowledged it. | ||||||
|  | 			switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) { | ||||||
|  | 				case 0: | ||||||
|  | 					dma_request_ = false; | ||||||
|  | 				break; | ||||||
|  | 				case SCSI::Line::Request: | ||||||
|  | 					dma_request_ = true; | ||||||
|  | 				break; | ||||||
|  | 				case SCSI::Line::Request | SCSI::Line::Acknowledge: | ||||||
|  | 					dma_request_ = false; | ||||||
|  | 				break; | ||||||
|  | 				case SCSI::Line::Acknowledge: | ||||||
|  | 					dma_acknowledge_ = false; | ||||||
|  | 					dma_request_ = false; | ||||||
|  | 					update_control_output(); | ||||||
|  | 					bus_.set_device_output(device_id_, bus_output_); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void NCR5380::set_execution_state(ExecutionState state) { | ||||||
|  | 	state_ = state; | ||||||
|  | 	if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready; | ||||||
|  | } | ||||||
							
								
								
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Components/5380/ncr5380.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | // | ||||||
|  | //  ncr5380.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 10/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef ncr5380_hpp | ||||||
|  | #define ncr5380_hpp | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | #include "../../Storage/MassStorage/SCSI/SCSI.hpp" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | namespace NCR { | ||||||
|  | namespace NCR5380 { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models the NCR 5380, a SCSI interface chip. | ||||||
|  | */ | ||||||
|  | class NCR5380 final: public SCSI::Bus::Observer { | ||||||
|  | 	public: | ||||||
|  | 		NCR5380(SCSI::Bus &bus, int clock_rate); | ||||||
|  |  | ||||||
|  | 		/*! Writes @c value to @c address.  */ | ||||||
|  | 		void write(int address, uint8_t value, bool dma_acknowledge = false); | ||||||
|  |  | ||||||
|  | 		/*! Reads from @c address. */ | ||||||
|  | 		uint8_t read(int address, bool dma_acknowledge = false); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		SCSI::Bus &bus_; | ||||||
|  |  | ||||||
|  | 		const int clock_rate_; | ||||||
|  | 		size_t device_id_; | ||||||
|  |  | ||||||
|  | 		SCSI::BusState bus_output_ = SCSI::DefaultBusState; | ||||||
|  | 		SCSI::BusState expected_phase_ = SCSI::DefaultBusState; | ||||||
|  | 		uint8_t mode_ = 0xff; | ||||||
|  | 		uint8_t initiator_command_ = 0xff; | ||||||
|  | 		uint8_t data_bus_ = 0xff; | ||||||
|  | 		uint8_t target_command_ = 0xff; | ||||||
|  | 		bool test_mode_ = false; | ||||||
|  | 		bool assert_data_bus_ = false; | ||||||
|  | 		bool dma_request_ = false; | ||||||
|  | 		bool dma_acknowledge_ = false; | ||||||
|  |  | ||||||
|  | 		enum class ExecutionState { | ||||||
|  | 			None, | ||||||
|  | 			WaitingForBusy, | ||||||
|  | 			WatchingBusy, | ||||||
|  | 			PerformingDMA, | ||||||
|  | 		} state_ = ExecutionState::None; | ||||||
|  | 		enum class DMAOperation { | ||||||
|  | 			Ready, | ||||||
|  | 			Send, | ||||||
|  | 			TargetReceive, | ||||||
|  | 			InitiatorReceive | ||||||
|  | 		} dma_operation_ = DMAOperation::Ready; | ||||||
|  | 		bool lost_arbitration_ = false, arbitration_in_progress_ = false; | ||||||
|  |  | ||||||
|  | 		void set_execution_state(ExecutionState state); | ||||||
|  |  | ||||||
|  | 		SCSI::BusState target_output(); | ||||||
|  | 		void update_control_output(); | ||||||
|  |  | ||||||
|  | 		void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* ncr5380_hpp */ | ||||||
| @@ -24,7 +24,7 @@ class BusHandler { | |||||||
| 		virtual void set_interrupt(bool irq) {} | 		virtual void set_interrupt(bool irq) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class i8272: public Storage::Disk::MFMController { | class i8272 : public Storage::Disk::MFMController { | ||||||
| 	public: | 	public: | ||||||
| 		i8272(BusHandler &bus_handler, Cycles clock_rate); | 		i8272(BusHandler &bus_handler, Cycles clock_rate); | ||||||
|  |  | ||||||
| @@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController { | |||||||
| 		void set_dma_acknowledge(bool dack); | 		void set_dma_acknowledge(bool dack); | ||||||
| 		void set_terminal_count(bool tc); | 		void set_terminal_count(bool tc); | ||||||
|  |  | ||||||
| 		ClockingHint::Preference preferred_clocking() override; | 		ClockingHint::Preference preferred_clocking() final; | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		virtual void select_drive(int number) = 0; | 		virtual void select_drive(int number) = 0; | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ class Base { | |||||||
| 			// (though, in practice, it won't happen until the next | 			// (though, in practice, it won't happen until the next | ||||||
| 			// external slot after this number of cycles after the | 			// external slot after this number of cycles after the | ||||||
| 			// device has requested the read or write). | 			// device has requested the read or write). | ||||||
| 			return 7; | 			return 6; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Holds the main status register. | 		// Holds the main status register. | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ namespace Apple { | |||||||
| /*! | /*! | ||||||
| 	Provides an emulation of the Apple Disk II. | 	Provides an emulation of the Apple Disk II. | ||||||
| */ | */ | ||||||
| class DiskII: | class DiskII final: | ||||||
| 	public Storage::Disk::Drive::EventDelegate, | 	public Storage::Disk::Drive::EventDelegate, | ||||||
| 	public ClockingHint::Source, | 	public ClockingHint::Source, | ||||||
| 	public ClockingHint::Observer { | 	public ClockingHint::Observer { | ||||||
| @@ -76,7 +76,7 @@ class DiskII: | |||||||
| 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | 		void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive); | ||||||
|  |  | ||||||
| 		// As per Sleeper. | 		// As per Sleeper. | ||||||
| 		ClockingHint::Preference preferred_clocking() override; | 		ClockingHint::Preference preferred_clocking() final; | ||||||
|  |  | ||||||
| 		// The Disk II functions as a potential target for @c Activity::Sources. | 		// The Disk II functions as a potential target for @c Activity::Sources. | ||||||
| 		void set_activity_observer(Activity::Observer *observer); | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|   | |||||||
| @@ -235,23 +235,32 @@ void IWM::run_for(const Cycles cycles) { | |||||||
| 	// Activity otherwise depends on mode and motor state. | 	// Activity otherwise depends on mode and motor state. | ||||||
| 	int integer_cycles = cycles.as_int(); | 	int integer_cycles = cycles.as_int(); | ||||||
| 	switch(shift_mode_) { | 	switch(shift_mode_) { | ||||||
| 		case ShiftMode::Reading: | 		case ShiftMode::Reading: { | ||||||
|  | 			// Per the IWM patent, column 7, around line 35 onwards: "The expected time | ||||||
|  | 			// is widened by approximately one-half an interval before and after the | ||||||
|  | 			// expected time since the data is not precisely spaced when read due to | ||||||
|  | 			// variations in drive speed and other external factors". The error_margin | ||||||
|  | 			// here implements the 'after' part of that contract. | ||||||
|  | 			const auto error_margin = Cycles(bit_length_.as_int() >> 1); | ||||||
|  |  | ||||||
| 			if(drive_is_rotating_[active_drive_]) { | 			if(drive_is_rotating_[active_drive_]) { | ||||||
| 				while(integer_cycles--) { | 				while(integer_cycles--) { | ||||||
| 					drives_[active_drive_]->run_for(Cycles(1)); | 					drives_[active_drive_]->run_for(Cycles(1)); | ||||||
| 					++cycles_since_shift_; | 					++cycles_since_shift_; | ||||||
| 					if(cycles_since_shift_ == bit_length_ + Cycles(2)) { | 					if(cycles_since_shift_ == bit_length_ + error_margin) { | ||||||
| 						propose_shift(0); | 						propose_shift(0); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) { | 				while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) { | ||||||
|  | 					const auto run_length = bit_length_ + error_margin - cycles_since_shift_; | ||||||
|  | 					integer_cycles -= run_length.as_int(); | ||||||
|  | 					cycles_since_shift_ += run_length; | ||||||
| 					propose_shift(0); | 					propose_shift(0); | ||||||
| 					integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int(); |  | ||||||
| 				} | 				} | ||||||
| 				cycles_since_shift_ += Cycles(integer_cycles); | 				cycles_since_shift_ += Cycles(integer_cycles); | ||||||
| 			} | 			} | ||||||
| 		break; | 		} break; | ||||||
|  |  | ||||||
| 		case ShiftMode::Writing: | 		case ShiftMode::Writing: | ||||||
| 			if(drives_[active_drive_]->is_writing()) { | 			if(drives_[active_drive_]->is_writing()) { | ||||||
| @@ -351,12 +360,28 @@ void IWM::propose_shift(uint8_t bit) { | |||||||
| 	// TODO: synchronous mode. | 	// TODO: synchronous mode. | ||||||
|  |  | ||||||
| //	LOG("Shifting input"); | //	LOG("Shifting input"); | ||||||
|  |  | ||||||
|  | 	// See above for text from the IWM patent, column 7, around line 35 onwards. | ||||||
|  | 	// The error_margin here implements the 'before' part of that contract. | ||||||
|  | 	// | ||||||
|  | 	// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred | ||||||
|  | 	// on the current expected bit delivery time as implied by cycles_since_shift_, | ||||||
|  | 	// shift in a 1 and start a new window wherever the first found 1 was. | ||||||
|  | 	// | ||||||
|  | 	// If no 1s are found, shift in a 0 and don't alter expectations as to window placement. | ||||||
|  | 	const auto error_margin = Cycles(bit_length_.as_int() >> 1); | ||||||
|  | 	if(bit && cycles_since_shift_ < error_margin) return; | ||||||
|  |  | ||||||
| 	shift_register_ = uint8_t((shift_register_ << 1) | bit); | 	shift_register_ = uint8_t((shift_register_ << 1) | bit); | ||||||
| 	if(shift_register_ & 0x80) { | 	if(shift_register_ & 0x80) { | ||||||
| 		data_register_ = shift_register_; | 		data_register_ = shift_register_; | ||||||
| 		shift_register_ = 0; | 		shift_register_ = 0; | ||||||
| 	} | 	} | ||||||
| 	cycles_since_shift_ = Cycles(0); |  | ||||||
|  | 	if(bit) | ||||||
|  | 		cycles_since_shift_ = Cycles(0); | ||||||
|  | 	else | ||||||
|  | 		cycles_since_shift_ -= bit_length_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void IWM::set_drive(int slot, IWMDrive *drive) { | void IWM::set_drive(int slot, IWMDrive *drive) { | ||||||
| @@ -374,3 +399,8 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki | |||||||
| 		drive_is_rotating_[1] = is_rotating; | 		drive_is_rotating_[1] = is_rotating; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void IWM::set_activity_observer(Activity::Observer *observer) { | ||||||
|  | 	if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true); | ||||||
|  | 	if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,8 +9,11 @@ | |||||||
| #ifndef IWM_hpp | #ifndef IWM_hpp | ||||||
| #define IWM_hpp | #define IWM_hpp | ||||||
|  |  | ||||||
|  | #include "../../Activity/Observer.hpp" | ||||||
|  |  | ||||||
| #include "../../ClockReceiver/ClockReceiver.hpp" | #include "../../ClockReceiver/ClockReceiver.hpp" | ||||||
| #include "../../ClockReceiver/ClockingHintSource.hpp" | #include "../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  |  | ||||||
| #include "../../Storage/Disk/Drive.hpp" | #include "../../Storage/Disk/Drive.hpp" | ||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| @@ -67,6 +70,10 @@ class IWM: | |||||||
| 		/// Connects a drive to the IWM. | 		/// Connects a drive to the IWM. | ||||||
| 		void set_drive(int slot, IWMDrive *drive); | 		void set_drive(int slot, IWMDrive *drive); | ||||||
|  |  | ||||||
|  | 		/// Registers the currently-connected drives as @c Activity::Sources ; | ||||||
|  | 		/// the first will be declared 'Internal', the second 'External'. | ||||||
|  | 		void set_activity_observer(Activity::Observer *observer); | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		// Storage::Disk::Drive::EventDelegate. | 		// Storage::Disk::Drive::EventDelegate. | ||||||
| 		void process_event(const Storage::Disk::Drive::Event &event) override; | 		void process_event(const Storage::Disk::Drive::Event &event) override; | ||||||
|   | |||||||
| @@ -71,7 +71,9 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) { | |||||||
|  |  | ||||||
| // MARK: - Control input/output. | // MARK: - Control input/output. | ||||||
|  |  | ||||||
| void DoubleDensityDrive::set_enabled(bool) { | void DoubleDensityDrive::set_enabled(bool enabled) { | ||||||
|  | 	// Disabling a drive also stops its motor. | ||||||
|  | 	if(!enabled) set_motor_on(false); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DoubleDensityDrive::set_control_lines(int lines) { | void DoubleDensityDrive::set_control_lines(int lines) { | ||||||
|   | |||||||
| @@ -21,9 +21,9 @@ void append_bool(Configurable::SelectionSet &selection_set, const std::string &n | |||||||
| 	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. | 	Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. | ||||||
| */ | */ | ||||||
| bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { | bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { | ||||||
| 	auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload"); | 	auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name); | ||||||
| 	if(!quickload) return false; | 	if(!selection) return false; | ||||||
| 	result = quickload->value; | 	result = selection->value; | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -42,6 +42,7 @@ std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_option | |||||||
| 		options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); | 		options.emplace_back(new Configurable::ListOption("Display", "display", display_options)); | ||||||
| 	} | 	} | ||||||
| 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | 	if(mask & AutomaticTapeMotorControl)	options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor")); | ||||||
|  | 	if(mask & QuickBoot)					options.emplace_back(new Configurable::BooleanOption("Boot Quickly", "quickboot")); | ||||||
| 	return options; | 	return options; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -66,6 +67,10 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio | |||||||
| 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection)); | 	selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) { | ||||||
|  | 	append_bool(selection_set, "quickboot", selection); | ||||||
|  | } | ||||||
|  |  | ||||||
| // MARK: - Selection parsers | // MARK: - Selection parsers | ||||||
| bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) { | bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) { | ||||||
| 	return get_bool(selections_by_option, "quickload", result); | 	return get_bool(selections_by_option, "quickload", result); | ||||||
| @@ -97,3 +102,7 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o | |||||||
| 	} | 	} | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) { | ||||||
|  | 	return get_bool(selections_by_option, "quickboot", result); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -19,7 +19,8 @@ enum StandardOptions { | |||||||
| 	DisplayCompositeColour		= (1 << 2), | 	DisplayCompositeColour		= (1 << 2), | ||||||
| 	DisplayCompositeMonochrome	= (1 << 3), | 	DisplayCompositeMonochrome	= (1 << 3), | ||||||
| 	QuickLoadTape				= (1 << 4), | 	QuickLoadTape				= (1 << 4), | ||||||
| 	AutomaticTapeMotorControl	= (1 << 5) | 	AutomaticTapeMotorControl	= (1 << 5), | ||||||
|  | 	QuickBoot					= (1 << 6), | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class Display { | enum class Display { | ||||||
| @@ -49,6 +50,11 @@ void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, | |||||||
| */ | */ | ||||||
| void append_display_selection(SelectionSet &selection_set, Display selection); | void append_display_selection(SelectionSet &selection_set, Display selection); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Appends to @c selection_set a selection of @c selection for QuickBoot. | ||||||
|  | */ | ||||||
|  | void append_quick_boot_selection(SelectionSet &selection_set, bool selection); | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Attempts to discern a QuickLoadTape selection from @c selections_by_option. | 	Attempts to discern a QuickLoadTape selection from @c selections_by_option. | ||||||
|   |   | ||||||
| @@ -76,6 +82,15 @@ bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_b | |||||||
| */ | */ | ||||||
| bool get_display(const SelectionSet &selections_by_option, Display &result); | bool get_display(const SelectionSet &selections_by_option, Display &result); | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option. | ||||||
|  |  | ||||||
|  | 	@param selections_by_option The user selections. | ||||||
|  | 	@param result The location to which the selection will be stored if found. | ||||||
|  | 	@returns @c true if a selection is found; @c false otherwise. | ||||||
|  | */ | ||||||
|  | bool get_quick_boot(const SelectionSet &selections_by_option, bool &result); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif /* StandardOptions_hpp */ | #endif /* StandardOptions_hpp */ | ||||||
|   | |||||||
| @@ -10,13 +10,14 @@ | |||||||
|  |  | ||||||
| using namespace Inputs; | using namespace Inputs; | ||||||
|  |  | ||||||
| Keyboard::Keyboard() { | Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) { | ||||||
| 	for(int k = 0; k < int(Key::Help); ++k) { | 	for(int k = 0; k < int(Key::Help); ++k) { | ||||||
| 		observed_keys_.insert(Key(k)); | 		observed_keys_.insert(Key(k)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {} | Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) : | ||||||
|  | 	observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {} | ||||||
|  |  | ||||||
| void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) { | void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) { | ||||||
| 	std::size_t key_offset = static_cast<std::size_t>(key); | 	std::size_t key_offset = static_cast<std::size_t>(key); | ||||||
| @@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) { | |||||||
| 	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed); | 	if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() { | ||||||
|  | 	return essential_modifiers_; | ||||||
|  | } | ||||||
|  |  | ||||||
| void Keyboard::reset_all_keys() { | void Keyboard::reset_all_keys() { | ||||||
| 	std::fill(key_states_.begin(), key_states_.end(), false); | 	std::fill(key_states_.begin(), key_states_.end(), false); | ||||||
| 	if(delegate_) delegate_->reset_all_keys(this); | 	if(delegate_) delegate_->reset_all_keys(this); | ||||||
|   | |||||||
| @@ -23,8 +23,8 @@ class Keyboard { | |||||||
| 	public: | 	public: | ||||||
| 		enum class Key { | 		enum class Key { | ||||||
| 			Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause, | 			Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause, | ||||||
| 			BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace, | 			BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, Backspace, | ||||||
| 			Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash, | 			Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, Backslash, | ||||||
| 			CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter, | 			CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter, | ||||||
| 			LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift, | 			LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift, | ||||||
| 			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl, | 			LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl, | ||||||
| @@ -39,10 +39,10 @@ class Keyboard { | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		/// Constructs a Keyboard that declares itself to observe all keys. | 		/// Constructs a Keyboard that declares itself to observe all keys. | ||||||
| 		Keyboard(); | 		Keyboard(const std::set<Key> &essential_modifiers = {}); | ||||||
|  |  | ||||||
| 		/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys. | 		/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys. | ||||||
| 		Keyboard(const std::set<Key> &observed_keys); | 		Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers); | ||||||
|  |  | ||||||
| 		// Host interface. | 		// Host interface. | ||||||
| 		virtual void set_key_pressed(Key key, char value, bool is_pressed); | 		virtual void set_key_pressed(Key key, char value, bool is_pressed); | ||||||
| @@ -51,10 +51,18 @@ class Keyboard { | |||||||
| 		/// @returns a set of all Keys that this keyboard responds to. | 		/// @returns a set of all Keys that this keyboard responds to. | ||||||
| 		virtual const std::set<Key> &observed_keys(); | 		virtual const std::set<Key> &observed_keys(); | ||||||
|  |  | ||||||
| 		/* | 		/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used). | ||||||
|  | 		virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
| 			@returns @c true if this keyboard, on its original machine, looked | 			@returns @c true if this keyboard, on its original machine, looked | ||||||
| 			like a complete keyboard — i.e. if a user would expect this keyboard | 			like a complete keyboard — i.e. if a user would expect this keyboard | ||||||
| 			to be the only thing a real keyboard maps to. | 			to be the only thing a real keyboard maps to. | ||||||
|  |  | ||||||
|  | 			So this would be true of something like the Amstrad CPC, which has a full | ||||||
|  | 			keyboard, but it would be false of something like the Sega Master System | ||||||
|  | 			which has some buttons that you'd expect an emulator to map to its host | ||||||
|  | 			keyboard but which does not offer a full keyboard. | ||||||
| 		*/ | 		*/ | ||||||
| 		virtual bool is_exclusive(); | 		virtual bool is_exclusive(); | ||||||
|  |  | ||||||
| @@ -68,6 +76,7 @@ class Keyboard { | |||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::set<Key> observed_keys_; | 		std::set<Key> observed_keys_; | ||||||
|  | 		std::set<Key> essential_modifiers_; | ||||||
| 		std::vector<bool> key_states_; | 		std::vector<bool> key_states_; | ||||||
| 		Delegate *delegate_ = nullptr; | 		Delegate *delegate_ = nullptr; | ||||||
| 		bool is_exclusive_ = true; | 		bool is_exclusive_ = true; | ||||||
|   | |||||||
| @@ -31,12 +31,12 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
| 		BIND(F11, KeyRightSquareBracket); | 		BIND(F11, KeyRightSquareBracket); | ||||||
| 		BIND(F12, KeyClear); | 		BIND(F12, KeyClear); | ||||||
|  |  | ||||||
| 		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyCaret);		BIND(BackSpace, KeyDelete); | 		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyCaret);		BIND(Backspace, KeyDelete); | ||||||
| 		BIND(Tab, KeyTab); | 		BIND(Tab, KeyTab); | ||||||
|  |  | ||||||
| 		BIND(OpenSquareBracket, KeyAt); | 		BIND(OpenSquareBracket, KeyAt); | ||||||
| 		BIND(CloseSquareBracket, KeyLeftSquareBracket); | 		BIND(CloseSquareBracket, KeyLeftSquareBracket); | ||||||
| 		BIND(BackSlash, KeyBackSlash); | 		BIND(Backslash, KeyBackSlash); | ||||||
|  |  | ||||||
| 		BIND(CapsLock, KeyCapsLock); | 		BIND(CapsLock, KeyCapsLock); | ||||||
| 		BIND(Semicolon, KeyColon); | 		BIND(Semicolon, KeyColon); | ||||||
|   | |||||||
| @@ -84,8 +84,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 			speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); | 			speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); | ||||||
| 		} | 		} | ||||||
| 		void update_just_in_time_cards() { | 		void update_just_in_time_cards() { | ||||||
| 			for(const auto &card : just_in_time_cards_) { | 			if(cycles_since_card_update_ > Cycles(0)) { | ||||||
| 				card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); | 				for(const auto &card : just_in_time_cards_) { | ||||||
|  | 					card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 			cycles_since_card_update_ = 0; | 			cycles_since_card_update_ = 0; | ||||||
| 			stretched_cycles_since_card_update_ = 0; | 			stretched_cycles_since_card_update_ = 0; | ||||||
| @@ -124,19 +126,25 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 			pick_card_messaging_group(card); | 			pick_card_messaging_group(card); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool is_every_cycle_card(Apple::II::Card *card) { | 		bool is_every_cycle_card(const Apple::II::Card *card) { | ||||||
| 			return !card->get_select_constraints(); | 			return !card->get_select_constraints(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		bool card_lists_are_dirty_ = true; | ||||||
|  | 		bool card_became_just_in_time_ = false; | ||||||
| 		void pick_card_messaging_group(Apple::II::Card *card) { | 		void pick_card_messaging_group(Apple::II::Card *card) { | ||||||
|  | 			// Simplify to a card being either just-in-time or realtime. | ||||||
|  | 			// Don't worry about exactly what it's watching, | ||||||
| 			const bool is_every_cycle = is_every_cycle_card(card); | 			const bool is_every_cycle = is_every_cycle_card(card); | ||||||
| 			std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; | 			std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; | ||||||
| 			std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; |  | ||||||
|  |  | ||||||
|  | 			// If the card is already in the proper group, stop. | ||||||
| 			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; | 			if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; | ||||||
| 			auto old_membership = std::find(undesired.begin(), undesired.end(), card); |  | ||||||
| 			if(old_membership != undesired.end()) undesired.erase(old_membership); | 			// Otherwise, mark the sets as dirty. It isn't safe to transition the card here, | ||||||
| 			intended.push_back(card); | 			// as the main loop may be part way through iterating the two lists. | ||||||
|  | 			card_lists_are_dirty_ = true; | ||||||
|  | 			card_became_just_in_time_ |= !is_every_cycle; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		void card_did_change_select_constraints(Apple::II::Card *card) override { | 		void card_did_change_select_constraints(Apple::II::Card *card) override { | ||||||
| @@ -753,6 +761,31 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// Update the card lists if any mutations are due. | ||||||
|  | 			if(card_lists_are_dirty_) { | ||||||
|  | 				card_lists_are_dirty_ = false; | ||||||
|  |  | ||||||
|  | 				// There's only one counter of time since update | ||||||
|  | 				// for just-in-time cards. If something new is | ||||||
|  | 				// transitioning, that needs to be zeroed. | ||||||
|  | 				if(card_became_just_in_time_) { | ||||||
|  | 					card_became_just_in_time_ = false; | ||||||
|  | 					update_just_in_time_cards(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// Clear the two lists and repopulate. | ||||||
|  | 				every_cycle_cards_.clear(); | ||||||
|  | 				just_in_time_cards_.clear(); | ||||||
|  | 				for(const auto &card: cards_) { | ||||||
|  | 					if(!card) continue; | ||||||
|  | 					if(is_every_cycle_card(card.get())) { | ||||||
|  | 						every_cycle_cards_.push_back(card.get()); | ||||||
|  | 					} else { | ||||||
|  | 						just_in_time_cards_.push_back(card.get()); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// Update analogue charge level. | 			// Update analogue charge level. | ||||||
| 			analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f); | 			analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f); | ||||||
|  |  | ||||||
| @@ -795,7 +828,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine: | |||||||
| 					case Key::Right:		value = 0x15;	break; | 					case Key::Right:		value = 0x15;	break; | ||||||
| 					case Key::Down:			value = 0x0a;	break; | 					case Key::Down:			value = 0x0a;	break; | ||||||
| 					case Key::Up:			value = 0x0b;	break; | 					case Key::Up:			value = 0x0b;	break; | ||||||
| 					case Key::BackSpace:	value = 0x7f;	break; | 					case Key::Backspace:	value = 0x7f;	break; | ||||||
| 					default: return; | 					default: return; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -83,10 +83,8 @@ class Card { | |||||||
| 			will receive a perform_bus_operation every cycle. To reduce the number of | 			will receive a perform_bus_operation every cycle. To reduce the number of | ||||||
| 			virtual method calls, they **will not** receive run_for. run_for will propagate | 			virtual method calls, they **will not** receive run_for. run_for will propagate | ||||||
| 			only to cards that register for IO and/or Device accesses only. | 			only to cards that register for IO and/or Device accesses only. | ||||||
|  |  | ||||||
|  |  | ||||||
| 		*/ | 		*/ | ||||||
| 		int get_select_constraints() { | 		int get_select_constraints() const { | ||||||
| 			return select_constraints_; | 			return select_constraints_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) { | |||||||
|  |  | ||||||
| void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { | ||||||
| 	diskii_clocking_preference_ = preference; | 	diskii_clocking_preference_ = preference; | ||||||
| 	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0); | 	set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : None); | ||||||
| } | } | ||||||
|  |  | ||||||
| Storage::Disk::Drive &DiskIICard::get_drive(int drive) { | Storage::Disk::Drive &DiskIICard::get_drive(int drive) { | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| using namespace Apple::Macintosh; | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
| void DriveSpeedAccumulator::post_sample(uint8_t sample) { | void DriveSpeedAccumulator::post_sample(uint8_t sample) { | ||||||
| 	if(!number_of_drives_) return; | 	if(!delegate_) return; | ||||||
|  |  | ||||||
| 	// An Euler-esque approximation is used here: just collect all | 	// An Euler-esque approximation is used here: just collect all | ||||||
| 	// the samples until there is a certain small quantity of them, | 	// the samples until there is a certain small quantity of them, | ||||||
| @@ -50,14 +50,7 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) { | |||||||
| 		const float normalised_sum = float(sum) / float(samples_.size()); | 		const float normalised_sum = float(sum) / float(samples_.size()); | ||||||
| 		const float rotation_speed = (normalised_sum * 27.08f) - 259.0f; | 		const float rotation_speed = (normalised_sum * 27.08f) - 259.0f; | ||||||
|  |  | ||||||
| 		for(int c = 0; c < number_of_drives_; ++c) { | 		delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed); | ||||||
| 			drives_[c]->set_rotation_speed(rotation_speed); |  | ||||||
| 		} |  | ||||||
| //		printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) { |  | ||||||
| 	drives_[number_of_drives_] = drive; |  | ||||||
| 	++number_of_drives_; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -13,8 +13,6 @@ | |||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  |  | ||||||
| #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" |  | ||||||
|  |  | ||||||
| namespace Apple { | namespace Apple { | ||||||
| namespace Macintosh { | namespace Macintosh { | ||||||
|  |  | ||||||
| @@ -25,18 +23,20 @@ class DriveSpeedAccumulator { | |||||||
| 		*/ | 		*/ | ||||||
| 		void post_sample(uint8_t sample); | 		void post_sample(uint8_t sample); | ||||||
|  |  | ||||||
|  | 		struct Delegate { | ||||||
|  | 			virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; | ||||||
|  | 		}; | ||||||
| 		/*! | 		/*! | ||||||
| 			Adds a connected drive. Up to two of these | 			Sets the delegate to receive drive speed changes. | ||||||
| 			can be supplied. Only Macintosh DoubleDensityDrives |  | ||||||
| 			are supported. |  | ||||||
| 		*/ | 		*/ | ||||||
| 		void add_drive(Apple::Macintosh::DoubleDensityDrive *drive); | 		void set_delegate(Delegate *delegate) { | ||||||
|  | 			delegate_ = delegate;; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		std::array<uint8_t, 20> samples_; | 		std::array<uint8_t, 20> samples_; | ||||||
| 		std::size_t sample_pointer_ = 0; | 		std::size_t sample_pointer_ = 0; | ||||||
| 		Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr}; | 		Delegate *delegate_ = nullptr; | ||||||
| 		int number_of_drives_ = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								Machines/Apple/Macintosh/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Machines/Apple/Macintosh/Keyboard.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | // | ||||||
|  | //  Keyboard.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 02/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Keyboard.hpp" | ||||||
|  |  | ||||||
|  | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
|  | uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | ||||||
|  | 	using Key = Inputs::Keyboard::Key; | ||||||
|  | 	using MacKey = Apple::Macintosh::Key; | ||||||
|  | 	switch(key) { | ||||||
|  | 		default: return KeyboardMachine::MappedMachine::KeyNotMapped; | ||||||
|  |  | ||||||
|  | #define Bind(x, y) case Key::x: return uint16_t(y) | ||||||
|  |  | ||||||
|  | 		Bind(BackTick, MacKey::BackTick); | ||||||
|  | 		Bind(k1, MacKey::k1);	Bind(k2, MacKey::k2);	Bind(k3, MacKey::k3); | ||||||
|  | 		Bind(k4, MacKey::k4);	Bind(k5, MacKey::k5);	Bind(k6, MacKey::k6); | ||||||
|  | 		Bind(k7, MacKey::k7);	Bind(k8, MacKey::k8);	Bind(k9, MacKey::k9); | ||||||
|  | 		Bind(k0, MacKey::k0); | ||||||
|  | 		Bind(Hyphen, MacKey::Hyphen); | ||||||
|  | 		Bind(Equals, MacKey::Equals); | ||||||
|  | 		Bind(Backspace, MacKey::Backspace); | ||||||
|  |  | ||||||
|  | 		Bind(Tab, MacKey::Tab); | ||||||
|  | 		Bind(Q, MacKey::Q);		Bind(W, MacKey::W);		Bind(E, MacKey::E);		Bind(R, MacKey::R); | ||||||
|  | 		Bind(T, MacKey::T);		Bind(Y, MacKey::Y);		Bind(U, MacKey::U);		Bind(I, MacKey::I); | ||||||
|  | 		Bind(O, MacKey::O);		Bind(P, MacKey::P); | ||||||
|  | 		Bind(OpenSquareBracket, MacKey::OpenSquareBracket); | ||||||
|  | 		Bind(CloseSquareBracket, MacKey::CloseSquareBracket); | ||||||
|  |  | ||||||
|  | 		Bind(CapsLock, MacKey::CapsLock); | ||||||
|  | 		Bind(A, MacKey::A);		Bind(S, MacKey::S);		Bind(D, MacKey::D);		Bind(F, MacKey::F); | ||||||
|  | 		Bind(G, MacKey::G);		Bind(H, MacKey::H);		Bind(J, MacKey::J);		Bind(K, MacKey::K); | ||||||
|  | 		Bind(L, MacKey::L); | ||||||
|  | 		Bind(Semicolon, MacKey::Semicolon); | ||||||
|  | 		Bind(Quote, MacKey::Quote); | ||||||
|  | 		Bind(Enter, MacKey::Return); | ||||||
|  |  | ||||||
|  | 		Bind(LeftShift, MacKey::Shift); | ||||||
|  | 		Bind(Z, MacKey::Z);		Bind(X, MacKey::X);		Bind(C, MacKey::C);		Bind(V, MacKey::V); | ||||||
|  | 		Bind(B, MacKey::B);		Bind(N, MacKey::N);		Bind(M, MacKey::M); | ||||||
|  | 		Bind(Comma, MacKey::Comma); | ||||||
|  | 		Bind(FullStop, MacKey::FullStop); | ||||||
|  | 		Bind(ForwardSlash, MacKey::ForwardSlash); | ||||||
|  | 		Bind(RightShift, MacKey::Shift); | ||||||
|  |  | ||||||
|  | 		Bind(Left, MacKey::Left); | ||||||
|  | 		Bind(Right, MacKey::Right); | ||||||
|  | 		Bind(Up, MacKey::Up); | ||||||
|  | 		Bind(Down, MacKey::Down); | ||||||
|  |  | ||||||
|  | 		Bind(LeftOption, MacKey::Option); | ||||||
|  | 		Bind(RightOption, MacKey::Option); | ||||||
|  | 		Bind(LeftMeta, MacKey::Command); | ||||||
|  | 		Bind(RightMeta, MacKey::Command); | ||||||
|  |  | ||||||
|  | 		Bind(Space, MacKey::Space); | ||||||
|  | 		Bind(Backslash, MacKey::Backslash); | ||||||
|  |  | ||||||
|  | 		Bind(KeyPadDelete, MacKey::KeyPadDelete); | ||||||
|  | 		Bind(KeyPadEquals, MacKey::KeyPadEquals); | ||||||
|  | 		Bind(KeyPadSlash, MacKey::KeyPadSlash); | ||||||
|  | 		Bind(KeyPadAsterisk, MacKey::KeyPadAsterisk); | ||||||
|  | 		Bind(KeyPadMinus, MacKey::KeyPadMinus); | ||||||
|  | 		Bind(KeyPadPlus, MacKey::KeyPadPlus); | ||||||
|  | 		Bind(KeyPadEnter, MacKey::KeyPadEnter); | ||||||
|  | 		Bind(KeyPadDecimalPoint, MacKey::KeyPadDecimalPoint); | ||||||
|  |  | ||||||
|  | 		Bind(KeyPad9, MacKey::KeyPad9); | ||||||
|  | 		Bind(KeyPad8, MacKey::KeyPad8); | ||||||
|  | 		Bind(KeyPad7, MacKey::KeyPad7); | ||||||
|  | 		Bind(KeyPad6, MacKey::KeyPad6); | ||||||
|  | 		Bind(KeyPad5, MacKey::KeyPad5); | ||||||
|  | 		Bind(KeyPad4, MacKey::KeyPad4); | ||||||
|  | 		Bind(KeyPad3, MacKey::KeyPad3); | ||||||
|  | 		Bind(KeyPad2, MacKey::KeyPad2); | ||||||
|  | 		Bind(KeyPad1, MacKey::KeyPad1); | ||||||
|  | 		Bind(KeyPad0, MacKey::KeyPad0); | ||||||
|  |  | ||||||
|  | #undef Bind | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -10,6 +10,7 @@ | |||||||
| #define Apple_Macintosh_Keyboard_hpp | #define Apple_Macintosh_Keyboard_hpp | ||||||
|  |  | ||||||
| #include "../../KeyboardMachine.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  |  | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -17,6 +18,72 @@ | |||||||
| namespace Apple { | namespace Apple { | ||||||
| namespace Macintosh { | namespace Macintosh { | ||||||
|  |  | ||||||
|  | static const uint16_t KeypadMask = 0x100; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Defines the keycodes that could be passed directly to a Macintosh via set_key_pressed. | ||||||
|  | */ | ||||||
|  | enum class Key: uint16_t { | ||||||
|  | 	/* | ||||||
|  | 		See p284 of the Apple Guide to the Macintosh Family Hardware | ||||||
|  | 		for documentation of the mapping below. | ||||||
|  | 	*/ | ||||||
|  | 	BackTick = 0x65, | ||||||
|  | 	k1 = 0x25,	k2 = 0x27,	k3 = 0x29,	k4 = 0x2b,	k5 = 0x2f, | ||||||
|  | 	k6 = 0x2d,	k7 = 0x35,	k8 = 0x39,	k9 = 0x33,	k0 = 0x3b, | ||||||
|  |  | ||||||
|  | 	Hyphen = 0x37, | ||||||
|  | 	Equals = 0x31, | ||||||
|  | 	Backspace = 0x67, | ||||||
|  | 	Tab = 0x61, | ||||||
|  |  | ||||||
|  | 	Q = 0x19, W = 0x1b, E = 0x1d, R = 0x1f, T = 0x23, Y = 0x21, U = 0x41, I = 0x45, O = 0x3f, P = 0x47, | ||||||
|  | 	A = 0x01, S = 0x03, D = 0x05, F = 0x07, G = 0x0b, H = 0x09, J = 0x4d, K = 0x51, L = 0x4b, | ||||||
|  | 	Z = 0x0d, X = 0x0f, C = 0x11, V = 0x13, B = 0x17, N = 0x5b, M = 0x5d, | ||||||
|  |  | ||||||
|  | 	OpenSquareBracket = 0x43, | ||||||
|  | 	CloseSquareBracket = 0x3d, | ||||||
|  | 	Semicolon = 0x53, | ||||||
|  | 	Quote = 0x4f, | ||||||
|  | 	Comma = 0x57, | ||||||
|  | 	FullStop = 0x5f, | ||||||
|  | 	ForwardSlash = 0x59, | ||||||
|  |  | ||||||
|  | 	CapsLock = 0x73, | ||||||
|  | 	Shift = 0x71, | ||||||
|  | 	Option = 0x75, | ||||||
|  | 	Command = 0x6f, | ||||||
|  |  | ||||||
|  | 	Space = 0x63, | ||||||
|  | 	Backslash = 0x55, | ||||||
|  | 	Return = 0x49, | ||||||
|  |  | ||||||
|  | 	Left = KeypadMask | 0x0d, | ||||||
|  | 	Right = KeypadMask | 0x05, | ||||||
|  | 	Up = KeypadMask | 0x1b, | ||||||
|  | 	Down = KeypadMask | 0x11, | ||||||
|  |  | ||||||
|  | 	KeyPadDelete = KeypadMask | 0x0f, | ||||||
|  | 	KeyPadEquals = KeypadMask | 0x11, | ||||||
|  | 	KeyPadSlash = KeypadMask | 0x1b, | ||||||
|  | 	KeyPadAsterisk = KeypadMask | 0x05, | ||||||
|  | 	KeyPadMinus = KeypadMask | 0x1d, | ||||||
|  | 	KeyPadPlus = KeypadMask | 0x0d, | ||||||
|  | 	KeyPadEnter = KeypadMask | 0x19, | ||||||
|  | 	KeyPadDecimalPoint = KeypadMask | 0x03, | ||||||
|  |  | ||||||
|  | 	KeyPad9 = KeypadMask | 0x39, | ||||||
|  | 	KeyPad8 = KeypadMask | 0x37, | ||||||
|  | 	KeyPad7 = KeypadMask | 0x33, | ||||||
|  | 	KeyPad6 = KeypadMask | 0x31, | ||||||
|  | 	KeyPad5 = KeypadMask | 0x2f, | ||||||
|  | 	KeyPad4 = KeypadMask | 0x2d, | ||||||
|  | 	KeyPad3 = KeypadMask | 0x2b, | ||||||
|  | 	KeyPad2 = KeypadMask | 0x29, | ||||||
|  | 	KeyPad1 = KeypadMask | 0x27, | ||||||
|  | 	KeyPad0 = KeypadMask | 0x25 | ||||||
|  | }; | ||||||
|  |  | ||||||
| class Keyboard { | class Keyboard { | ||||||
| 	public: | 	public: | ||||||
| 		void set_input(bool data) { | 		void set_input(bool data) { | ||||||
| @@ -147,14 +214,16 @@ class Keyboard { | |||||||
|  |  | ||||||
| 			// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme | 			// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme | ||||||
| 			// they are indicated by having bit 8 set. So add the $79 prefix if required. | 			// they are indicated by having bit 8 set. So add the $79 prefix if required. | ||||||
| 			if(key & 0x100) { | 			if(key & KeypadMask) { | ||||||
| 				key_queue_.insert(key_queue_.begin(), 0x79); | 				key_queue_.insert(key_queue_.begin(), 0x79); | ||||||
| 			} | 			} | ||||||
| 			key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); | 			key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
|  | 		/// Performs the pre-ADB Apple keyboard protocol command @c command, returning | ||||||
|  | 		/// the proper result if the command were to terminate now. So, it treats inquiry | ||||||
|  | 		/// and instant as the same command. | ||||||
| 		int perform_command(int command) { | 		int perform_command(int command) { | ||||||
| 			switch(command) { | 			switch(command) { | ||||||
| 				case 0x10:		// Inquiry. | 				case 0x10:		// Inquiry. | ||||||
| @@ -180,22 +249,41 @@ class Keyboard { | |||||||
| 			return 0x7b;	// No key transition. | 			return 0x7b;	// No key transition. | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/// Maintains the current operating mode — a record of what the | ||||||
|  | 		/// keyboard is doing now. | ||||||
| 		enum class Mode { | 		enum class Mode { | ||||||
|  | 			/// The keyboard is waiting to begin a transaction. | ||||||
| 			Waiting, | 			Waiting, | ||||||
|  | 			/// The keyboard is currently clocking in a new command. | ||||||
| 			AcceptingCommand, | 			AcceptingCommand, | ||||||
|  | 			/// The keyboard is waiting for the computer to indicate that it is ready for a response. | ||||||
| 			AwaitingEndOfCommand, | 			AwaitingEndOfCommand, | ||||||
|  | 			/// The keyboard is in the process of performing the command it most-recently received. | ||||||
|  | 			/// If the command was an 'inquiry', this state may persist for a non-neglibible period of time. | ||||||
|  | 			PerformingCommand, | ||||||
|  | 			/// The keyboard is currently shifting a response back to the computer. | ||||||
| 			SendingResponse, | 			SendingResponse, | ||||||
| 			PerformingCommand |  | ||||||
| 		} mode_ = Mode::Waiting; | 		} mode_ = Mode::Waiting; | ||||||
|  |  | ||||||
|  | 		/// Holds a count of progress through the current @c Mode. Exact meaning depends on mode. | ||||||
| 		int phase_ = 0; | 		int phase_ = 0; | ||||||
|  | 		/// Holds the most-recently-received command; the command is shifted into here as it is received | ||||||
|  | 		/// so this may not be valid prior to Mode::PerformingCommand. | ||||||
| 		int command_ = 0; | 		int command_ = 0; | ||||||
|  | 		/// Populated during PerformingCommand as the response to the most-recently-received command, this | ||||||
|  | 		/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, | ||||||
|  | 		/// but not afterwards. | ||||||
| 		int response_ = 0; | 		int response_ = 0; | ||||||
|  |  | ||||||
|  | 		/// The current state of the serial connection's data input. | ||||||
| 		bool data_input_ = false; | 		bool data_input_ = false; | ||||||
|  | 		/// The current clock output from this keyboard. | ||||||
| 		bool clock_output_ = false; | 		bool clock_output_ = false; | ||||||
|  |  | ||||||
| 		// TODO: improve this very, very simple implementation. | 		/// Guards multithread access to key_queue_. | ||||||
| 		std::mutex key_queue_mutex_; | 		std::mutex key_queue_mutex_; | ||||||
|  | 		/// A FIFO queue for key events, in the form they'd be communicated to the Macintosh, | ||||||
|  | 		/// with the newest events towards the front. | ||||||
| 		std::vector<uint8_t> key_queue_; | 		std::vector<uint8_t> key_queue_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -203,89 +291,7 @@ class Keyboard { | |||||||
| 	Provides a mapping from idiomatic PC keys to Macintosh keys. | 	Provides a mapping from idiomatic PC keys to Macintosh keys. | ||||||
| */ | */ | ||||||
| class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { | ||||||
| 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override { | 	uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final; | ||||||
| 		using Key = Inputs::Keyboard::Key; |  | ||||||
| 		switch(key) { |  | ||||||
| 			default: return KeyboardMachine::MappedMachine::KeyNotMapped; |  | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 				See p284 of the Apple Guide to the Macintosh Family Hardware |  | ||||||
| 				for documentation of the mapping below. |  | ||||||
| 			*/ |  | ||||||
|  |  | ||||||
| 			case Key::BackTick:				return 0x65; |  | ||||||
| 			case Key::k1:					return 0x25; |  | ||||||
| 			case Key::k2:					return 0x27; |  | ||||||
| 			case Key::k3:					return 0x29; |  | ||||||
| 			case Key::k4:					return 0x2b; |  | ||||||
| 			case Key::k5:					return 0x2f; |  | ||||||
| 			case Key::k6:					return 0x2d; |  | ||||||
| 			case Key::k7:					return 0x35; |  | ||||||
| 			case Key::k8:					return 0x39; |  | ||||||
| 			case Key::k9:					return 0x33; |  | ||||||
| 			case Key::k0:					return 0x3b; |  | ||||||
| 			case Key::Hyphen:				return 0x37; |  | ||||||
| 			case Key::Equals:				return 0x31; |  | ||||||
| 			case Key::BackSpace:			return 0x67; |  | ||||||
|  |  | ||||||
| 			case Key::Tab:					return 0x61; |  | ||||||
| 			case Key::Q:					return 0x19; |  | ||||||
| 			case Key::W:					return 0x1b; |  | ||||||
| 			case Key::E:					return 0x1d; |  | ||||||
| 			case Key::R:					return 0x1f; |  | ||||||
| 			case Key::T:					return 0x23; |  | ||||||
| 			case Key::Y:					return 0x21; |  | ||||||
| 			case Key::U:					return 0x41; |  | ||||||
| 			case Key::I:					return 0x45; |  | ||||||
| 			case Key::O:					return 0x3f; |  | ||||||
| 			case Key::P:					return 0x47; |  | ||||||
| 			case Key::OpenSquareBracket:	return 0x43; |  | ||||||
| 			case Key::CloseSquareBracket:	return 0x3d; |  | ||||||
|  |  | ||||||
| 			case Key::CapsLock:				return 0x73; |  | ||||||
| 			case Key::A:					return 0x01; |  | ||||||
| 			case Key::S:					return 0x03; |  | ||||||
| 			case Key::D:					return 0x05; |  | ||||||
| 			case Key::F:					return 0x07; |  | ||||||
| 			case Key::G:					return 0x0b; |  | ||||||
| 			case Key::H:					return 0x09; |  | ||||||
| 			case Key::J:					return 0x4d; |  | ||||||
| 			case Key::K:					return 0x51; |  | ||||||
| 			case Key::L:					return 0x4b; |  | ||||||
| 			case Key::Semicolon:			return 0x53; |  | ||||||
| 			case Key::Quote:				return 0x4f; |  | ||||||
| 			case Key::Enter:				return 0x49; |  | ||||||
|  |  | ||||||
| 			case Key::LeftShift:			return 0x71; |  | ||||||
| 			case Key::Z:					return 0x0d; |  | ||||||
| 			case Key::X:					return 0x0f; |  | ||||||
| 			case Key::C:					return 0x11; |  | ||||||
| 			case Key::V:					return 0x13; |  | ||||||
| 			case Key::B:					return 0x17; |  | ||||||
| 			case Key::N:					return 0x5b; |  | ||||||
| 			case Key::M:					return 0x5d; |  | ||||||
| 			case Key::Comma:				return 0x57; |  | ||||||
| 			case Key::FullStop:				return 0x5f; |  | ||||||
| 			case Key::ForwardSlash:			return 0x59; |  | ||||||
| 			case Key::RightShift:			return 0x71; |  | ||||||
|  |  | ||||||
| 			case Key::Left:					return 0x100 | 0x0d; |  | ||||||
| 			case Key::Right:				return 0x100 | 0x05; |  | ||||||
| 			case Key::Up:					return 0x100 | 0x1b; |  | ||||||
| 			case Key::Down:					return 0x100 | 0x11; |  | ||||||
|  |  | ||||||
| 			case Key::LeftOption: |  | ||||||
| 			case Key::RightOption:			return 0x75; |  | ||||||
| 			case Key::LeftMeta: |  | ||||||
| 			case Key::RightMeta:			return 0x6f; |  | ||||||
|  |  | ||||||
| 			case Key::Space:				return 0x63; |  | ||||||
| 			case Key::BackSlash:			return 0x55; |  | ||||||
|  |  | ||||||
| 			/* TODO: the numeric keypad. */ |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
| #include "RealTimeClock.hpp" | #include "RealTimeClock.hpp" | ||||||
| #include "Video.hpp" | #include "Video.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Activity/Source.hpp" | ||||||
| #include "../../CRTMachine.hpp" | #include "../../CRTMachine.hpp" | ||||||
| #include "../../KeyboardMachine.hpp" | #include "../../KeyboardMachine.hpp" | ||||||
| #include "../../MediaTarget.hpp" | #include "../../MediaTarget.hpp" | ||||||
| @@ -25,15 +26,22 @@ | |||||||
| #include "../../../Outputs/Log.hpp" | #include "../../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
| #include "../../../ClockReceiver/JustInTime.hpp" | #include "../../../ClockReceiver/JustInTime.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  | #include "../../../Configurable/StandardOptions.hpp" | ||||||
|  |  | ||||||
| //#define LOG_TRACE | //#define LOG_TRACE | ||||||
|  |  | ||||||
|  | #include "../../../Components/5380/ncr5380.hpp" | ||||||
| #include "../../../Components/6522/6522.hpp" | #include "../../../Components/6522/6522.hpp" | ||||||
| #include "../../../Components/8530/z8530.hpp" | #include "../../../Components/8530/z8530.hpp" | ||||||
| #include "../../../Components/DiskII/IWM.hpp" | #include "../../../Components/DiskII/IWM.hpp" | ||||||
| #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | #include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp" | ||||||
| #include "../../../Processors/68000/68000.hpp" | #include "../../../Processors/68000/68000.hpp" | ||||||
|  |  | ||||||
|  | #include "../../../Storage/MassStorage/SCSI/SCSI.hpp" | ||||||
|  | #include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp" | ||||||
|  | #include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp" | ||||||
|  |  | ||||||
| #include "../../../Analyser/Static/Macintosh/Target.hpp" | #include "../../../Analyser/Static/Macintosh/Target.hpp" | ||||||
|  |  | ||||||
| #include "../../Utility/MemoryPacker.hpp" | #include "../../Utility/MemoryPacker.hpp" | ||||||
| @@ -48,6 +56,12 @@ const int CLOCK_RATE = 7833600; | |||||||
| namespace Apple { | namespace Apple { | ||||||
| namespace Macintosh { | namespace Macintosh { | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options() { | ||||||
|  | 	return Configurable::standard_options( | ||||||
|  | 		static_cast<Configurable::StandardOptions>(Configurable::QuickBoot) | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
| template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine: | template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine: | ||||||
| 	public Machine, | 	public Machine, | ||||||
| 	public CRTMachine::Machine, | 	public CRTMachine::Machine, | ||||||
| @@ -55,16 +69,28 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 	public MouseMachine::Machine, | 	public MouseMachine::Machine, | ||||||
| 	public CPU::MC68000::BusHandler, | 	public CPU::MC68000::BusHandler, | ||||||
| 	public KeyboardMachine::MappedMachine, | 	public KeyboardMachine::MappedMachine, | ||||||
| 	public Zilog::SCC::z8530::Delegate { | 	public Zilog::SCC::z8530::Delegate, | ||||||
|  | 	public Activity::Source, | ||||||
|  | 	public Configurable::Device, | ||||||
|  | 	public DriveSpeedAccumulator::Delegate, | ||||||
|  | 	public ClockingHint::Observer { | ||||||
| 	public: | 	public: | ||||||
| 		using Target = Analyser::Static::Macintosh::Target; | 		using Target = Analyser::Static::Macintosh::Target; | ||||||
|  |  | ||||||
| 		ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | 		ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : | ||||||
|  | 			KeyboardMachine::MappedMachine({ | ||||||
|  | 				Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, | ||||||
|  | 				Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, | ||||||
|  | 				Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, | ||||||
|  | 			}), | ||||||
| 		 	mc68000_(*this), | 		 	mc68000_(*this), | ||||||
| 		 	iwm_(CLOCK_RATE), | 		 	iwm_(CLOCK_RATE), | ||||||
| 		 	video_(ram_, audio_, drive_speed_accumulator_), | 		 	video_(audio_, drive_speed_accumulator_), | ||||||
| 		 	via_(via_port_handler_), | 		 	via_(via_port_handler_), | ||||||
| 		 	via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_), | 		 	via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), | ||||||
|  | 		 	scsi_bus_(CLOCK_RATE * 2), | ||||||
|  | 		 	scsi_(scsi_bus_, CLOCK_RATE * 2), | ||||||
|  | 		 	hard_drive_(scsi_bus_, 6 /* SCSI ID */), | ||||||
| 		 	drives_{ | 		 	drives_{ | ||||||
| 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, | 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, | ||||||
| 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} | 		 		{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} | ||||||
| @@ -91,7 +117,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 				break; | 				break; | ||||||
| 				case Model::Mac512ke: | 				case Model::Mac512ke: | ||||||
| 				case Model::MacPlus: { | 				case Model::MacPlus: { | ||||||
| 					ram_size = 512*1024; | 					ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; | ||||||
| 					rom_size = 128*1024; | 					rom_size = 128*1024; | ||||||
| 					const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e }; | 					const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e }; | ||||||
| 					rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s); | 					rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s); | ||||||
| @@ -99,7 +125,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 			} | 			} | ||||||
| 			ram_mask_ = (ram_size >> 1) - 1; | 			ram_mask_ = (ram_size >> 1) - 1; | ||||||
| 			rom_mask_ = (rom_size >> 1) - 1; | 			rom_mask_ = (rom_size >> 1) - 1; | ||||||
| 			video_.set_ram_mask(ram_mask_); | 			ram_.resize(ram_size >> 1); | ||||||
|  | 			video_.set_ram(ram_.data(), ram_mask_); | ||||||
|  |  | ||||||
| 			// Grab a copy of the ROM and convert it into big-endian data. | 			// Grab a copy of the ROM and convert it into big-endian data. | ||||||
| 			const auto roms = rom_fetcher(rom_descriptions); | 			const auto roms = rom_fetcher(rom_descriptions); | ||||||
| @@ -110,25 +137,34 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 			Memory::PackBigEndian16(*roms[0], rom_); | 			Memory::PackBigEndian16(*roms[0], rom_); | ||||||
|  |  | ||||||
| 			// Randomise memory contents. | 			// Randomise memory contents. | ||||||
| 			Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_)); | 			Memory::Fuzz(ram_); | ||||||
|  |  | ||||||
| 			// Attach the drives to the IWM. | 			// Attach the drives to the IWM. | ||||||
| 			iwm_.iwm.set_drive(0, &drives_[0]); | 			iwm_->set_drive(0, &drives_[0]); | ||||||
| 			iwm_.iwm.set_drive(1, &drives_[1]); | 			iwm_->set_drive(1, &drives_[1]); | ||||||
|  |  | ||||||
| 			// If they are 400kb drives, also attach them to the drive-speed accumulator. | 			// If they are 400kb drives, also attach them to the drive-speed accumulator. | ||||||
| 			if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]); | 			if(!drives_[0].is_800k() || !drives_[1].is_800k()) { | ||||||
| 			if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]); | 				drive_speed_accumulator_.set_delegate(this); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// Make sure interrupt changes from the SCC are observed. | 			// Make sure interrupt changes from the SCC are observed. | ||||||
| 			scc_.set_delegate(this); | 			scc_.set_delegate(this); | ||||||
|  |  | ||||||
|  | 			// Also watch for changes in clocking requirement from the SCSI chip. | ||||||
|  | 			if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||||
|  | 				scsi_bus_.set_clocking_hint_observer(this); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// The Mac runs at 7.8336mHz. | 			// The Mac runs at 7.8336mHz. | ||||||
| 			set_clock_rate(double(CLOCK_RATE)); | 			set_clock_rate(double(CLOCK_RATE)); | ||||||
| 			audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); | 			audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); | ||||||
|  |  | ||||||
| 			// Insert any supplied media. | 			// Insert any supplied media. | ||||||
| 			insert_media(target.media); | 			insert_media(target.media); | ||||||
|  |  | ||||||
|  | 			// Set the immutables of the memory map. | ||||||
|  | 			setup_memory_map(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		~ConcreteMachine() { | 		~ConcreteMachine() { | ||||||
| @@ -149,96 +185,45 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
|  |  | ||||||
| 		using Microcycle = CPU::MC68000::Microcycle; | 		using Microcycle = CPU::MC68000::Microcycle; | ||||||
|  |  | ||||||
| 		HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { | 		forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { | ||||||
| 			// TODO: pick a delay if this is a video-clashing memory fetch. | 			// Advance time. | ||||||
| 			HalfCycles delay(0); | 			advance_time(cycle.length); | ||||||
|  |  | ||||||
| 			time_since_video_update_ += cycle.length; |  | ||||||
| 			iwm_.time_since_update += cycle.length; |  | ||||||
|  |  | ||||||
| 			// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. |  | ||||||
| 			// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division |  | ||||||
| 			// may occur here in order to provide VSYNC at a proper moment. |  | ||||||
| 			// Possibly route vsync. |  | ||||||
| 			if(time_since_video_update_ < time_until_video_event_) { |  | ||||||
| 				via_clock_ += cycle.length; |  | ||||||
| 				via_.run_for(via_clock_.divide(HalfCycles(10))); |  | ||||||
| 			} else { |  | ||||||
| 				auto via_time_base = time_since_video_update_ - cycle.length; |  | ||||||
| 				auto via_cycles_outstanding = cycle.length; |  | ||||||
| 				while(time_until_video_event_ < time_since_video_update_) { |  | ||||||
| 					const auto via_cycles = time_until_video_event_ - via_time_base; |  | ||||||
| 					via_time_base = HalfCycles(0); |  | ||||||
| 					via_cycles_outstanding -= via_cycles; |  | ||||||
|  |  | ||||||
| 					via_clock_ += via_cycles; |  | ||||||
| 					via_.run_for(via_clock_.divide(HalfCycles(10))); |  | ||||||
|  |  | ||||||
| 					video_.run_for(time_until_video_event_); |  | ||||||
| 					time_since_video_update_ -= time_until_video_event_; |  | ||||||
| 					time_until_video_event_ = video_.get_next_sequence_point(); |  | ||||||
|  |  | ||||||
| 					via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				via_clock_ += via_cycles_outstanding; |  | ||||||
| 				via_.run_for(via_clock_.divide(HalfCycles(10))); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. |  | ||||||
| 			// Its clock and data lines are connected to the VIA. |  | ||||||
| 			keyboard_clock_ += cycle.length; |  | ||||||
| 			const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000)); |  | ||||||
| 			if(keyboard_ticks > HalfCycles(0)) { |  | ||||||
| 				keyboard_.run_for(keyboard_ticks); |  | ||||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); |  | ||||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Feed mouse inputs within at most 1250 cycles of each other. |  | ||||||
| 			if(mouse_.has_steps()) { |  | ||||||
| 				time_since_mouse_update_ += cycle.length; |  | ||||||
| 				const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); |  | ||||||
| 				if(mouse_ticks > HalfCycles(0)) { |  | ||||||
| 					mouse_.prepare_step(); |  | ||||||
| 					scc_.set_dcd(0, mouse_.get_channel(1) & 1); |  | ||||||
| 					scc_.set_dcd(1, mouse_.get_channel(0) & 1); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// TODO: SCC should be clocked at a divide-by-two, if and when it actually has |  | ||||||
| 			// anything connected. |  | ||||||
|  |  | ||||||
| 			// Consider updating the real-time clock. |  | ||||||
| 			real_time_clock_ += cycle.length; |  | ||||||
| 			auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int(); |  | ||||||
| 			while(ticks--) { |  | ||||||
| 				clock_.update(); |  | ||||||
| 				// TODO: leave a delay between toggling the input rather than using this coupled hack. |  | ||||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); |  | ||||||
| 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// A null cycle leaves nothing else to do. | 			// A null cycle leaves nothing else to do. | ||||||
| 			if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay; | 			if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); | ||||||
|  |  | ||||||
| 			auto word_address = cycle.active_operation_word_address(); | 			// Grab the value on the address bus, at word precision. | ||||||
|  | 			uint32_t word_address = cycle.active_operation_word_address(); | ||||||
|  |  | ||||||
| 			// Everything above E0 0000 is signalled as being on the peripheral bus. | 			// Everything above E0 0000 is signalled as being on the peripheral bus. | ||||||
| 			mc68000_.set_is_peripheral_address(word_address >= 0x700000); | 			mc68000_.set_is_peripheral_address(word_address >= 0x700000); | ||||||
|  |  | ||||||
| 			// All code below deals only with reads and writes — cycles in which a | 			// All code below deals only with reads and writes — cycles in which a | ||||||
| 			// data select is active. So quit now if this is not the active part of | 			// data select is active. So quit now if this is not the active part of | ||||||
| 			//  a read or write. | 			// a read or write. | ||||||
| 			if(!cycle.data_select_active()) return delay; | 			// | ||||||
|  | 			// The 68000 uses 6800-style autovectored interrupts, so the mere act of | ||||||
|  | 			// having set VPA above deals with those given that the generated address | ||||||
|  | 			// for interrupt acknowledge cycles always has all bits set except the | ||||||
|  | 			// lowest explicit address lines. | ||||||
|  | 			if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0); | ||||||
|  |  | ||||||
| 			// Check whether this access maps into the IO area; if so then | 			// Grab the word-precision address being accessed. | ||||||
| 			// apply more complicated decoding logic. | 			uint16_t *memory_base = nullptr; | ||||||
| 			if(word_address >= 0x400000) { | 			HalfCycles delay; | ||||||
| 				const int register_address = word_address >> 8; | 			switch(memory_map_[word_address >> 16]) { | ||||||
|  | 				default: assert(false); | ||||||
|  |  | ||||||
|  | 				case BusDevice::Unassigned: | ||||||
|  | 					fill_unmapped(cycle); | ||||||
|  | 				return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::VIA: { | ||||||
|  | 					if(*cycle.address & 1) { | ||||||
|  | 						fill_unmapped(cycle); | ||||||
|  | 					} else { | ||||||
|  | 						const int register_address = word_address >> 8; | ||||||
|  |  | ||||||
| 				switch(word_address & 0x78f000) { |  | ||||||
| 					case 0x70f000: |  | ||||||
| 						// VIA accesses are via address 0xefe1fe + register*512, | 						// VIA accesses are via address 0xefe1fe + register*512, | ||||||
| 						// which at word precision is 0x77f0ff + register*256. | 						// which at word precision is 0x77f0ff + register*256. | ||||||
| 						if(cycle.operation & Microcycle::Read) { | 						if(cycle.operation & Microcycle::Read) { | ||||||
| @@ -246,116 +231,139 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 						} else { | 						} else { | ||||||
| 							via_.set_register(register_address, cycle.value->halves.low); | 							via_.set_register(register_address, cycle.value->halves.low); | ||||||
| 						} | 						} | ||||||
| 					break; |  | ||||||
|  |  | ||||||
| 					case 0x68f000: | 						if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||||
|  | 					} | ||||||
|  | 				} return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::PhaseRead: { | ||||||
|  | 					if(cycle.operation & Microcycle::Read) { | ||||||
|  | 						cycle.value->halves.low = phase_ & 7; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||||
|  | 				} return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::IWM: { | ||||||
|  | 					if(*cycle.address & 1) { | ||||||
|  | 						const int register_address = word_address >> 8; | ||||||
|  |  | ||||||
| 						// The IWM; this is a purely polled device, so can be run on demand. | 						// The IWM; this is a purely polled device, so can be run on demand. | ||||||
| 						iwm_.flush(); |  | ||||||
| 						if(cycle.operation & Microcycle::Read) { | 						if(cycle.operation & Microcycle::Read) { | ||||||
| 							cycle.value->halves.low = iwm_.iwm.read(register_address); | 							cycle.value->halves.low = iwm_->read(register_address); | ||||||
| 						} else { | 						} else { | ||||||
| 							iwm_.iwm.write(register_address, cycle.value->halves.low); | 							iwm_->write(register_address, cycle.value->halves.low); | ||||||
| 						} | 						} | ||||||
| 					break; |  | ||||||
|  |  | ||||||
| 					case 0x780000: | 						if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; | ||||||
| 						// Phase read. | 					} else { | ||||||
|  | 						fill_unmapped(cycle); | ||||||
|  | 					} | ||||||
|  | 				} return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::SCSI: { | ||||||
|  | 					const int register_address = word_address >> 3; | ||||||
|  | 					const bool dma_acknowledge = word_address & 0x100; | ||||||
|  |  | ||||||
|  | 					// Even accesses = read; odd = write. | ||||||
|  | 					if(*cycle.address & 1) { | ||||||
|  | 						// Odd access => this is a write. Data will be in the upper byte. | ||||||
| 						if(cycle.operation & Microcycle::Read) { | 						if(cycle.operation & Microcycle::Read) { | ||||||
| 							cycle.value->halves.low = phase_ & 7; | 							scsi_.write(register_address, 0xff, dma_acknowledge); | ||||||
| 						} |  | ||||||
| 					break; |  | ||||||
|  |  | ||||||
| 					case 0x480000: case 0x48f000: |  | ||||||
| 					case 0x580000: case 0x58f000: |  | ||||||
| 						// Any word access here adjusts phase. |  | ||||||
| 						if(cycle.operation & Microcycle::SelectWord) { |  | ||||||
| 							++phase_; |  | ||||||
| 						} else { | 						} else { | ||||||
| 							if(word_address < 0x500000) { | 							if(cycle.operation & Microcycle::SelectWord) { | ||||||
| 								// A0 = 1 => reset; A0 = 0 => read. | 								scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge); | ||||||
| 								if(*cycle.address & 1) { |  | ||||||
| 									scc_.reset(); |  | ||||||
| 								} else { |  | ||||||
| 									const auto read = scc_.read(int(word_address)); |  | ||||||
| 									if(cycle.operation & Microcycle::Read) { |  | ||||||
| 										cycle.value->halves.low = read; |  | ||||||
| 									} |  | ||||||
| 								} |  | ||||||
| 							} else { | 							} else { | ||||||
| 								if(*cycle.address & 1) { | 								scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge); | ||||||
| 									if(cycle.operation & Microcycle::Read) { |  | ||||||
| 										scc_.write(int(word_address), 0xff); |  | ||||||
| 									} else { |  | ||||||
| 										scc_.write(int(word_address), cycle.value->halves.low); |  | ||||||
| 									} |  | ||||||
| 								} |  | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					break; |  | ||||||
|  |  | ||||||
| 					default: |  | ||||||
| 						if(cycle.operation & Microcycle::Read) { |  | ||||||
| 							LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff)); |  | ||||||
| 							cycle.value->halves.low = 0x00; |  | ||||||
| 						} else { |  | ||||||
| 							LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff)); |  | ||||||
| 						} |  | ||||||
| 					break; |  | ||||||
| 				} |  | ||||||
| 				if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; |  | ||||||
|  |  | ||||||
| 				return delay; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Having reached here, this is a RAM or ROM access. |  | ||||||
|  |  | ||||||
| 			// When ROM overlay is enabled, the ROM begins at both $000000 and $400000, |  | ||||||
| 			// and RAM is available at $600000. |  | ||||||
| 			// |  | ||||||
| 			// Otherwise RAM is mapped at $000000 and ROM from $400000. |  | ||||||
| 			uint16_t *memory_base; |  | ||||||
| 			if( |  | ||||||
| 				(!ROM_is_overlay_ && word_address < 0x200000) || |  | ||||||
| 				(ROM_is_overlay_ && word_address >= 0x300000) |  | ||||||
| 			) { |  | ||||||
| 				memory_base = ram_; |  | ||||||
| 				word_address &= ram_mask_; |  | ||||||
|  |  | ||||||
| 				// This is coupled with the Macintosh implementation of video; the magic |  | ||||||
| 				// constant should probably be factored into the Video class. |  | ||||||
| 				// It embodies knowledge of the fact that video (and audio) will always |  | ||||||
| 				// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory. |  | ||||||
| 				// (And that ram_mask_ = ram size - 1). |  | ||||||
| //				if(word_address > ram_mask_ - 0x6c80) |  | ||||||
| 					update_video(); |  | ||||||
| 			} else { |  | ||||||
| 				memory_base = rom_; |  | ||||||
| 				word_address &= rom_mask_; |  | ||||||
|  |  | ||||||
| 				// Writes to ROM have no effect, and it doesn't mirror above 0x60000. |  | ||||||
| 				if(!(cycle.operation & Microcycle::Read)) return delay; |  | ||||||
| 				if(word_address >= 0x300000) { |  | ||||||
| 					if(cycle.operation & Microcycle::SelectWord) { |  | ||||||
| 						cycle.value->full = 0xffff; |  | ||||||
| 					} else { | 					} else { | ||||||
| 						cycle.value->halves.low = 0xff; | 						// Even access => this is a read. | ||||||
|  | 						if(cycle.operation & Microcycle::Read) { | ||||||
|  | 							const auto result = scsi_.read(register_address, dma_acknowledge); | ||||||
|  | 							if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 								// Data is loaded on the top part of the bus only. | ||||||
|  | 								cycle.value->full = uint16_t((result << 8) | 0xff); | ||||||
|  | 							} else { | ||||||
|  | 								cycle.value->halves.low = result; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 					return delay; | 				} return delay; | ||||||
| 				} |  | ||||||
|  | 				case BusDevice::SCCReadResetPhase: { | ||||||
|  | 					// Any word access here adjusts phase. | ||||||
|  | 					if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 						adjust_phase(); | ||||||
|  | 					} else { | ||||||
|  | 						// A0 = 1 => reset; A0 = 0 => read. | ||||||
|  | 						if(*cycle.address & 1) { | ||||||
|  | 							scc_.reset(); | ||||||
|  |  | ||||||
|  | 							if(cycle.operation & Microcycle::Read) { | ||||||
|  | 								cycle.value->halves.low = 0xff; | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							const auto read = scc_.read(int(word_address)); | ||||||
|  | 							if(cycle.operation & Microcycle::Read) { | ||||||
|  | 								cycle.value->halves.low = read; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::SCCWrite: { | ||||||
|  | 					// Any word access here adjusts phase. | ||||||
|  | 					if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 						adjust_phase(); | ||||||
|  | 					} else { | ||||||
|  | 						if(*cycle.address & 1) { | ||||||
|  | 							if(cycle.operation & Microcycle::Read) { | ||||||
|  | 								scc_.write(int(word_address), 0xff); | ||||||
|  | 								cycle.value->halves.low = 0xff; | ||||||
|  | 							} else { | ||||||
|  | 								scc_.write(int(word_address), cycle.value->halves.low); | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							fill_unmapped(cycle); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} return delay; | ||||||
|  |  | ||||||
|  | 				case BusDevice::RAM: { | ||||||
|  | 					// This is coupled with the Macintosh implementation of video; the magic | ||||||
|  | 					// constant should probably be factored into the Video class. | ||||||
|  | 					// It embodies knowledge of the fact that video (and audio) will always | ||||||
|  | 					// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory. | ||||||
|  | 					// (And that ram_mask_ = ram size - 1). | ||||||
|  | 					if(word_address > ram_mask_ - 0x6c80) | ||||||
|  | 						update_video(); | ||||||
|  |  | ||||||
|  | 					memory_base = ram_.data(); | ||||||
|  | 					word_address &= ram_mask_; | ||||||
|  |  | ||||||
|  | 					// Apply a delay due to video contention if applicable; scheme applied: | ||||||
|  | 					// only every other access slot is available during the period of video | ||||||
|  | 					// output. I believe this to be correct for the 128k, 512k and Plus. | ||||||
|  | 					// More research to do on other models. | ||||||
|  | 					if(video_is_outputting() && ram_subcycle_ < 8) { | ||||||
|  | 						delay = HalfCycles(8 - ram_subcycle_); | ||||||
|  | 						advance_time(delay); | ||||||
|  | 					} | ||||||
|  | 				} break; | ||||||
|  |  | ||||||
|  | 				case BusDevice::ROM: { | ||||||
|  | 					if(!(cycle.operation & Microcycle::Read)) return delay; | ||||||
|  | 					memory_base = rom_; | ||||||
|  | 					word_address &= rom_mask_; | ||||||
|  | 				} break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) { | 			// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. | ||||||
|  | 			switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { | ||||||
| 				default: | 				default: | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 				// Catches the deliberation set of operation to 0 above. |  | ||||||
| 				case 0: break; |  | ||||||
|  |  | ||||||
| 				case Microcycle::InterruptAcknowledge | Microcycle::SelectByte: |  | ||||||
| 					// The Macintosh uses autovectored interrupts. |  | ||||||
| 					mc68000_.set_is_peripheral_address(true); |  | ||||||
| 				break; |  | ||||||
|  |  | ||||||
| 				case Microcycle::SelectWord | Microcycle::Read: | 				case Microcycle::SelectWord | Microcycle::Read: | ||||||
| 					cycle.value->full = memory_base[word_address]; | 					cycle.value->full = memory_base[word_address]; | ||||||
| 				break; | 				break; | ||||||
| @@ -373,17 +381,6 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 				Normal memory map: |  | ||||||
|  |  | ||||||
| 				000000: 	RAM |  | ||||||
| 				400000: 	ROM |  | ||||||
| 				9FFFF8+:	SCC read operations |  | ||||||
| 				BFFFF8+:	SCC write operations |  | ||||||
| 				DFE1FF+:	IWM |  | ||||||
| 				EFE1FE+:	VIA |  | ||||||
| 			*/ |  | ||||||
|  |  | ||||||
| 			return delay; | 			return delay; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -404,6 +401,48 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
|  |  | ||||||
| 		void set_rom_is_overlay(bool rom_is_overlay) { | 		void set_rom_is_overlay(bool rom_is_overlay) { | ||||||
| 			ROM_is_overlay_ = rom_is_overlay; | 			ROM_is_overlay_ = rom_is_overlay; | ||||||
|  |  | ||||||
|  | 			using Model = Analyser::Static::Macintosh::Target::Model; | ||||||
|  | 			switch(model) { | ||||||
|  | 				case Model::Mac128k: | ||||||
|  | 				case Model::Mac512k: | ||||||
|  | 				case Model::Mac512ke: | ||||||
|  | 					populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||||
|  | 						// Addresses up to $80 0000 aren't affected by this bit. | ||||||
|  | 						if(rom_is_overlay) { | ||||||
|  | 							// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. | ||||||
|  | 							for(int c = 0; c < 0x600000; c += 0x100000) { | ||||||
|  | 								map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); | ||||||
|  | 							} | ||||||
|  | 							map_to(0x800000, BusDevice::RAM); | ||||||
|  | 						} else { | ||||||
|  | 							map_to(0x400000, BusDevice::RAM); | ||||||
|  | 							map_to(0x500000, BusDevice::ROM); | ||||||
|  | 							map_to(0x800000, BusDevice::Unassigned); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case Model::MacPlus: | ||||||
|  | 					populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) { | ||||||
|  | 						// Addresses up to $80 0000 aren't affected by this bit. | ||||||
|  | 						if(rom_is_overlay) { | ||||||
|  | 							for(int c = 0; c < 0x580000; c += 0x20000) { | ||||||
|  | 								map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); | ||||||
|  | 							} | ||||||
|  | 							map_to(0x600000, BusDevice::SCSI); | ||||||
|  | 							map_to(0x800000, BusDevice::RAM); | ||||||
|  | 						} else { | ||||||
|  | 							map_to(0x400000, BusDevice::RAM); | ||||||
|  | 							for(int c = 0x400000; c < 0x580000; c += 0x20000) { | ||||||
|  | 								map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); | ||||||
|  | 							} | ||||||
|  | 							map_to(0x600000, BusDevice::SCSI); | ||||||
|  | 							map_to(0x800000, BusDevice::Unassigned); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool video_is_outputting() { | 		bool video_is_outputting() { | ||||||
| @@ -416,16 +455,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		bool insert_media(const Analyser::Static::Media &media) override { | 		bool insert_media(const Analyser::Static::Media &media) override { | ||||||
| 			if(media.disks.empty()) | 			if(media.disks.empty() && media.mass_storage_devices.empty()) | ||||||
| 				return false; | 				return false; | ||||||
|  |  | ||||||
| 			// TODO: shouldn't allow disks to be replaced like this, as the Mac | 			// TODO: shouldn't allow disks to be replaced like this, as the Mac | ||||||
| 			// uses software eject. Will need to expand messaging ability of | 			// uses software eject. Will need to expand messaging ability of | ||||||
| 			// insert_media. | 			// insert_media. | ||||||
| 			if(drives_[0].has_disk()) | 			if(!media.disks.empty()) { | ||||||
| 				drives_[1].set_disk(media.disks[0]); | 				if(drives_[0].has_disk()) | ||||||
| 			else | 					drives_[1].set_disk(media.disks[0]); | ||||||
| 				drives_[0].set_disk(media.disks[0]); | 				else | ||||||
|  | 					drives_[0].set_disk(media.disks[0]); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// TODO: allow this only at machine startup. | ||||||
|  | 			if(!media.mass_storage_devices.empty()) { | ||||||
|  | 				const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get()); | ||||||
|  | 				if(volume) { | ||||||
|  | 					volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); | ||||||
|  | 				} | ||||||
|  | 				hard_drive_->set_storage(media.mass_storage_devices.front()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -460,8 +510,144 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Activity Source | ||||||
|  | 		void set_activity_observer(Activity::Observer *observer) override { | ||||||
|  | 			iwm_->set_activity_observer(observer); | ||||||
|  |  | ||||||
|  | 			if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||||
|  | 				scsi_bus_.set_activity_observer(observer); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// MARK: - Configuration options. | ||||||
|  | 		std::vector<std::unique_ptr<Configurable::Option>> get_options() override { | ||||||
|  | 			return Apple::Macintosh::get_options(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_selections(const Configurable::SelectionSet &selections_by_option) override { | ||||||
|  | 			bool quick_boot; | ||||||
|  | 			if(Configurable::get_quick_boot(selections_by_option, quick_boot)) { | ||||||
|  | 				if(quick_boot) { | ||||||
|  | 					// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the | ||||||
|  | 					// test at $E00. TODO: adapt as(/if?) necessary for other Macs. | ||||||
|  | 					ram_[0x02ae >> 1] = 0x40; | ||||||
|  | 					ram_[0x02b0 >> 1] = 0x00; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_accurate_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_quick_boot_selection(selection_set, false); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet get_user_friendly_selections() override { | ||||||
|  | 			Configurable::SelectionSet selection_set; | ||||||
|  | 			Configurable::append_quick_boot_selection(selection_set, true); | ||||||
|  | 			return selection_set; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	private: | 	private: | ||||||
| 		void update_video() { | 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { | ||||||
|  | 			scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override { | ||||||
|  | 			iwm_.flush(); | ||||||
|  | 			drives_[0].set_rotation_speed(speed); | ||||||
|  | 			drives_[1].set_rotation_speed(speed); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void adjust_phase() { | ||||||
|  | 			++phase_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void fill_unmapped(const Microcycle &cycle) { | ||||||
|  | 			if(!(cycle.operation & Microcycle::Read)) return; | ||||||
|  | 			if(cycle.operation & Microcycle::SelectWord) { | ||||||
|  | 				cycle.value->full = 0xffff; | ||||||
|  | 			} else { | ||||||
|  | 				cycle.value->halves.low = 0xff; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/// Advances all non-CPU components by @c duration half cycles. | ||||||
|  | 		forceinline void advance_time(HalfCycles duration) { | ||||||
|  | 			time_since_video_update_ += duration; | ||||||
|  | 			iwm_ += duration; | ||||||
|  | 			ram_subcycle_ = (ram_subcycle_ + duration.as_int()) & 15; | ||||||
|  |  | ||||||
|  | 			// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. | ||||||
|  | 			// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division | ||||||
|  | 			// may occur here in order to provide VSYNC at a proper moment. | ||||||
|  | 			// Possibly route vsync. | ||||||
|  | 			if(time_since_video_update_ < time_until_video_event_) { | ||||||
|  | 				via_clock_ += duration; | ||||||
|  | 				via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  | 			} else { | ||||||
|  | 				auto via_time_base = time_since_video_update_ - duration; | ||||||
|  | 				auto via_cycles_outstanding = duration; | ||||||
|  | 				while(time_until_video_event_ < time_since_video_update_) { | ||||||
|  | 					const auto via_cycles = time_until_video_event_ - via_time_base; | ||||||
|  | 					via_time_base = HalfCycles(0); | ||||||
|  | 					via_cycles_outstanding -= via_cycles; | ||||||
|  |  | ||||||
|  | 					via_clock_ += via_cycles; | ||||||
|  | 					via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  |  | ||||||
|  | 					video_.run_for(time_until_video_event_); | ||||||
|  | 					time_since_video_update_ -= time_until_video_event_; | ||||||
|  | 					time_until_video_event_ = video_.get_next_sequence_point(); | ||||||
|  |  | ||||||
|  | 					via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				via_clock_ += via_cycles_outstanding; | ||||||
|  | 				via_.run_for(via_clock_.divide(HalfCycles(10))); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. | ||||||
|  | 			// Its clock and data lines are connected to the VIA. | ||||||
|  | 			keyboard_clock_ += duration; | ||||||
|  | 			const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000)); | ||||||
|  | 			if(keyboard_ticks > HalfCycles(0)) { | ||||||
|  | 				keyboard_.run_for(keyboard_ticks); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Feed mouse inputs within at most 1250 cycles of each other. | ||||||
|  | 			if(mouse_.has_steps()) { | ||||||
|  | 				time_since_mouse_update_ += duration; | ||||||
|  | 				const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); | ||||||
|  | 				if(mouse_ticks > HalfCycles(0)) { | ||||||
|  | 					mouse_.prepare_step(); | ||||||
|  | 					scc_.set_dcd(0, mouse_.get_channel(1) & 1); | ||||||
|  | 					scc_.set_dcd(1, mouse_.get_channel(0) & 1); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// TODO: SCC should be clocked at a divide-by-two, if and when it actually has | ||||||
|  | 			// anything connected. | ||||||
|  |  | ||||||
|  | 			// Consider updating the real-time clock. | ||||||
|  | 			real_time_clock_ += duration; | ||||||
|  | 			auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int(); | ||||||
|  | 			while(ticks--) { | ||||||
|  | 				clock_.update(); | ||||||
|  | 				// TODO: leave a delay between toggling the input rather than using this coupled hack. | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); | ||||||
|  | 				via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Update the SCSI if currently active. | ||||||
|  | 			if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) { | ||||||
|  | 				if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		forceinline void update_video() { | ||||||
| 			video_.run_for(time_since_video_update_.flush<HalfCycles>()); | 			video_.run_for(time_since_video_update_.flush<HalfCycles>()); | ||||||
| 			time_until_video_event_ = video_.get_next_sequence_point(); | 			time_until_video_event_ = video_.get_next_sequence_point(); | ||||||
| 		} | 		} | ||||||
| @@ -470,21 +656,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 			return mouse_; | 			return mouse_; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		struct IWM { |  | ||||||
| 			IWM(int clock_rate) : iwm(clock_rate) {} |  | ||||||
|  |  | ||||||
| 			HalfCycles time_since_update; |  | ||||||
| 			Apple::IWM iwm; |  | ||||||
|  |  | ||||||
| 			void flush() { |  | ||||||
| 				iwm.run_for(time_since_update.flush<Cycles>()); |  | ||||||
| 			} |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		class VIAPortHandler: public MOS::MOS6522::PortHandler { | 		class VIAPortHandler: public MOS::MOS6522::PortHandler { | ||||||
| 			public: | 			public: | ||||||
| 				VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) : | 				VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, JustInTimeActor<IWM, HalfCycles, Cycles> &iwm, Inputs::QuadratureMouse &mouse) : | ||||||
| 					machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {} | 					machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} | ||||||
|  |  | ||||||
| 				using Port = MOS::MOS6522::Port; | 				using Port = MOS::MOS6522::Port; | ||||||
| 				using Line = MOS::MOS6522::Line; | 				using Line = MOS::MOS6522::Line; | ||||||
| @@ -505,8 +680,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 									b3:	0 = use alternate sound buffer, 1 = use ordinary sound buffer | 									b3:	0 = use alternate sound buffer, 1 = use ordinary sound buffer | ||||||
| 									b2–b0:	audio output volume | 									b2–b0:	audio output volume | ||||||
| 							*/ | 							*/ | ||||||
| 							iwm_.flush(); | 							iwm_->set_select(!!(value & 0x20)); | ||||||
| 							iwm_.iwm.set_select(!!(value & 0x20)); |  | ||||||
|  |  | ||||||
| 							machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); | 							machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); | ||||||
| 							machine_.set_rom_is_overlay(!!(value & 0x10)); | 							machine_.set_rom_is_overlay(!!(value & 0x10)); | ||||||
| @@ -589,16 +763,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
| 				ConcreteMachine &machine_; | 				ConcreteMachine &machine_; | ||||||
| 				RealTimeClock &clock_; | 				RealTimeClock &clock_; | ||||||
| 				Keyboard &keyboard_; | 				Keyboard &keyboard_; | ||||||
| 				Video &video_; |  | ||||||
| 				DeferredAudio &audio_; | 				DeferredAudio &audio_; | ||||||
| 				IWM &iwm_; | 				JustInTimeActor<IWM, HalfCycles, Cycles> &iwm_; | ||||||
| 				Inputs::QuadratureMouse &mouse_; | 				Inputs::QuadratureMouse &mouse_; | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		CPU::MC68000::Processor<ConcreteMachine, true> mc68000_; | 		CPU::MC68000::Processor<ConcreteMachine, true> mc68000_; | ||||||
|  |  | ||||||
| 		DriveSpeedAccumulator drive_speed_accumulator_; | 		DriveSpeedAccumulator drive_speed_accumulator_; | ||||||
| 		IWM iwm_; | 		JustInTimeActor<IWM, HalfCycles, Cycles> iwm_; | ||||||
|  |  | ||||||
| 		DeferredAudio audio_; | 		DeferredAudio audio_; | ||||||
| 		Video video_; | 		Video video_; | ||||||
| @@ -610,6 +783,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
|  		VIAPortHandler via_port_handler_; |  		VIAPortHandler via_port_handler_; | ||||||
|  |  | ||||||
|  		Zilog::SCC::z8530 scc_; |  		Zilog::SCC::z8530 scc_; | ||||||
|  | 		SCSI::Bus scsi_bus_; | ||||||
|  |  		NCR::NCR5380::NCR5380 scsi_; | ||||||
|  | 		SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_; | ||||||
|  |  		bool scsi_bus_is_clocked_ = false; | ||||||
|  |  | ||||||
|  		HalfCycles via_clock_; |  		HalfCycles via_clock_; | ||||||
|  		HalfCycles real_time_clock_; |  		HalfCycles real_time_clock_; | ||||||
| @@ -620,16 +797,61 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin | |||||||
|  |  | ||||||
| 		bool ROM_is_overlay_ = true; | 		bool ROM_is_overlay_ = true; | ||||||
| 		int phase_ = 1; | 		int phase_ = 1; | ||||||
|  | 		int ram_subcycle_ = 0; | ||||||
|  |  | ||||||
| 		DoubleDensityDrive drives_[2]; | 		DoubleDensityDrive drives_[2]; | ||||||
| 		Inputs::QuadratureMouse mouse_; | 		Inputs::QuadratureMouse mouse_; | ||||||
|  |  | ||||||
| 		Apple::Macintosh::KeyboardMapper keyboard_mapper_; | 		Apple::Macintosh::KeyboardMapper keyboard_mapper_; | ||||||
|  |  | ||||||
|  | 		enum class BusDevice { | ||||||
|  | 			RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording | ||||||
|  | 		/// which device is current mapped in each area. Keeping it in a table is a bit faster | ||||||
|  | 		/// than the multi-level address inspection that is otherwise required, as well as | ||||||
|  | 		/// simplifying slightly the handling of different models. | ||||||
|  | 		/// | ||||||
|  | 		/// So: index with the top 7 bits of the 24-bit address. | ||||||
|  | 		BusDevice memory_map_[128]; | ||||||
|  |  | ||||||
|  | 		void setup_memory_map() { | ||||||
|  | 			// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; | ||||||
|  | 			// start by calling into set_rom_is_overlay to seed everything up to $800000. | ||||||
|  | 			set_rom_is_overlay(true); | ||||||
|  |  | ||||||
|  | 			populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) { | ||||||
|  | 				map_to(0x900000, BusDevice::Unassigned); | ||||||
|  | 				map_to(0xa00000, BusDevice::SCCReadResetPhase); | ||||||
|  | 				map_to(0xb00000, BusDevice::Unassigned); | ||||||
|  | 				map_to(0xc00000, BusDevice::SCCWrite); | ||||||
|  | 				map_to(0xd00000, BusDevice::Unassigned); | ||||||
|  | 				map_to(0xe00000, BusDevice::IWM); | ||||||
|  | 				map_to(0xe80000, BusDevice::Unassigned); | ||||||
|  | 				map_to(0xf00000, BusDevice::VIA); | ||||||
|  | 				map_to(0xf80000, BusDevice::PhaseRead); | ||||||
|  | 				map_to(0x1000000, BusDevice::Unassigned); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) { | ||||||
|  | 			// Define semantics for below; map_to will write from the current cursor position | ||||||
|  | 			// to the supplied 24-bit address, setting a particular mapped device. | ||||||
|  | 			int segment = start_address >> 17; | ||||||
|  | 			auto map_to = [&segment, this](int address, BusDevice device) { | ||||||
|  | 				for(; segment < address >> 17; ++segment) { | ||||||
|  | 					this->memory_map_[segment] = device; | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			populator(map_to); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		uint32_t ram_mask_ = 0; | 		uint32_t ram_mask_ = 0; | ||||||
| 		uint32_t rom_mask_ = 0; | 		uint32_t rom_mask_ = 0; | ||||||
| 		uint16_t rom_[64*1024]; | 		uint16_t rom_[64*1024];	// i.e. up to 128kb in size. | ||||||
| 		uint16_t ram_[256*1024]; | 		std::vector<uint16_t> ram_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,12 +9,15 @@ | |||||||
| #ifndef Macintosh_hpp | #ifndef Macintosh_hpp | ||||||
| #define Macintosh_hpp | #define Macintosh_hpp | ||||||
|  |  | ||||||
|  | #include "../../../Configurable/Configurable.hpp" | ||||||
| #include "../../../Analyser/Static/StaticAnalyser.hpp" | #include "../../../Analyser/Static/StaticAnalyser.hpp" | ||||||
| #include "../../ROMMachine.hpp" | #include "../../ROMMachine.hpp" | ||||||
|  |  | ||||||
| namespace Apple { | namespace Apple { | ||||||
| namespace Macintosh { | namespace Macintosh { | ||||||
|  |  | ||||||
|  | std::vector<std::unique_ptr<Configurable::Option>> get_options(); | ||||||
|  |  | ||||||
| class Machine { | class Machine { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Machine(); | 		virtual ~Machine(); | ||||||
|   | |||||||
| @@ -25,8 +25,15 @@ class RealTimeClock { | |||||||
| 	public: | 	public: | ||||||
| 		RealTimeClock() { | 		RealTimeClock() { | ||||||
| 			// TODO: this should persist, if possible, rather than | 			// TODO: this should persist, if possible, rather than | ||||||
| 			// being randomly initialised. | 			// being default initialised. | ||||||
| 			Memory::Fuzz(data_, sizeof(data_)); | 			const uint8_t default_data[] = { | ||||||
|  | 				0xa8, 0x00, 0x00, 0x00, | ||||||
|  | 				0xcc, 0x0a, 0xcc, 0x0a, | ||||||
|  | 				0x00, 0x00, 0x00, 0x00, | ||||||
|  | 				0x00, 0x02, 0x63, 0x00, | ||||||
|  | 				0x03, 0x88, 0x00, 0x4c | ||||||
|  | 			}; | ||||||
|  | 			memcpy(data_, default_data, sizeof(data_)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
|   | |||||||
| @@ -12,16 +12,6 @@ | |||||||
|  |  | ||||||
| using namespace Apple::Macintosh; | using namespace Apple::Macintosh; | ||||||
|  |  | ||||||
| namespace { |  | ||||||
|  |  | ||||||
| const HalfCycles line_length(704); |  | ||||||
| const int number_of_lines = 370; |  | ||||||
| const HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); |  | ||||||
| const int sync_start = 36; |  | ||||||
| const int sync_end = 38; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family, | // Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family, | ||||||
| // bottom of page 400: | // bottom of page 400: | ||||||
| // | // | ||||||
| @@ -33,11 +23,10 @@ const int sync_end = 38; | |||||||
| //	"The visible portion of a full-screen display consists of 342 horizontal scan lines... | //	"The visible portion of a full-screen display consists of 342 horizontal scan lines... | ||||||
| //	During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," | //	During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," | ||||||
| // | // | ||||||
| Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : | Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) : | ||||||
| 	audio_(audio), | 	audio_(audio), | ||||||
| 	drive_speed_accumulator_(drive_speed_accumulator), | 	drive_speed_accumulator_(drive_speed_accumulator), | ||||||
|  	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1), |  	crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { | ||||||
|  	ram_(ram) { |  | ||||||
|  |  | ||||||
|  	crt_.set_display_type(Outputs::Display::DisplayType::RGB); |  	crt_.set_display_type(Outputs::Display::DisplayType::RGB); | ||||||
| 	crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); | 	crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f)); | ||||||
| @@ -184,18 +173,12 @@ HalfCycles Video::get_next_sequence_point() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Video::is_outputting(HalfCycles offset) { |  | ||||||
| 	const auto offset_position = frame_position_ + offset % frame_length; |  | ||||||
| 	const int column = (offset_position % line_length).as_int() >> 4; |  | ||||||
| 	const int line = (offset_position / line_length).as_int(); |  | ||||||
| 	return line < 342 && column < 32; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { | void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { | ||||||
| 	use_alternate_screen_buffer_ = use_alternate_screen_buffer; | 	use_alternate_screen_buffer_ = use_alternate_screen_buffer; | ||||||
| 	use_alternate_audio_buffer_ = use_alternate_audio_buffer; | 	use_alternate_audio_buffer_ = use_alternate_audio_buffer; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Video::set_ram_mask(uint32_t mask) { | void Video::set_ram(uint16_t *ram, uint32_t mask) { | ||||||
|  | 	ram_ = ram; | ||||||
| 	ram_mask_ = mask; | 	ram_mask_ = mask; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,12 @@ | |||||||
| namespace Apple { | namespace Apple { | ||||||
| namespace Macintosh { | namespace Macintosh { | ||||||
|  |  | ||||||
|  | static const HalfCycles line_length(704); | ||||||
|  | static const int number_of_lines = 370; | ||||||
|  | static const HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); | ||||||
|  | static const int sync_start = 36; | ||||||
|  | static const int sync_end = 38; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image, | 	Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image, | ||||||
| 	within a total scanning area of 370 lines, at 352 cycles per line. | 	within a total scanning area of 370 lines, at 352 cycles per line. | ||||||
| @@ -29,7 +35,7 @@ class Video { | |||||||
| 			Constructs an instance of @c Video sourcing its pixel data from @c ram and | 			Constructs an instance of @c Video sourcing its pixel data from @c ram and | ||||||
| 			providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. | 			providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. | ||||||
| 		*/ | 		*/ | ||||||
| 		Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); | 		Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Sets the target device for video data. | 			Sets the target device for video data. | ||||||
| @@ -47,10 +53,10 @@ class Video { | |||||||
| 		void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); | 		void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			Provides a mask indicating which parts of the generated video and audio/drive addresses are | 			Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are | ||||||
| 			actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. | 			actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. | ||||||
| 		*/ | 		*/ | ||||||
| 		void set_ram_mask(uint32_t); | 		void set_ram(uint16_t *ram, uint32_t mask); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			@returns @c true if the video is currently outputting a vertical sync, @c false otherwise. | 			@returns @c true if the video is currently outputting a vertical sync, @c false otherwise. | ||||||
| @@ -61,7 +67,12 @@ class Video { | |||||||
| 			@returns @c true if in @c offset half cycles from now, the video will be outputting pixels; | 			@returns @c true if in @c offset half cycles from now, the video will be outputting pixels; | ||||||
| 				@c false otherwise. | 				@c false otherwise. | ||||||
| 		*/ | 		*/ | ||||||
| 		bool is_outputting(HalfCycles offset = HalfCycles(0)); | 		bool is_outputting(HalfCycles offset = HalfCycles(0)) { | ||||||
|  | 			const auto offset_position = frame_position_ + offset % frame_length; | ||||||
|  | 			const int column = (offset_position % line_length).as_int() >> 4; | ||||||
|  | 			const int line = (offset_position / line_length).as_int(); | ||||||
|  | 			return line < 342 && column < 32; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			@returns the amount of time until there is next a transition on the | 			@returns the amount of time until there is next a transition on the | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
| 		BIND(OpenSquareBracket, KeyAt); | 		BIND(OpenSquareBracket, KeyAt); | ||||||
| 		BIND(CloseSquareBracket, KeyAsterisk); | 		BIND(CloseSquareBracket, KeyAsterisk); | ||||||
|  |  | ||||||
| 		BIND(BackSlash, KeyRestore); | 		BIND(Backslash, KeyRestore); | ||||||
| 		BIND(Hash, KeyUp); | 		BIND(Hash, KeyUp); | ||||||
| 		BIND(F10, KeyUp); | 		BIND(F10, KeyUp); | ||||||
|  |  | ||||||
| @@ -59,7 +59,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
|  |  | ||||||
| 		BIND(Enter, KeyReturn); | 		BIND(Enter, KeyReturn); | ||||||
| 		BIND(Space, KeySpace); | 		BIND(Space, KeySpace); | ||||||
| 		BIND(BackSpace, KeyDelete); | 		BIND(Backspace, KeyDelete); | ||||||
|  |  | ||||||
| 		BIND(Escape, KeyRunStop); | 		BIND(Escape, KeyRunStop); | ||||||
| 		BIND(F1, KeyF1); | 		BIND(F1, KeyF1); | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
| 		BIND(LeftShift, KeyShift);		BIND(RightShift, KeyShift); | 		BIND(LeftShift, KeyShift);		BIND(RightShift, KeyShift); | ||||||
|  |  | ||||||
| 		BIND(Hyphen, KeyMinus); | 		BIND(Hyphen, KeyMinus); | ||||||
| 		BIND(Delete, KeyDelete);		BIND(BackSpace, KeyDelete); | 		BIND(Delete, KeyDelete);		BIND(Backspace, KeyDelete); | ||||||
| 		BIND(Enter, KeyReturn);			BIND(KeyPadEnter, KeyReturn); | 		BIND(Enter, KeyReturn);			BIND(KeyPadEnter, KeyReturn); | ||||||
|  |  | ||||||
| 		BIND(KeyPad0, Key0);		BIND(KeyPad1, Key1);		BIND(KeyPad2, Key2);		BIND(KeyPad3, Key3);		BIND(KeyPad4, Key4); | 		BIND(KeyPad0, Key0);		BIND(KeyPad1, Key1);		BIND(KeyPad2, Key2);		BIND(KeyPad3, Key3);		BIND(KeyPad4, Key4); | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
|  |  | ||||||
| using namespace KeyboardMachine; | using namespace KeyboardMachine; | ||||||
|  |  | ||||||
| MappedMachine::MappedMachine() { | MappedMachine::MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers) : keyboard_(essential_modifiers) { | ||||||
| 	keyboard_.set_delegate(this); | 	keyboard_.set_delegate(this); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|  |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <set> | ||||||
|  |  | ||||||
| #include "../Inputs/Keyboard.hpp" | #include "../Inputs/Keyboard.hpp" | ||||||
|  |  | ||||||
| @@ -56,7 +57,7 @@ class Machine: public KeyActions { | |||||||
| */ | */ | ||||||
| class MappedMachine: public Inputs::Keyboard::Delegate, public Machine { | class MappedMachine: public Inputs::Keyboard::Delegate, public Machine { | ||||||
| 	public: | 	public: | ||||||
| 		MappedMachine(); | 		MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {}); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
| 			A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys. | 			A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys. | ||||||
|   | |||||||
| @@ -47,12 +47,12 @@ uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
| 		BIND(FullStop, KeyFullStop); | 		BIND(FullStop, KeyFullStop); | ||||||
| 		BIND(Comma, KeyComma); | 		BIND(Comma, KeyComma); | ||||||
| 		BIND(ForwardSlash, KeyForwardSlash); | 		BIND(ForwardSlash, KeyForwardSlash); | ||||||
| 		BIND(BackSlash, KeyBackSlash); | 		BIND(Backslash, KeyBackSlash); | ||||||
| 		BIND(BackTick, KeyGrave); | 		BIND(BackTick, KeyGrave); | ||||||
|  |  | ||||||
| 		BIND(Enter, KeyEnter); | 		BIND(Enter, KeyEnter); | ||||||
| 		BIND(Space, KeySpace); | 		BIND(Space, KeySpace); | ||||||
| 		BIND(BackSpace, KeyBackspace); | 		BIND(Backspace, KeyBackspace); | ||||||
|  |  | ||||||
| 		default: break; | 		default: break; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ class ConcreteMachine: | |||||||
| 				audio_queue_, | 				audio_queue_, | ||||||
| 				sn76489_divider), | 				sn76489_divider), | ||||||
| 			speaker_(sn76489_), | 			speaker_(sn76489_), | ||||||
| 			keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}) { | 			keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) { | ||||||
| 			// Pick the clock rate based on the region. | 			// Pick the clock rate based on the region. | ||||||
| 			const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0; | 			const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0; | ||||||
| 			speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider)); | 			speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider)); | ||||||
|   | |||||||
| @@ -26,10 +26,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { | |||||||
|  |  | ||||||
| 		BIND(Left, KeyLeft);	BIND(Right, KeyRight);		BIND(Up, KeyUp);		BIND(Down, KeyDown); | 		BIND(Left, KeyLeft);	BIND(Right, KeyRight);		BIND(Up, KeyUp);		BIND(Down, KeyDown); | ||||||
|  |  | ||||||
| 		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyEquals);		BIND(BackSlash, KeyBackSlash); | 		BIND(Hyphen, KeyMinus);		BIND(Equals, KeyEquals);		BIND(Backslash, KeyBackSlash); | ||||||
| 		BIND(OpenSquareBracket, KeyOpenSquare);	BIND(CloseSquareBracket, KeyCloseSquare); | 		BIND(OpenSquareBracket, KeyOpenSquare);	BIND(CloseSquareBracket, KeyCloseSquare); | ||||||
|  |  | ||||||
| 		BIND(BackSpace, KeyDelete);	BIND(Delete, KeyDelete); | 		BIND(Backspace, KeyDelete);	BIND(Delete, KeyDelete); | ||||||
|  |  | ||||||
| 		BIND(Semicolon, KeySemiColon);	BIND(Quote, KeyQuote); | 		BIND(Semicolon, KeySemiColon);	BIND(Quote, KeyQuote); | ||||||
| 		BIND(Comma, KeyComma);			BIND(FullStop, KeyFullStop);	BIND(ForwardSlash, KeyForwardSlash); | 		BIND(Comma, KeyComma);			BIND(FullStop, KeyFullStop);	BIND(ForwardSlash, KeyForwardSlash); | ||||||
|   | |||||||
| @@ -139,6 +139,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin | |||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); | ||||||
|  | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); | ||||||
| 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options())); | 	options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options())); | ||||||
|   | |||||||
| @@ -26,7 +26,3 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) { | |||||||
| void Memory::Fuzz(uint16_t *buffer, std::size_t size) { | void Memory::Fuzz(uint16_t *buffer, std::size_t size) { | ||||||
| 	Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t)); | 	Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Memory::Fuzz(std::vector<uint8_t> &buffer) { |  | ||||||
| 	Fuzz(buffer.data(), buffer.size()); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -22,7 +22,9 @@ void Fuzz(uint8_t *buffer, std::size_t size); | |||||||
| void Fuzz(uint16_t *buffer, std::size_t size); | void Fuzz(uint16_t *buffer, std::size_t size); | ||||||
|  |  | ||||||
| /// Replaces all existing vector contents with random bytes. | /// Replaces all existing vector contents with random bytes. | ||||||
| void Fuzz(std::vector<uint8_t> &buffer); | template <typename T> void Fuzz(std::vector<T> &buffer) { | ||||||
|  | 	Fuzz(reinterpret_cast<uint8_t *>(buffer.data()), buffer.size() * sizeof(buffer[0])); | ||||||
|  | } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -186,6 +186,7 @@ | |||||||
| 		4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; }; | 		4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; }; | ||||||
| 		4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; | 		4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; | ||||||
| 		4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; | 		4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; | ||||||
|  | 		4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; }; | ||||||
| 		4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; | 		4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; | ||||||
| 		4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; | 		4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; | ||||||
| 		4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; | 		4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; | ||||||
| @@ -210,16 +211,27 @@ | |||||||
| 		4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; }; | 		4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; }; | ||||||
| 		4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; | 		4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; | ||||||
| 		4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; | 		4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; | ||||||
|  | 		4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */; }; | ||||||
|  | 		4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */; }; | ||||||
| 		4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; | 		4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; | ||||||
| 		4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; | 		4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; | ||||||
| 		4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; | 		4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; | ||||||
| 		4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; | 		4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; | ||||||
|  | 		4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */; }; | ||||||
|  | 		4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; }; | ||||||
|  | 		4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; }; | ||||||
|  | 		4B6AAEAD230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; }; | ||||||
|  | 		4B6AAEAE230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; }; | ||||||
| 		4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; | 		4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; | ||||||
| 		4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; | 		4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; }; | ||||||
| 		4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; }; | 		4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; }; | ||||||
| 		4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; | 		4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; }; | ||||||
| 		4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; }; | 		4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; }; | ||||||
| 		4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; }; | 		4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; }; | ||||||
|  | 		4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; }; | ||||||
|  | 		4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; }; | ||||||
|  | 		4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; }; | ||||||
|  | 		4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; }; | ||||||
| 		4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; | 		4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; }; | ||||||
| 		4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; | 		4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; }; | ||||||
| 		4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; | 		4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; | ||||||
| @@ -629,6 +641,8 @@ | |||||||
| 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; | ||||||
| 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | 		4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; | ||||||
| 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | 		4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; | ||||||
|  | 		4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; }; | ||||||
|  | 		4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; }; | ||||||
| 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | 		4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; }; | ||||||
| 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | 		4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; | ||||||
| 		4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; | 		4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; | ||||||
| @@ -642,6 +656,7 @@ | |||||||
| 		4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; }; | 		4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; }; | ||||||
| 		4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; }; | 		4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; }; | ||||||
| 		4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; | 		4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; | ||||||
|  | 		4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; }; | ||||||
| 		4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; | 		4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; | ||||||
| 		4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; | 		4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; | ||||||
| 		4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; }; | 		4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; }; | ||||||
| @@ -667,6 +682,8 @@ | |||||||
| 		4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; }; | 		4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; }; | ||||||
| 		4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; }; | 		4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; }; | ||||||
| 		4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; | 		4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; | ||||||
|  | 		4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; | ||||||
|  | 		4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; | ||||||
| 		4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; | 		4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; | ||||||
| 		4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; | 		4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; | ||||||
| 		4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; | 		4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; | ||||||
| @@ -897,6 +914,7 @@ | |||||||
| 		4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; }; | 		4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = "<group>"; }; | 		4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; }; | 		4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; }; | ||||||
| 		4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = "<group>"; }; | 		4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = "<group>"; }; | 		4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; }; | 		4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -940,6 +958,7 @@ | |||||||
| 		4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; }; | 		4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; }; | ||||||
| 		4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; }; | 		4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; }; | ||||||
| 		4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; }; | 		4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; }; | ||||||
|  | 		4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; }; | 		4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; }; | 		4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; }; | 		4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -950,6 +969,13 @@ | |||||||
| 		4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; }; | 		4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; }; | 		4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; }; | 		4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MassStorageDevice.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MassStorageDevice.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA6230E40250078E864 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA7230E40250078E864 /* SCSI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SCSI.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA8230E40250078E864 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEA9230E40250078E864 /* SCSI.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SCSI.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TargetImplementation.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WOZ.cpp; sourceTree = "<group>"; }; | 		4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WOZ.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = "<group>"; }; | 		4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = "<group>"; }; | 		4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -965,6 +991,10 @@ | |||||||
| 		4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = "<group>"; }; | 		4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; }; | 		4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; }; | ||||||
| 		4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; }; | 		4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B74CF7F2312FA9C00500CE8 /* HFV.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = HFV.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MacintoshVolume.cpp; path = Encodings/MacintoshVolume.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MacintoshVolume.hpp; path = Encodings/MacintoshVolume.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; }; | 		4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = "<group>"; }; | 		4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; }; | 		4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; }; | ||||||
| @@ -1066,6 +1096,7 @@ | |||||||
| 		4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; }; | 		4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; }; | 		4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; }; | ||||||
| 		4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; }; | 		4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; }; | ||||||
|  | 		4B911A9B2337D8AB00A2BB1D /* CSApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSApplication.h; sourceTree = "<group>"; }; | ||||||
| 		4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; }; | 		4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; }; | 		4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; }; | ||||||
| 		4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; }; | 		4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1403,7 +1434,6 @@ | |||||||
| 		4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; }; | 		4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | 		4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | 		4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; }; |  | ||||||
| 		4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = i8272.cpp; path = 8272/i8272.cpp; sourceTree = "<group>"; }; | 		4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = i8272.cpp; path = 8272/i8272.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8272.hpp; path = 8272/i8272.hpp; sourceTree = "<group>"; }; | 		4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8272.hpp; path = 8272/i8272.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; }; | 		4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; }; | ||||||
| @@ -1419,6 +1449,8 @@ | |||||||
| 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | 		4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | 		4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | 		4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; | ||||||
|  | 		4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | 		4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | 		4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | 		4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; }; | ||||||
| @@ -1447,6 +1479,7 @@ | |||||||
| 		4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; }; | 		4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; }; | 		4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; }; | 		4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = "<group>"; }; | ||||||
| 		4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; }; | 		4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; }; | 		4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; }; | ||||||
| 		4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; }; | 		4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; }; | ||||||
| @@ -1478,6 +1511,8 @@ | |||||||
| 		4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; }; | 		4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; }; | ||||||
| 		4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = "<group>"; }; | 		4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = "<group>"; }; | ||||||
| 		4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = "<group>"; }; | 		4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = "<group>"; }; | ||||||
|  | 		4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = "<group>"; }; | ||||||
|  | 		4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; }; | 		4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; }; | 		4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; }; | ||||||
| 		4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; }; | 		4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; }; | ||||||
| @@ -1738,7 +1773,6 @@ | |||||||
| 		4B2A53921D117D36003C6002 /* Machine */ = { | 		4B2A53921D117D36003C6002 /* Machine */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4BBC34241D2208B100FFC9DF /* CSFastLoading.h */, |  | ||||||
| 				4B2A53951D117D36003C6002 /* CSMachine.h */, | 				4B2A53951D117D36003C6002 /* CSMachine.h */, | ||||||
| 				4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */, | 				4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */, | ||||||
| 				4B2A53971D117D36003C6002 /* KeyCodes.h */, | 				4B2A53971D117D36003C6002 /* KeyCodes.h */, | ||||||
| @@ -2103,6 +2137,7 @@ | |||||||
| 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | 				4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, | ||||||
| 				4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */, | 				4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */, | ||||||
| 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | 				4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, | ||||||
|  | 				4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */, | ||||||
| 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | 				4B2A332B1DB86821002876E3 /* OricOptions.xib */, | ||||||
| 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, | 				4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */, | ||||||
| 				4BD61662206B2AC700236112 /* QuickLoadOptions.xib */, | 				4BD61662206B2AC700236112 /* QuickLoadOptions.xib */, | ||||||
| @@ -2168,6 +2203,7 @@ | |||||||
| 				4BEE0A691D72496600532C7B /* Cartridge */, | 				4BEE0A691D72496600532C7B /* Cartridge */, | ||||||
| 				4B8805F81DCFF6CD003085B1 /* Data */, | 				4B8805F81DCFF6CD003085B1 /* Data */, | ||||||
| 				4BAB62AA1D3272D200DF5BA0 /* Disk */, | 				4BAB62AA1D3272D200DF5BA0 /* Disk */, | ||||||
|  | 				4B6AAEA1230E3E1D0078E864 /* MassStorage */, | ||||||
| 				4B69FB3A1C4D908A00B5F0AA /* Tape */, | 				4B69FB3A1C4D908A00B5F0AA /* Tape */, | ||||||
| 			); | 			); | ||||||
| 			name = Storage; | 			name = Storage; | ||||||
| @@ -2231,6 +2267,33 @@ | |||||||
| 			path = Implementation; | 			path = Implementation; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4B6AAEA1230E3E1D0078E864 /* MassStorage */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */, | ||||||
|  | 				4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */, | ||||||
|  | 				4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */, | ||||||
|  | 				4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */, | ||||||
|  | 				4B74CF7E2312FA9C00500CE8 /* Formats */, | ||||||
|  | 				4B6AAEA5230E40250078E864 /* SCSI */, | ||||||
|  | 			); | ||||||
|  | 			path = MassStorage; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
|  | 		4B6AAEA5230E40250078E864 /* SCSI */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */, | ||||||
|  | 				4B6AAEA7230E40250078E864 /* SCSI.cpp */, | ||||||
|  | 				4B6AAEA8230E40250078E864 /* Target.cpp */, | ||||||
|  | 				4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */, | ||||||
|  | 				4B6AAEA9230E40250078E864 /* SCSI.hpp */, | ||||||
|  | 				4B6AAEA6230E40250078E864 /* Target.hpp */, | ||||||
|  | 				4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */, | ||||||
|  | 			); | ||||||
|  | 			path = SCSI; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B7136831F78724F008B8ED9 /* MFM */ = { | 		4B7136831F78724F008B8ED9 /* MFM */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -2249,6 +2312,15 @@ | |||||||
| 			path = Encodings/MFM; | 			path = Encodings/MFM; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4B74CF7E2312FA9C00500CE8 /* Formats */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B74CF7F2312FA9C00500CE8 /* HFV.hpp */, | ||||||
|  | 				4B74CF802312FA9C00500CE8 /* HFV.cpp */, | ||||||
|  | 			); | ||||||
|  | 			path = Formats; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B77069E1EC9045B0053B588 /* Z80 */ = { | 		4B77069E1EC9045B0053B588 /* Z80 */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -2934,22 +3006,24 @@ | |||||||
| 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | 		4BB73EA01B587A5100552FC2 /* Clock Signal */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, |  | ||||||
| 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | 				4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, | ||||||
| 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | 				4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, | ||||||
|  | 				4B911A9B2337D8AB00A2BB1D /* CSApplication.h */, | ||||||
|  | 				4BD0FBC2233706A200148981 /* CSApplication.m */, | ||||||
| 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | 				4BB73EAD1B587A5100552FC2 /* Info.plist */, | ||||||
| 				4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, | 				4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, | ||||||
| 				4BB73EA81B587A5100552FC2 /* Assets.xcassets */, | 				4BB73EA81B587A5100552FC2 /* Assets.xcassets */, | ||||||
| 				4B2A538F1D117D36003C6002 /* Audio */, | 				4B2A538F1D117D36003C6002 /* Audio */, | ||||||
| 				4B643F3D1D77B88000D431D6 /* Document Controller */, | 				4B643F3D1D77B88000D431D6 /* Document Controller */, | ||||||
| 				4B55CE551C3B7D360093A61B /* Documents */, | 				4B55CE551C3B7D360093A61B /* Documents */, | ||||||
|  | 				4BBFE83B21015D9C00BF1C40 /* Joystick Manager */, | ||||||
| 				4B2A53921D117D36003C6002 /* Machine */, | 				4B2A53921D117D36003C6002 /* Machine */, | ||||||
| 				4B55DD7F20DF06680043F2E5 /* MachinePicker */, | 				4B55DD7F20DF06680043F2E5 /* MachinePicker */, | ||||||
| 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, | 				4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, | ||||||
| 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | 				4BE5F85A1C3E1C2500C43F01 /* Resources */, | ||||||
|  | 				4BDA00DB22E60EE900AC3CD0 /* ROMRequester */, | ||||||
| 				4BD5F1961D1352A000631CD1 /* Updater */, | 				4BD5F1961D1352A000631CD1 /* Updater */, | ||||||
| 				4B55CE5A1C3B7D6F0093A61B /* Views */, | 				4B55CE5A1C3B7D6F0093A61B /* Views */, | ||||||
| 				4BDA00DB22E60EE900AC3CD0 /* ROMRequester */, |  | ||||||
| 			); | 			); | ||||||
| 			path = "Clock Signal"; | 			path = "Clock Signal"; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -3108,6 +3182,7 @@ | |||||||
| 		4BC9DF4A1D04691600F44158 /* Components */ = { | 		4BC9DF4A1D04691600F44158 /* Components */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
|  | 				4BDACBE922FFA5B50045EF7E /* 5380 */, | ||||||
| 				4BD468F81D8DF4290084958B /* 1770 */, | 				4BD468F81D8DF4290084958B /* 1770 */, | ||||||
| 				4BC9DF4B1D04691600F44158 /* 6522 */, | 				4BC9DF4B1D04691600F44158 /* 6522 */, | ||||||
| 				4B1E85791D174DEC001EF87D /* 6532 */, | 				4B1E85791D174DEC001EF87D /* 6532 */, | ||||||
| @@ -3182,6 +3257,7 @@ | |||||||
| 			children = ( | 			children = ( | ||||||
| 				4B9378E222A199C600973513 /* Audio.cpp */, | 				4B9378E222A199C600973513 /* Audio.cpp */, | ||||||
| 				4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */, | 				4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */, | ||||||
|  | 				4B65085F22F4CF8D009C1100 /* Keyboard.cpp */, | ||||||
| 				4BCE0058227CFFCA000CA200 /* Macintosh.cpp */, | 				4BCE0058227CFFCA000CA200 /* Macintosh.cpp */, | ||||||
| 				4BCE005E227D39AB000CA200 /* Video.cpp */, | 				4BCE005E227D39AB000CA200 /* Video.cpp */, | ||||||
| 				4B9378E322A199C600973513 /* Audio.hpp */, | 				4B9378E322A199C600973513 /* Audio.hpp */, | ||||||
| @@ -3313,6 +3389,15 @@ | |||||||
| 			path = ROMRequester; | 			path = ROMRequester; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4BDACBE922FFA5B50045EF7E /* 5380 */ = { | ||||||
|  | 			isa = PBXGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */, | ||||||
|  | 				4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */, | ||||||
|  | 			); | ||||||
|  | 			path = 5380; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4BE5F85A1C3E1C2500C43F01 /* Resources */ = { | 		4BE5F85A1C3E1C2500C43F01 /* Resources */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -3503,7 +3588,7 @@ | |||||||
| 			isa = PBXProject; | 			isa = PBXProject; | ||||||
| 			attributes = { | 			attributes = { | ||||||
| 				LastSwiftUpdateCheck = 0700; | 				LastSwiftUpdateCheck = 0700; | ||||||
| 				LastUpgradeCheck = 1030; | 				LastUpgradeCheck = 1100; | ||||||
| 				ORGANIZATIONNAME = "Thomas Harte"; | 				ORGANIZATIONNAME = "Thomas Harte"; | ||||||
| 				TargetAttributes = { | 				TargetAttributes = { | ||||||
| 					4B055A691FAE763F0060FFFF = { | 					4B055A691FAE763F0060FFFF = { | ||||||
| @@ -3572,6 +3657,7 @@ | |||||||
| 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | ||||||
| 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | ||||||
| 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | 				4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */, | ||||||
|  | 				4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */, | ||||||
| 				4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */, | 				4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */, | ||||||
| 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | ||||||
| 				4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */, | 				4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */, | ||||||
| @@ -3900,6 +3986,7 @@ | |||||||
| 				4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */, | 				4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */, | ||||||
| 				4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */, | 				4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */, | 				4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */, | ||||||
|  | 				4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */, | ||||||
| 				4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, | 				4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, | ||||||
| 				4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */, | 				4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */, | ||||||
| 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | 				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, | ||||||
| @@ -3933,6 +4020,7 @@ | |||||||
| 				4B8318B322D3E540006DB630 /* Audio.cpp in Sources */, | 				4B8318B322D3E540006DB630 /* Audio.cpp in Sources */, | ||||||
| 				4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */, | 				4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */, | ||||||
| 				4B89452B201967B4007DE474 /* File.cpp in Sources */, | 				4B89452B201967B4007DE474 /* File.cpp in Sources */, | ||||||
|  | 				4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */, | ||||||
| 				4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */, | 				4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */, | ||||||
| 				4BD424E62193B5830097291A /* Shader.cpp in Sources */, | 				4BD424E62193B5830097291A /* Shader.cpp in Sources */, | ||||||
| 				4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */, | 				4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */, | ||||||
| @@ -3944,11 +4032,14 @@ | |||||||
| 				4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, | 				4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, | ||||||
| 				4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, | 				4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, | ||||||
| 				4B89451D201967B4007DE474 /* Disk.cpp in Sources */, | 				4B89451D201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
|  | 				4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, | ||||||
| 				4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, | 				4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, | ||||||
| 				4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, | 				4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, | ||||||
| 				4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, | 				4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, | 				4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, | ||||||
| 				4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, | 				4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, | ||||||
|  | 				4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, | ||||||
|  | 				4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, | ||||||
| 				4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, | 				4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, | ||||||
| 				4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */, | 				4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */, | ||||||
| 				4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */, | 				4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */, | ||||||
| @@ -3971,6 +4062,7 @@ | |||||||
| 				4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */, | 				4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */, | 				4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */, | ||||||
| 				4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */, | 				4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */, | ||||||
|  | 				4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */, | ||||||
| 				4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */, | 				4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */, | ||||||
| 				4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */, | 				4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */, | ||||||
| 				4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */, | 				4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */, | ||||||
| @@ -4004,6 +4096,7 @@ | |||||||
| 				4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */, | 				4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */, | ||||||
| 				4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */, | 				4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */, | ||||||
| 				4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */, | 				4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */, | ||||||
|  | 				4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, | ||||||
| 				4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */, | 				4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */, | ||||||
| 				4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */, | 				4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */, | ||||||
| 				4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, | 				4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, | ||||||
| @@ -4051,6 +4144,7 @@ | |||||||
| 				4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */, | 				4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */, | ||||||
| 				4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, | 				4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, | ||||||
| 				4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, | 				4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, | ||||||
|  | 				4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */, | ||||||
| 				4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, | 				4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, | ||||||
| 				4B9378E422A199C600973513 /* Audio.cpp in Sources */, | 				4B9378E422A199C600973513 /* Audio.cpp in Sources */, | ||||||
| 				4B89451E201967B4007DE474 /* Tape.cpp in Sources */, | 				4B89451E201967B4007DE474 /* Tape.cpp in Sources */, | ||||||
| @@ -4060,6 +4154,7 @@ | |||||||
| 				4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, | 				4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, | ||||||
| 				4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */, | 				4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, | 				4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, | ||||||
|  | 				4B6AAEAD230E40250078E864 /* Target.cpp in Sources */, | ||||||
| 				4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, | 				4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, | ||||||
| 				4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */, | 				4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */, | ||||||
| 				4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */, | 				4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */, | ||||||
| @@ -4099,8 +4194,10 @@ | |||||||
| 				4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */, | 				4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */, | ||||||
| 				4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, | 				4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, | ||||||
| 				4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */, | 				4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */, | ||||||
|  | 				4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */, | ||||||
| 				4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */, | 				4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */, | ||||||
|  | 				4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */, | ||||||
| 				4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */, | 				4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */, | ||||||
| 				4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, | 				4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, | ||||||
| 				4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, | 				4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, | ||||||
| @@ -4114,6 +4211,7 @@ | |||||||
| 				4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, | 				4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, | ||||||
| 				4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */, | 				4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */, | ||||||
| 				4BCE0060227D39AB000CA200 /* Video.cpp in Sources */, | 				4BCE0060227D39AB000CA200 /* Video.cpp in Sources */, | ||||||
|  | 				4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */, | ||||||
| 				4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */, | 				4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */, | ||||||
| 				4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, | 				4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, | ||||||
| 				4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, | 				4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, | ||||||
| @@ -4160,6 +4258,7 @@ | |||||||
| 				4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */, | 				4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */, | ||||||
| 				4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, | 				4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, | ||||||
| 				4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, | 				4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, | ||||||
|  | 				4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, | ||||||
| 				4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, | 				4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, | ||||||
| 				4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, | 				4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, | ||||||
| @@ -4170,11 +4269,14 @@ | |||||||
| 				4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, | 				4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, | ||||||
| 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | 				4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */, | ||||||
| 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | 				4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, | ||||||
|  | 				4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */, | ||||||
| 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | 				4B894528201967B4007DE474 /* Disk.cpp in Sources */, | ||||||
| 				4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | 				4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */, | ||||||
| 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | 				4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */, | ||||||
| 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | 				4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, | ||||||
|  | 				4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */, | ||||||
| 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | 				4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */, | ||||||
|  | 				4BD0FBC3233706A200148981 /* CSApplication.m in Sources */, | ||||||
| 				4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */, | 				4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */, | ||||||
| 				4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */, | 				4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */, | ||||||
| 				4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */, | 				4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */, | ||||||
| @@ -4332,6 +4434,14 @@ | |||||||
| 			name = OricOptions.xib; | 			name = OricOptions.xib; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
|  | 		4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */ = { | ||||||
|  | 			isa = PBXVariantGroup; | ||||||
|  | 			children = ( | ||||||
|  | 				4B49F0A823346F7A0045E6A6 /* Base */, | ||||||
|  | 			); | ||||||
|  | 			name = MacintoshOptions.xib; | ||||||
|  | 			sourceTree = "<group>"; | ||||||
|  | 		}; | ||||||
| 		4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = { | 		4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = { | ||||||
| 			isa = PBXVariantGroup; | 			isa = PBXVariantGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -4407,6 +4517,7 @@ | |||||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; | ||||||
| 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | ||||||
| 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | ||||||
|  | 				CODE_SIGN_IDENTITY = "-"; | ||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				FRAMEWORK_SEARCH_PATHS = ( | 				FRAMEWORK_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| @@ -4426,6 +4537,7 @@ | |||||||
| 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; | ||||||
| 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES; | ||||||
| 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; | ||||||
|  | 				CODE_SIGN_IDENTITY = "-"; | ||||||
| 				CODE_SIGN_STYLE = Automatic; | 				CODE_SIGN_STYLE = Automatic; | ||||||
| 				FRAMEWORK_SEARCH_PATHS = ( | 				FRAMEWORK_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| @@ -4555,6 +4667,7 @@ | |||||||
| 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; | 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; | ||||||
| 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; | 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; | ||||||
| 				CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; | 				CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; | ||||||
|  | 				CODE_SIGN_IDENTITY = "-"; | ||||||
| 				COMBINE_HIDPI_IMAGES = YES; | 				COMBINE_HIDPI_IMAGES = YES; | ||||||
| 				FRAMEWORK_SEARCH_PATHS = ( | 				FRAMEWORK_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| @@ -4596,6 +4709,7 @@ | |||||||
| 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; | 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; | ||||||
| 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; | 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; | ||||||
| 				CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; | 				CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; | ||||||
|  | 				CODE_SIGN_IDENTITY = "-"; | ||||||
| 				COMBINE_HIDPI_IMAGES = YES; | 				COMBINE_HIDPI_IMAGES = YES; | ||||||
| 				FRAMEWORK_SEARCH_PATHS = ( | 				FRAMEWORK_SEARCH_PATHS = ( | ||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
|   | |||||||
| @@ -1,9 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <Workspace | <Workspace | ||||||
|    version = "1.0"> |    version = "1.0"> | ||||||
|    <FileRef |  | ||||||
|       location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm"> |  | ||||||
|    </FileRef> |  | ||||||
|    <FileRef |    <FileRef | ||||||
|       location = "self:Clock Signal.xcodeproj"> |       location = "self:Clock Signal.xcodeproj"> | ||||||
|    </FileRef> |    </FileRef> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <Scheme | <Scheme | ||||||
|    LastUpgradeVersion = "1030" |    LastUpgradeVersion = "1100" | ||||||
|    version = "1.3"> |    version = "1.3"> | ||||||
|    <BuildAction |    <BuildAction | ||||||
|       parallelizeBuildables = "YES" |       parallelizeBuildables = "YES" | ||||||
| @@ -26,9 +26,18 @@ | |||||||
|       buildConfiguration = "Debug" |       buildConfiguration = "Debug" | ||||||
|       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||||||
|       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||||||
|  |       shouldUseLaunchSchemeArgsEnv = "YES" | ||||||
|       disableMainThreadChecker = "YES" |       disableMainThreadChecker = "YES" | ||||||
|       codeCoverageEnabled = "YES" |       codeCoverageEnabled = "YES"> | ||||||
|       shouldUseLaunchSchemeArgsEnv = "YES"> |       <MacroExpansion> | ||||||
|  |          <BuildableReference | ||||||
|  |             BuildableIdentifier = "primary" | ||||||
|  |             BlueprintIdentifier = "4BB73E9D1B587A5100552FC2" | ||||||
|  |             BuildableName = "Clock Signal.app" | ||||||
|  |             BlueprintName = "Clock Signal" | ||||||
|  |             ReferencedContainer = "container:Clock Signal.xcodeproj"> | ||||||
|  |          </BuildableReference> | ||||||
|  |       </MacroExpansion> | ||||||
|       <Testables> |       <Testables> | ||||||
|          <TestableReference |          <TestableReference | ||||||
|             skipped = "NO"> |             skipped = "NO"> | ||||||
| @@ -56,17 +65,6 @@ | |||||||
|             </BuildableReference> |             </BuildableReference> | ||||||
|          </TestableReference> |          </TestableReference> | ||||||
|       </Testables> |       </Testables> | ||||||
|       <MacroExpansion> |  | ||||||
|          <BuildableReference |  | ||||||
|             BuildableIdentifier = "primary" |  | ||||||
|             BlueprintIdentifier = "4BB73E9D1B587A5100552FC2" |  | ||||||
|             BuildableName = "Clock Signal.app" |  | ||||||
|             BlueprintName = "Clock Signal" |  | ||||||
|             ReferencedContainer = "container:Clock Signal.xcodeproj"> |  | ||||||
|          </BuildableReference> |  | ||||||
|       </MacroExpansion> |  | ||||||
|       <AdditionalOptions> |  | ||||||
|       </AdditionalOptions> |  | ||||||
|    </TestAction> |    </TestAction> | ||||||
|    <LaunchAction |    <LaunchAction | ||||||
|       buildConfiguration = "Debug" |       buildConfiguration = "Debug" | ||||||
| @@ -78,8 +76,7 @@ | |||||||
|       useCustomWorkingDirectory = "NO" |       useCustomWorkingDirectory = "NO" | ||||||
|       ignoresPersistentStateOnLaunch = "NO" |       ignoresPersistentStateOnLaunch = "NO" | ||||||
|       debugDocumentVersioning = "YES" |       debugDocumentVersioning = "YES" | ||||||
|       stopOnEveryThreadSanitizerIssue = "YES" |       migratedStopOnEveryIssue = "YES" | ||||||
|       stopOnEveryUBSanitizerIssue = "YES" |  | ||||||
|       debugServiceExtension = "internal" |       debugServiceExtension = "internal" | ||||||
|       allowLocationSimulation = "NO"> |       allowLocationSimulation = "NO"> | ||||||
|       <BuildableProductRunnable |       <BuildableProductRunnable | ||||||
| @@ -92,8 +89,6 @@ | |||||||
|             ReferencedContainer = "container:Clock Signal.xcodeproj"> |             ReferencedContainer = "container:Clock Signal.xcodeproj"> | ||||||
|          </BuildableReference> |          </BuildableReference> | ||||||
|       </BuildableProductRunnable> |       </BuildableProductRunnable> | ||||||
|       <AdditionalOptions> |  | ||||||
|       </AdditionalOptions> |  | ||||||
|    </LaunchAction> |    </LaunchAction> | ||||||
|    <ProfileAction |    <ProfileAction | ||||||
|       buildConfiguration = "Release" |       buildConfiguration = "Release" | ||||||
|   | |||||||
| @@ -12,15 +12,26 @@ import Cocoa | |||||||
| class AppDelegate: NSObject, NSApplicationDelegate { | class AppDelegate: NSObject, NSApplicationDelegate { | ||||||
|  |  | ||||||
| 	func applicationDidFinishLaunching(_ aNotification: Notification) { | 	func applicationDidFinishLaunching(_ aNotification: Notification) { | ||||||
| 		// Insert code here to initialize your application | 		// Insert code here to initialize your application. | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	func applicationWillTerminate(_ aNotification: Notification) { | 	func applicationWillTerminate(_ aNotification: Notification) { | ||||||
| 		// Insert code here to tear down your application | 		// Insert code here to tear down your application. | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// decline to open a new file unless the user explicitly requests it | 	private var hasShownOpenDocument = false | ||||||
| 	func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { | 	func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { | ||||||
|  | 		// Decline to show the 'New...' selector by default; the 'Open...' | ||||||
|  | 		// dialogue has already been shown if this application was started | ||||||
|  | 		// without a file. | ||||||
|  | 		// | ||||||
|  | 		// Obiter: I dislike it when other applications do this for me, but it | ||||||
|  | 		// seems to be the new norm, and I've had user feedback that showing | ||||||
|  | 		// nothing is confusing. So here it is. | ||||||
|  | 		if !hasShownOpenDocument { | ||||||
|  | 			NSDocumentController.shared.openDocument(self) | ||||||
|  | 			hasShownOpenDocument = true | ||||||
|  | 		} | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/MacintoshOptions.xib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								OSBindings/Mac/Clock Signal/Base.lproj/MacintoshOptions.xib
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|  |     <dependencies> | ||||||
|  |         <deployment identifier="macosx"/> | ||||||
|  |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> | ||||||
|  |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|  |     </dependencies> | ||||||
|  |     <objects> | ||||||
|  |         <customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/> | ||||||
|  |             </connections> | ||||||
|  |         </customObject> | ||||||
|  |         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||||||
|  |         <customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||||||
|  |         <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target"> | ||||||
|  |             <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> | ||||||
|  |             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> | ||||||
|  |             <rect key="contentRect" x="80" y="150" width="200" height="54"/> | ||||||
|  |             <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> | ||||||
|  |             <view key="contentView" id="tpZ-0B-QQu"> | ||||||
|  |                 <rect key="frame" x="0.0" y="0.0" width="200" height="54"/> | ||||||
|  |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|  |                 <subviews> | ||||||
|  |                     <button translatesAutoresizingMaskIntoConstraints="NO" id="zPG-yW-4Gy"> | ||||||
|  |                         <rect key="frame" x="18" y="18" width="164" height="18"/> | ||||||
|  |                         <buttonCell key="cell" type="check" title="Start Quickly" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="alI-Mw-35c"> | ||||||
|  |                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> | ||||||
|  |                             <font key="font" metaFont="system"/> | ||||||
|  |                         </buttonCell> | ||||||
|  |                         <connections> | ||||||
|  |                             <action selector="setFastBooting:" target="ZW7-Bw-4RP" id="AgA-2q-qUU"/> | ||||||
|  |                         </connections> | ||||||
|  |                     </button> | ||||||
|  |                 </subviews> | ||||||
|  |                 <constraints> | ||||||
|  |                     <constraint firstAttribute="bottom" secondItem="zPG-yW-4Gy" secondAttribute="bottom" constant="20" id="7u0-BP-FXG"/> | ||||||
|  |                     <constraint firstAttribute="trailing" secondItem="zPG-yW-4Gy" secondAttribute="trailing" constant="20" id="Mtb-hf-4ap"/> | ||||||
|  |                     <constraint firstItem="zPG-yW-4Gy" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="imk-5k-8nm"/> | ||||||
|  |                     <constraint firstItem="zPG-yW-4Gy" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="jAt-iF-uaT"/> | ||||||
|  |                 </constraints> | ||||||
|  |             </view> | ||||||
|  |             <connections> | ||||||
|  |                 <outlet property="fastBootingButton" destination="zPG-yW-4Gy" id="3Mq-l2-NEp"/> | ||||||
|  |             </connections> | ||||||
|  |             <point key="canvasLocation" x="-50" y="2"/> | ||||||
|  |         </window> | ||||||
|  |     </objects> | ||||||
|  | </document> | ||||||
							
								
								
									
										33
									
								
								OSBindings/Mac/Clock Signal/CSApplication.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								OSBindings/Mac/Clock Signal/CSApplication.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // | ||||||
|  | //  CSApplication.h | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/09/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef CSApplication_h | ||||||
|  | #define CSApplication_h | ||||||
|  |  | ||||||
|  | #import <Cocoa/Cocoa.h> | ||||||
|  |  | ||||||
|  | @class CSApplication; | ||||||
|  |  | ||||||
|  | @protocol CSApplicationEventDelegate | ||||||
|  | - (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	CSApplication differs from NSApplication in only one regard: it supports an eventDelegate. | ||||||
|  |  | ||||||
|  | 	If conected, an eventDelegate will be offered all application events prior to their propagation | ||||||
|  | 	into the application proper. It may opt to remove those events from the queue. This primarily | ||||||
|  | 	provides a way to divert things like the command key that will otherwise trigger menu | ||||||
|  | 	shortcuts, for periods when it is appropriate to do so. | ||||||
|  | */ | ||||||
|  | @interface CSApplication: NSApplication | ||||||
|  | @property(nonatomic, weak, nullable) id<CSApplicationEventDelegate> eventDelegate; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* CSApplication_h */ | ||||||
							
								
								
									
										20
									
								
								OSBindings/Mac/Clock Signal/CSApplication.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								OSBindings/Mac/Clock Signal/CSApplication.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | // | ||||||
|  | //  Application.m | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/09/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #import "CSApplication.h" | ||||||
|  |  | ||||||
|  | @implementation CSApplication | ||||||
|  |  | ||||||
|  | - (void)sendEvent:(NSEvent *)event { | ||||||
|  | 	// Send the event unless an event delegate says otherwise. | ||||||
|  | 	if(!self.eventDelegate || [self.eventDelegate application:self shouldSendEvent:event]) { | ||||||
|  | 		[super sendEvent:event]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -3,7 +3,6 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #import "CSMachine.h" | #import "CSMachine.h" | ||||||
| #import "CSFastLoading.h" |  | ||||||
|  |  | ||||||
| #import "CSAtari2600.h" | #import "CSAtari2600.h" | ||||||
| #import "CSZX8081.h" | #import "CSZX8081.h" | ||||||
|   | |||||||
| @@ -154,6 +154,11 @@ class MachineDocument: | |||||||
| 	// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window | 	// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window | ||||||
| 	// is visible, though it's a little premature. | 	// is visible, though it's a little premature. | ||||||
| 	func windowDidUpdate(_ notification: Notification) { | 	func windowDidUpdate(_ notification: Notification) { | ||||||
|  | 		// Grab the regular window title, if it's not already stored. | ||||||
|  | 		if self.unadornedWindowTitle.count == 0 { | ||||||
|  | 			self.unadornedWindowTitle = self.windowControllers[0].window!.title | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing. | 		// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing. | ||||||
| 		if self.interactionMode == .notStarted { | 		if self.interactionMode == .notStarted { | ||||||
| 			// If a full machine exists, just continue showing it. | 			// If a full machine exists, just continue showing it. | ||||||
| @@ -204,8 +209,10 @@ class MachineDocument: | |||||||
| 			openGLView.delegate = self | 			openGLView.delegate = self | ||||||
| 			openGLView.responderDelegate = self | 			openGLView.responderDelegate = self | ||||||
|  |  | ||||||
| 			// If this machine has a mouse, enable mouse capture. | 			// If this machine has a mouse, enable mouse capture; also indicate whether usurption | ||||||
|  | 			// of the command key is desired. | ||||||
| 			openGLView.shouldCaptureMouse = machine.hasMouse | 			openGLView.shouldCaptureMouse = machine.hasMouse | ||||||
|  | 			openGLView.shouldUsurpCommand = machine.shouldUsurpCommand | ||||||
|  |  | ||||||
| 			setupAudioQueueClockRate() | 			setupAudioQueueClockRate() | ||||||
|  |  | ||||||
| @@ -613,7 +620,17 @@ class MachineDocument: | |||||||
| 		try! pngData?.write(to: url) | 		try! pngData?.write(to: url) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// MARK: Activity display. | 	// MARK: - Window Title Updates. | ||||||
|  | 	private var unadornedWindowTitle = "" | ||||||
|  | 	func openGLViewDidCaptureMouse(_ view: CSOpenGLView) { | ||||||
|  | 		self.windowControllers[0].window?.title = self.unadornedWindowTitle + " (press ⌘+control to release mouse)" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	func openGLViewDidReleaseMouse(_ view: CSOpenGLView) { | ||||||
|  | 		self.windowControllers[0].window?.title = self.unadornedWindowTitle | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: - Activity Display. | ||||||
|  |  | ||||||
| 	private class LED { | 	private class LED { | ||||||
| 		let levelIndicator: NSLevelIndicator | 		let levelIndicator: NSLevelIndicator | ||||||
|   | |||||||
| @@ -16,18 +16,29 @@ class MachinePanel: NSPanel { | |||||||
| 		return "\(self.machine.userDefaultsPrefix).\(key)" | 		return "\(self.machine.userDefaultsPrefix).\(key)" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Fast Loading | ||||||
| 	var fastLoadingUserDefaultsKey: String { | 	var fastLoadingUserDefaultsKey: String { | ||||||
| 		return prefixedUserDefaultsKey("fastLoading") | 		return prefixedUserDefaultsKey("fastLoading") | ||||||
| 	} | 	} | ||||||
| 	@IBOutlet var fastLoadingButton: NSButton? | 	@IBOutlet var fastLoadingButton: NSButton? | ||||||
| 	@IBAction func setFastLoading(_ sender: NSButton!) { | 	@IBAction func setFastLoading(_ sender: NSButton!) { | ||||||
| 		if let fastLoadingMachine = machine as? CSFastLoading { | 		let useFastLoadingHack = sender.state == .on | ||||||
| 			let useFastLoadingHack = sender.state == .on | 		machine.useFastLoadingHack = useFastLoadingHack | ||||||
| 			fastLoadingMachine.useFastLoadingHack = useFastLoadingHack | 		UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey) | ||||||
| 			UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Quick Boot | ||||||
|  | 	var bootQuicklyUserDefaultsKey: String { | ||||||
|  | 		return prefixedUserDefaultsKey("bootQuickly") | ||||||
|  | 	} | ||||||
|  | 	@IBOutlet var fastBootingButton: NSButton? | ||||||
|  | 	@IBAction func setFastBooting(_ sender: NSButton!) { | ||||||
|  | 		let useQuickBootingHack = sender.state == .on | ||||||
|  | 		machine.useQuickBootingHack = useQuickBootingHack | ||||||
|  | 		UserDefaults.standard.set(useQuickBootingHack, forKey: bootQuicklyUserDefaultsKey) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Display-Type Selection | ||||||
| 	fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal { | 	fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal { | ||||||
| 		switch tag { | 		switch tag { | ||||||
| 			case 1: return .composite | 			case 1: return .composite | ||||||
| @@ -49,17 +60,25 @@ class MachinePanel: NSPanel { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// MARK: Restoring user defaults | ||||||
| 	func establishStoredOptions() { | 	func establishStoredOptions() { | ||||||
| 		let standardUserDefaults = UserDefaults.standard | 		let standardUserDefaults = UserDefaults.standard | ||||||
| 		standardUserDefaults.register(defaults: [ | 		standardUserDefaults.register(defaults: [ | ||||||
| 			fastLoadingUserDefaultsKey: true, | 			fastLoadingUserDefaultsKey: true, | ||||||
|  | 			bootQuicklyUserDefaultsKey: true, | ||||||
| 			displayTypeUserDefaultsKey: 0 | 			displayTypeUserDefaultsKey: 0 | ||||||
| 		]) | 		]) | ||||||
|  |  | ||||||
| 		if let fastLoadingMachine = machine as? CSFastLoading { | 		if let fastLoadingButton = self.fastLoadingButton { | ||||||
| 			let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey) | 			let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey) | ||||||
| 			fastLoadingMachine.useFastLoadingHack = useFastLoadingHack | 			machine.useFastLoadingHack = useFastLoadingHack | ||||||
| 			self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off | 			fastLoadingButton.state = useFastLoadingHack ? .on : .off | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if let fastBootingButton = self.fastBootingButton { | ||||||
|  | 			let bootQuickly = standardUserDefaults.bool(forKey: self.bootQuicklyUserDefaultsKey) | ||||||
|  | 			machine.useQuickBootingHack = bootQuickly | ||||||
|  | 			fastBootingButton.state = bootQuickly ? .on : .off | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if let displayTypeButton = self.displayTypeButton { | 		if let displayTypeButton = self.displayTypeButton { | ||||||
|   | |||||||
| @@ -554,6 +554,6 @@ | |||||||
| 	<key>NSMainNibFile</key> | 	<key>NSMainNibFile</key> | ||||||
| 	<string>MainMenu</string> | 	<string>MainMenu</string> | ||||||
| 	<key>NSPrincipalClass</key> | 	<key>NSPrincipalClass</key> | ||||||
| 	<string>NSApplication</string> | 	<string>CSApplication</string> | ||||||
| </dict> | </dict> | ||||||
| </plist> | </plist> | ||||||
|   | |||||||
| @@ -1,11 +0,0 @@ | |||||||
| // |  | ||||||
| //  CSFastLoading.h |  | ||||||
| //  Clock Signal |  | ||||||
| // |  | ||||||
| //  Created by Thomas Harte on 05/06/2016. |  | ||||||
| //  Copyright 2016 Thomas Harte. All rights reserved. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| @protocol CSFastLoading <NSObject> |  | ||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; |  | ||||||
| @end |  | ||||||
| @@ -9,7 +9,6 @@ | |||||||
| #import <Foundation/Foundation.h> | #import <Foundation/Foundation.h> | ||||||
|  |  | ||||||
| #import "CSAudioQueue.h" | #import "CSAudioQueue.h" | ||||||
| #import "CSFastLoading.h" |  | ||||||
| #import "CSOpenGLView.h" | #import "CSOpenGLView.h" | ||||||
| #import "CSStaticAnalyser.h" | #import "CSStaticAnalyser.h" | ||||||
| #import "CSJoystickManager.h" | #import "CSJoystickManager.h" | ||||||
| @@ -86,6 +85,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
| @property (nonatomic, assign) BOOL useFastLoadingHack; | @property (nonatomic, assign) BOOL useFastLoadingHack; | ||||||
| @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | @property (nonatomic, assign) CSMachineVideoSignal videoSignal; | ||||||
| @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | @property (nonatomic, assign) BOOL useAutomaticTapeMotorControl; | ||||||
|  | @property (nonatomic, assign) BOOL useQuickBootingHack; | ||||||
|  |  | ||||||
| @property (nonatomic, readonly) BOOL canInsertMedia; | @property (nonatomic, readonly) BOOL canInsertMedia; | ||||||
|  |  | ||||||
| @@ -93,6 +93,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { | |||||||
|  |  | ||||||
| // Input control. | // Input control. | ||||||
| @property (nonatomic, readonly) BOOL hasExclusiveKeyboard; | @property (nonatomic, readonly) BOOL hasExclusiveKeyboard; | ||||||
|  | @property (nonatomic, readonly) BOOL shouldUsurpCommand; | ||||||
| @property (nonatomic, readonly) BOOL hasJoystick; | @property (nonatomic, readonly) BOOL hasJoystick; | ||||||
| @property (nonatomic, readonly) BOOL hasMouse; | @property (nonatomic, readonly) BOOL hasMouse; | ||||||
| @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ | |||||||
| #include "../../../../Outputs/OpenGL/ScanTarget.hpp" | #include "../../../../Outputs/OpenGL/ScanTarget.hpp" | ||||||
| #include "../../../../Outputs/OpenGL/Screenshot.hpp" | #include "../../../../Outputs/OpenGL/Screenshot.hpp" | ||||||
|  |  | ||||||
| @interface CSMachine() <CSFastLoading> | @interface CSMachine() | ||||||
| - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; | - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; | ||||||
| - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; | - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; | ||||||
| - (void)addLED:(NSString *)led; | - (void)addLED:(NSString *)led; | ||||||
| @@ -131,7 +131,7 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| } | } | ||||||
|  |  | ||||||
| - (NSString *)description { | - (NSString *)description { | ||||||
| 	return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s]; | 	return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s]; | ||||||
| } | } | ||||||
|  |  | ||||||
| @end | @end | ||||||
| @@ -415,7 +415,7 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 			BIND(VK_ANSI_Quote, Quote);							BIND(VK_ANSI_Grave, BackTick); | 			BIND(VK_ANSI_Quote, Quote);							BIND(VK_ANSI_Grave, BackTick); | ||||||
|  |  | ||||||
| 			BIND(VK_ANSI_Semicolon, Semicolon); | 			BIND(VK_ANSI_Semicolon, Semicolon); | ||||||
| 			BIND(VK_ANSI_Backslash, BackSlash);					BIND(VK_ANSI_Slash, ForwardSlash); | 			BIND(VK_ANSI_Backslash, Backslash);					BIND(VK_ANSI_Slash, ForwardSlash); | ||||||
| 			BIND(VK_ANSI_Comma, Comma);							BIND(VK_ANSI_Period, FullStop); | 			BIND(VK_ANSI_Comma, Comma);							BIND(VK_ANSI_Period, FullStop); | ||||||
|  |  | ||||||
| 			BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint);	BIND(VK_ANSI_KeypadEquals, KeyPadEquals); | 			BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint);	BIND(VK_ANSI_KeypadEquals, KeyPadEquals); | ||||||
| @@ -424,7 +424,7 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 			BIND(VK_ANSI_KeypadClear, KeyPadDelete);			BIND(VK_ANSI_KeypadEnter, KeyPadEnter); | 			BIND(VK_ANSI_KeypadClear, KeyPadDelete);			BIND(VK_ANSI_KeypadEnter, KeyPadEnter); | ||||||
|  |  | ||||||
| 			BIND(VK_Return, Enter);					BIND(VK_Tab, Tab); | 			BIND(VK_Return, Enter);					BIND(VK_Tab, Tab); | ||||||
| 			BIND(VK_Space, Space);					BIND(VK_Delete, BackSpace); | 			BIND(VK_Space, Space);					BIND(VK_Delete, Backspace); | ||||||
| 			BIND(VK_Control, LeftControl);			BIND(VK_Option, LeftOption); | 			BIND(VK_Control, LeftControl);			BIND(VK_Option, LeftOption); | ||||||
| 			BIND(VK_Command, LeftMeta);				BIND(VK_Shift, LeftShift); | 			BIND(VK_Command, LeftMeta);				BIND(VK_Shift, LeftShift); | ||||||
| 			BIND(VK_RightControl, RightControl);	BIND(VK_RightOption, RightOption); | 			BIND(VK_RightControl, RightControl);	BIND(VK_RightOption, RightOption); | ||||||
| @@ -624,6 +624,19 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (void)setUseQuickBootingHack:(BOOL)useQuickBootingHack { | ||||||
|  | 	Configurable::Device *configurable_device = _machine->configurable_device(); | ||||||
|  | 	if(!configurable_device) return; | ||||||
|  |  | ||||||
|  | 	@synchronized(self) { | ||||||
|  | 		_useQuickBootingHack = useQuickBootingHack; | ||||||
|  |  | ||||||
|  | 		Configurable::SelectionSet selection_set; | ||||||
|  | 		append_quick_boot_selection(selection_set, useQuickBootingHack ? true : false); | ||||||
|  | 		configurable_device->set_selections(selection_set); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| - (NSString *)userDefaultsPrefix { | - (NSString *)userDefaultsPrefix { | ||||||
| 	// Assumes that the first machine in the targets list is the source of user defaults. | 	// Assumes that the first machine in the targets list is the source of user defaults. | ||||||
| 	std::string name = Machine::ShortNameForTargetMachine(_analyser.targets.front()->machine); | 	std::string name = Machine::ShortNameForTargetMachine(_analyser.targets.front()->machine); | ||||||
| @@ -658,6 +671,14 @@ struct ActivityObserver: public Activity::Observer { | |||||||
| 	return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive(); | 	return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | - (BOOL)shouldUsurpCommand { | ||||||
|  | 	if(!_machine->keyboard_machine()) return NO; | ||||||
|  |  | ||||||
|  | 	const auto essential_modifiers = _machine->keyboard_machine()->get_keyboard().get_essential_modifiers(); | ||||||
|  | 	return	essential_modifiers.find(Inputs::Keyboard::Key::LeftMeta) != essential_modifiers.end() || | ||||||
|  | 			essential_modifiers.find(Inputs::Keyboard::Key::RightMeta) != essential_modifiers.end(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #pragma mark - Activity observation | #pragma mark - Activity observation | ||||||
|  |  | ||||||
| - (void)addLED:(NSString *)led { | - (void)addLED:(NSString *)led { | ||||||
|   | |||||||
| @@ -219,6 +219,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K | |||||||
| 		case Analyser::Machine::Atari2600:		return @"Atari2600Options"; | 		case Analyser::Machine::Atari2600:		return @"Atari2600Options"; | ||||||
| 		case Analyser::Machine::ColecoVision:	return @"CompositeOptions"; | 		case Analyser::Machine::ColecoVision:	return @"CompositeOptions"; | ||||||
| 		case Analyser::Machine::Electron:		return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::Electron:		return @"QuickLoadCompositeOptions"; | ||||||
|  | 		case Analyser::Machine::Macintosh:		return @"MacintoshOptions"; | ||||||
| 		case Analyser::Machine::MasterSystem:	return @"CompositeOptions"; | 		case Analyser::Machine::MasterSystem:	return @"CompositeOptions"; | ||||||
| 		case Analyser::Machine::MSX:			return @"QuickLoadCompositeOptions"; | 		case Analyser::Machine::MSX:			return @"QuickLoadCompositeOptions"; | ||||||
| 		case Analyser::Machine::Oric:			return @"OricOptions"; | 		case Analyser::Machine::Oric:			return @"OricOptions"; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -50,7 +50,7 @@ Gw | |||||||
|                         </connections> |                         </connections> | ||||||
|                     </button> |                     </button> | ||||||
|                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0"> |                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0"> | ||||||
|                         <rect key="frame" x="20" y="14" width="398" height="34"/> |                         <rect key="frame" x="20" y="15" width="398" height="32"/> | ||||||
|                         <textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5"> |                         <textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5"> | ||||||
|                             <font key="font" metaFont="system"/> |                             <font key="font" metaFont="system"/> | ||||||
|                             <string key="title">If you use File -> Open... to select a disk, tape or cartridge directly, the emulator will select and configure a machine for you.</string> |                             <string key="title">If you use File -> Open... to select a disk, tape or cartridge directly, the emulator will select and configure a machine for you.</string> | ||||||
| @@ -68,7 +68,7 @@ Gw | |||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="46" height="17"/> |                                             <rect key="frame" x="15" y="73" width="46" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -76,7 +76,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6"> | ||||||
|                                             <rect key="frame" x="15" y="41" width="96" height="17"/> |                                             <rect key="frame" x="15" y="42" width="96" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -148,7 +148,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="46" height="17"/> |                                             <rect key="frame" x="15" y="73" width="46" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -202,20 +202,35 @@ Gw | |||||||
|                                     <rect key="frame" x="10" y="33" width="604" height="94"/> |                                     <rect key="frame" x="10" y="33" width="604" height="94"/> | ||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl"> | ||||||
|                                             <rect key="frame" x="15" y="74" width="574" height="17"/> |                                             <rect key="frame" x="15" y="73" width="46" height="16"/> | ||||||
|                                             <textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j"> | ||||||
|                                                 <font key="font" usesAppearanceFont="YES"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
|                                                 <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|  |                                         <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5"> | ||||||
|  |                                             <rect key="frame" x="65" y="67" width="74" height="25"/> | ||||||
|  |                                             <popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="proportionallyDown" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM"> | ||||||
|  |                                                 <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> | ||||||
|  |                                                 <font key="font" metaFont="menu"/> | ||||||
|  |                                                 <menu key="menu" id="ofy-j9-YnU"> | ||||||
|  |                                                     <items> | ||||||
|  |                                                         <menuItem title="512ke" tag="2" id="WCG-6u-ANQ"/> | ||||||
|  |                                                         <menuItem title="Plus" state="on" tag="3" id="R6T-hg-rOF"/> | ||||||
|  |                                                     </items> | ||||||
|  |                                                 </menu> | ||||||
|  |                                             </popUpButtonCell> | ||||||
|  |                                         </popUpButton> | ||||||
|                                     </subviews> |                                     </subviews> | ||||||
|                                     <constraints> |                                     <constraints> | ||||||
|                                         <constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/> |                                         <constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="3hY-Ca-mnR"/> | ||||||
|                                         <constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/> |                                         <constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="5s6-87-VT6"/> | ||||||
|                                         <constraint firstItem="F6e-UC-25u" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="cxs-OP-oH5"/> |                                         <constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="3" id="KYf-GJ-Y7k"/> | ||||||
|                                         <constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/> |                                         <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="17" id="LZ5-xH-fU0"/> | ||||||
|  |                                         <constraint firstItem="ZOY-4E-Cfl" firstAttribute="centerY" secondItem="xa6-NA-JY5" secondAttribute="centerY" id="Wa5-KX-3Me"/> | ||||||
|  |                                         <constraint firstItem="xa6-NA-JY5" firstAttribute="leading" secondItem="ZOY-4E-Cfl" secondAttribute="trailing" constant="8" id="ktS-sr-F8L"/> | ||||||
|                                     </constraints> |                                     </constraints> | ||||||
|                                 </view> |                                 </view> | ||||||
|                             </tabViewItem> |                             </tabViewItem> | ||||||
| @@ -246,7 +261,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="50" height="17"/> |                                             <rect key="frame" x="15" y="73" width="50" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -273,7 +288,7 @@ Gw | |||||||
|                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||||||
|                                     <subviews> |                                     <subviews> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="46" height="17"/> |                                             <rect key="frame" x="15" y="73" width="46" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -309,7 +324,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF"> | ||||||
|                                             <rect key="frame" x="15" y="41" width="92" height="17"/> |                                             <rect key="frame" x="15" y="42" width="92" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -368,7 +383,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="50" height="17"/> |                                             <rect key="frame" x="15" y="73" width="50" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -376,7 +391,7 @@ Gw | |||||||
|                                             </textFieldCell> |                                             </textFieldCell> | ||||||
|                                         </textField> |                                         </textField> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy"> | ||||||
|                                             <rect key="frame" x="15" y="41" width="87" height="17"/> |                                             <rect key="frame" x="15" y="42" width="87" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -429,7 +444,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="87" height="17"/> |                                             <rect key="frame" x="15" y="73" width="87" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -477,7 +492,7 @@ Gw | |||||||
|                                             </popUpButtonCell> |                                             </popUpButtonCell> | ||||||
|                                         </popUpButton> |                                         </popUpButton> | ||||||
|                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE"> |                                         <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE"> | ||||||
|                                             <rect key="frame" x="15" y="72" width="87" height="17"/> |                                             <rect key="frame" x="15" y="73" width="87" height="16"/> | ||||||
|                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2"> |                                             <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2"> | ||||||
|                                                 <font key="font" metaFont="system"/> |                                                 <font key="font" metaFont="system"/> | ||||||
|                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -523,6 +538,7 @@ Gw | |||||||
|                 <outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/> |                 <outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/> | ||||||
|                 <outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/> |                 <outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/> | ||||||
|                 <outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/> |                 <outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/> | ||||||
|  |                 <outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/> | ||||||
|                 <outlet property="msxHasDiskDriveButton" destination="8xT-Pr-8SE" id="zGH-GA-9QF"/> |                 <outlet property="msxHasDiskDriveButton" destination="8xT-Pr-8SE" id="zGH-GA-9QF"/> | ||||||
|                 <outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/> |                 <outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/> | ||||||
|                 <outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/> |                 <outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/> | ||||||
|   | |||||||
| @@ -22,6 +22,9 @@ class MachinePicker: NSObject { | |||||||
| 	// MARK: - CPC properties | 	// MARK: - CPC properties | ||||||
| 	@IBOutlet var cpcModelTypeButton: NSPopUpButton? | 	@IBOutlet var cpcModelTypeButton: NSPopUpButton? | ||||||
|  |  | ||||||
|  | 	// MARK: - Macintosh properties | ||||||
|  | 	@IBOutlet var macintoshModelTypeButton: NSPopUpButton? | ||||||
|  |  | ||||||
| 	// MARK: - MSX properties | 	// MARK: - MSX properties | ||||||
| 	@IBOutlet var msxRegionButton: NSPopUpButton? | 	@IBOutlet var msxRegionButton: NSPopUpButton? | ||||||
| 	@IBOutlet var msxHasDiskDriveButton: NSButton? | 	@IBOutlet var msxHasDiskDriveButton: NSButton? | ||||||
| @@ -62,6 +65,9 @@ class MachinePicker: NSObject { | |||||||
| 		// CPC settings | 		// CPC settings | ||||||
| 		cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel")) | 		cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel")) | ||||||
|  |  | ||||||
|  | 		// Macintosh settings | ||||||
|  | 		macintoshModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel")) | ||||||
|  |  | ||||||
| 		// MSX settings | 		// MSX settings | ||||||
| 		msxRegionButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxRegion")) | 		msxRegionButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxRegion")) | ||||||
| 		msxHasDiskDriveButton?.state = standardUserDefaults.bool(forKey: "new.msxDiskDrive") ? .on : .off | 		msxHasDiskDriveButton?.state = standardUserDefaults.bool(forKey: "new.msxDiskDrive") ? .on : .off | ||||||
| @@ -100,6 +106,9 @@ class MachinePicker: NSObject { | |||||||
| 		// CPC settings | 		// CPC settings | ||||||
| 		standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel") | 		standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel") | ||||||
|  |  | ||||||
|  | 		// Macintosh settings | ||||||
|  | 		standardUserDefaults.set(macintoshModelTypeButton!.selectedTag(), forKey: "new.macintoshModel") | ||||||
|  |  | ||||||
| 		// MSX settings | 		// MSX settings | ||||||
| 		standardUserDefaults.set(msxRegionButton!.selectedTag(), forKey: "new.msxRegion") | 		standardUserDefaults.set(msxRegionButton!.selectedTag(), forKey: "new.msxRegion") | ||||||
| 		standardUserDefaults.set(msxHasDiskDriveButton?.state == .on, forKey: "new.msxDiskDrive") | 		standardUserDefaults.set(msxHasDiskDriveButton?.state == .on, forKey: "new.msxDiskDrive") | ||||||
| @@ -158,7 +167,13 @@ class MachinePicker: NSObject { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			case "mac": | 			case "mac": | ||||||
| 				return CSStaticAnalyser(macintoshModel: .model512ke) | 				switch macintoshModelTypeButton!.selectedItem!.tag { | ||||||
|  | 					case 0:		return CSStaticAnalyser(macintoshModel: .model128k) | ||||||
|  | 					case 1:		return CSStaticAnalyser(macintoshModel: .model512k) | ||||||
|  | 					case 2:		return CSStaticAnalyser(macintoshModel: .model512ke) | ||||||
|  | 					case 3:		fallthrough | ||||||
|  | 					default:	return CSStaticAnalyser(macintoshModel: .modelPlus) | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 			case "msx": | 			case "msx": | ||||||
| 				let hasDiskDrive = msxHasDiskDriveButton!.state == .on | 				let hasDiskDrive = msxHasDiskDriveButton!.state == .on | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <deployment identifier="macosx"/> |         <deployment identifier="macosx"/> | ||||||
|         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> |         <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/> | ||||||
|         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <objects> |     <objects> | ||||||
| @@ -25,11 +25,11 @@ | |||||||
|                 <rect key="frame" x="0.0" y="0.0" width="480" height="270"/> |                 <rect key="frame" x="0.0" y="0.0" width="480" height="270"/> | ||||||
|                 <autoresizingMask key="autoresizingMask"/> |                 <autoresizingMask key="autoresizingMask"/> | ||||||
|                 <subviews> |                 <subviews> | ||||||
|                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav"> |                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav"> | ||||||
|                         <rect key="frame" x="18" y="148" width="444" height="102"/> |                         <rect key="frame" x="18" y="154" width="444" height="96"/> | ||||||
|                         <textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia"> |                         <textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia"> | ||||||
|                             <font key="font" usesAppearanceFont="YES"/> |                             <font key="font" usesAppearanceFont="YES"/> | ||||||
|                             <string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this view: |                             <string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this text: | ||||||
|  |  | ||||||
| </string> | </string> | ||||||
|                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -50,7 +50,7 @@ DQ | |||||||
|                         </connections> |                         </connections> | ||||||
|                     </button> |                     </button> | ||||||
|                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK"> |                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK"> | ||||||
|                         <rect key="frame" x="20" y="61" width="442" height="17"/> |                         <rect key="frame" x="20" y="61" width="442" height="16"/> | ||||||
|                         <textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP"> |                         <textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP"> | ||||||
|                             <font key="font" metaFont="message"/> |                             <font key="font" metaFont="message"/> | ||||||
|                             <color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |                             <color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> | ||||||
| @@ -70,6 +70,7 @@ DQ | |||||||
|                     <constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/> |                     <constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/> | ||||||
|                 </constraints> |                 </constraints> | ||||||
|             </view> |             </view> | ||||||
|  |             <point key="canvasLocation" x="139" y="147"/> | ||||||
|         </window> |         </window> | ||||||
|     </objects> |     </objects> | ||||||
| </document> | </document> | ||||||
|   | |||||||
| @@ -37,6 +37,19 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { | |||||||
| */ | */ | ||||||
| - (void)openGLView:(nonnull CSOpenGLView *)view didReceiveFileAtURL:(nonnull NSURL *)URL; | - (void)openGLView:(nonnull CSOpenGLView *)view didReceiveFileAtURL:(nonnull NSURL *)URL; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Announces 'capture' of the mouse — i.e. that the view is now preventing the mouse from exiting | ||||||
|  | 	the window, in order to forward continuous mouse motion. | ||||||
|  | 	@param view The view making the announcement. | ||||||
|  | */ | ||||||
|  | - (void)openGLViewDidCaptureMouse:(nonnull CSOpenGLView *)view; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Announces that the mouse is no longer captured. | ||||||
|  | 	@param view The view making the announcement. | ||||||
|  | */ | ||||||
|  | - (void)openGLViewDidReleaseMouse:(nonnull CSOpenGLView *)view; | ||||||
|  |  | ||||||
| @end | @end | ||||||
|  |  | ||||||
| @protocol CSOpenGLViewResponderDelegate <NSObject> | @protocol CSOpenGLViewResponderDelegate <NSObject> | ||||||
| @@ -97,8 +110,22 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { | |||||||
| @property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | @property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate; | ||||||
| @property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate; | @property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate; | ||||||
|  |  | ||||||
|  | /// Determines whether the view offers mouse capturing — i.e. if the user clicks on the view then | ||||||
|  | /// then the system cursor is disabled and the mouse events defined by CSOpenGLViewResponderDelegate | ||||||
|  | /// are forwarded, unless and until the user releases the mouse using the control+command shortcut. | ||||||
| @property (nonatomic, assign) BOOL shouldCaptureMouse; | @property (nonatomic, assign) BOOL shouldCaptureMouse; | ||||||
|  |  | ||||||
|  | /// Determines whether the CSOpenGLViewResponderDelegate of this window expects to use the command | ||||||
|  | /// key as though it were any other key — i.e. all command combinations should be forwarded to the delegate, | ||||||
|  | /// not being allowed to trigger regular application shortcuts such as command+q or command+h. | ||||||
|  | /// | ||||||
|  | /// How the view respects this will depend on other state; if this view is one that captures the mouse then it | ||||||
|  | /// will usurp command only while the mouse is captured. | ||||||
|  | /// | ||||||
|  | /// TODO: what's smart behaviour if this view doesn't capture the mouse? Probably | ||||||
|  | /// force a similar capturing behaviour? | ||||||
|  | @property (nonatomic, assign) BOOL shouldUsurpCommand; | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
| 	Ends the timer tracking time; should be called prior to giving up the last owning reference | 	Ends the timer tracking time; should be called prior to giving up the last owning reference | ||||||
| 	to ensure that any retain cycles implied by the timer are resolved. | 	to ensure that any retain cycles implied by the timer are resolved. | ||||||
|   | |||||||
| @@ -7,10 +7,11 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #import "CSOpenGLView.h" | #import "CSOpenGLView.h" | ||||||
|  | #import "CSApplication.h" | ||||||
| @import CoreVideo; | @import CoreVideo; | ||||||
| @import GLKit; | @import GLKit; | ||||||
|  |  | ||||||
| @interface CSOpenGLView () <NSDraggingDestination> | @interface CSOpenGLView () <NSDraggingDestination, CSApplicationEventDelegate> | ||||||
| @end | @end | ||||||
|  |  | ||||||
| @implementation CSOpenGLView { | @implementation CSOpenGLView { | ||||||
| @@ -139,23 +140,32 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
| 	return YES; | 	return YES; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)keyDown:(NSEvent *)theEvent { | - (void)keyDown:(NSEvent *)event { | ||||||
| 	[self.responderDelegate keyDown:theEvent]; | 	[self.responderDelegate keyDown:event]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)keyUp:(NSEvent *)theEvent { | - (void)keyUp:(NSEvent *)event { | ||||||
| 	[self.responderDelegate keyUp:theEvent]; | 	[self.responderDelegate keyUp:event]; | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)flagsChanged:(NSEvent *)theEvent { | - (void)flagsChanged:(NSEvent *)event { | ||||||
| 	[self.responderDelegate flagsChanged:theEvent]; |  | ||||||
|  |  | ||||||
| 	// Release the mouse upon a control + command. | 	// Release the mouse upon a control + command. | ||||||
| 	if(_mouseIsCaptured && | 	if(_mouseIsCaptured && | ||||||
| 		theEvent.modifierFlags & NSEventModifierFlagControl && | 		event.modifierFlags & NSEventModifierFlagControl && | ||||||
| 		theEvent.modifierFlags & NSEventModifierFlagCommand) { | 		event.modifierFlags & NSEventModifierFlagCommand) { | ||||||
| 		[self releaseMouse]; | 		[self releaseMouse]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	[self.responderDelegate flagsChanged:event]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event { | ||||||
|  | 	switch(event.type) { | ||||||
|  | 		default: return YES; | ||||||
|  | 		case NSEventTypeKeyUp:			[self keyUp:event];		return NO; | ||||||
|  | 		case NSEventTypeKeyDown:		[self keyDown:event];	return NO; | ||||||
|  | 		case NSEventTypeFlagsChanged:	[self flagsChanged:event];	return NO; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| - (void)paste:(id)sender { | - (void)paste:(id)sender { | ||||||
| @@ -223,6 +233,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
| 		_mouseIsCaptured = NO; | 		_mouseIsCaptured = NO; | ||||||
| 		CGAssociateMouseAndMouseCursorPosition(true); | 		CGAssociateMouseAndMouseCursorPosition(true); | ||||||
| 		[NSCursor unhide]; | 		[NSCursor unhide]; | ||||||
|  | 		[self.delegate openGLViewDidReleaseMouse:self]; | ||||||
|  | 		((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -281,6 +293,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt | |||||||
| 			_mouseIsCaptured = YES; | 			_mouseIsCaptured = YES; | ||||||
| 			[NSCursor hide]; | 			[NSCursor hide]; | ||||||
| 			CGAssociateMouseAndMouseCursorPosition(false); | 			CGAssociateMouseAndMouseCursorPosition(false); | ||||||
|  | 			[self.delegate openGLViewDidCaptureMouse:self]; | ||||||
|  | 			if(self.shouldUsurpCommand) { | ||||||
|  | 				((CSApplication *)[NSApplication sharedApplication]).eventDelegate = self; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// Don't report the first click to the delegate; treat that as merely | 			// Don't report the first click to the delegate; treat that as merely | ||||||
| 			// an invitation to capture the cursor. | 			// an invitation to capture the cursor. | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,9 +1,10 @@ | |||||||
| import glob | import glob | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| # establish UTF-8 encoding | # establish UTF-8 encoding for Python 2 | ||||||
| reload(sys) | if sys.version_info < (3, 0): | ||||||
| sys.setdefaultencoding('utf-8') | 	reload(sys) | ||||||
|  | 	sys.setdefaultencoding('utf-8') | ||||||
|  |  | ||||||
| # create build environment | # create build environment | ||||||
| env = Environment() | env = Environment() | ||||||
| @@ -35,6 +36,7 @@ SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp') | |||||||
| SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp') | SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp') | ||||||
|  |  | ||||||
| SOURCES += glob.glob('../../Components/1770/*.cpp') | SOURCES += glob.glob('../../Components/1770/*.cpp') | ||||||
|  | SOURCES += glob.glob('../../Components/5380/*.cpp') | ||||||
| SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp') | SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp') | ||||||
| SOURCES += glob.glob('../../Components/6560/*.cpp') | SOURCES += glob.glob('../../Components/6560/*.cpp') | ||||||
| SOURCES += glob.glob('../../Components/8272/*.cpp') | SOURCES += glob.glob('../../Components/8272/*.cpp') | ||||||
| @@ -96,6 +98,10 @@ SOURCES += glob.glob('../../Storage/Disk/Encodings/MFM/*.cpp') | |||||||
| SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp') | SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp') | SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp') | SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp') | ||||||
|  | SOURCES += glob.glob('../../Storage/MassStorage/*.cpp') | ||||||
|  | SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp') | ||||||
|  | SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp') | ||||||
|  | SOURCES += glob.glob('../../Storage/MassStorage/SCSI/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Tape/*.cpp') | SOURCES += glob.glob('../../Storage/Tape/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp') | SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp') | ||||||
| SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp') | SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp') | ||||||
|   | |||||||
| @@ -189,11 +189,11 @@ bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) | |||||||
|  |  | ||||||
| 		BIND(PRINTSCREEN, PrintScreen)	BIND(SCROLLLOCK, ScrollLock)	BIND(PAUSE, Pause) | 		BIND(PRINTSCREEN, PrintScreen)	BIND(SCROLLLOCK, ScrollLock)	BIND(PAUSE, Pause) | ||||||
|  |  | ||||||
| 		BIND(GRAVE, BackTick)		BIND(MINUS, Hyphen)		BIND(EQUALS, Equals)	BIND(BACKSPACE, BackSpace) | 		BIND(GRAVE, BackTick)		BIND(MINUS, Hyphen)		BIND(EQUALS, Equals)	BIND(BACKSPACE, Backspace) | ||||||
|  |  | ||||||
| 		BIND(TAB, Tab) | 		BIND(TAB, Tab) | ||||||
| 		BIND(LEFTBRACKET, OpenSquareBracket)	BIND(RIGHTBRACKET, CloseSquareBracket) | 		BIND(LEFTBRACKET, OpenSquareBracket)	BIND(RIGHTBRACKET, CloseSquareBracket) | ||||||
| 		BIND(BACKSLASH, BackSlash) | 		BIND(BACKSLASH, Backslash) | ||||||
|  |  | ||||||
| 		BIND(CAPSLOCK, CapsLock)	BIND(SEMICOLON, Semicolon) | 		BIND(CAPSLOCK, CapsLock)	BIND(SEMICOLON, Semicolon) | ||||||
| 		BIND(APOSTROPHE, Quote)		BIND(RETURN, Enter) | 		BIND(APOSTROPHE, Quote)		BIND(RETURN, Enter) | ||||||
| @@ -302,6 +302,32 @@ std::string system_get(const char *command) { | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Maintains a communicative window title. | ||||||
|  | */ | ||||||
|  | class DynamicWindowTitler { | ||||||
|  | 	public: | ||||||
|  | 		DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {} | ||||||
|  |  | ||||||
|  | 		std::string window_title() { | ||||||
|  | 			if(!mouse_is_captured_) return file_name_; | ||||||
|  | 			return file_name_ + " (press control+escape to release mouse)"; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void set_mouse_is_captured(bool is_captured) { | ||||||
|  | 			mouse_is_captured_ = is_captured; | ||||||
|  | 			update_window_title(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		void update_window_title() { | ||||||
|  | 			SDL_SetWindowTitle(window_, window_title().c_str()); | ||||||
|  | 		} | ||||||
|  | 		bool mouse_is_captured_ = false; | ||||||
|  | 		SDL_Window *window_ = nullptr; | ||||||
|  | 		const std::string file_name_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { | int main(int argc, char *argv[]) { | ||||||
| @@ -351,7 +377,7 @@ int main(int argc, char *argv[]) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Determine the machine for the supplied file. | 	// Determine the machine for the supplied file. | ||||||
| 	Analyser::Static::TargetList targets = Analyser::Static::GetTargets(arguments.file_name); | 	const auto targets = Analyser::Static::GetTargets(arguments.file_name); | ||||||
| 	if(targets.empty()) { | 	if(targets.empty()) { | ||||||
| 		std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl; | 		std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl; | ||||||
| 		return EXIT_FAILURE; | 		return EXIT_FAILURE; | ||||||
| @@ -459,6 +485,8 @@ int main(int argc, char *argv[]) { | |||||||
| 								400, 300, | 								400, 300, | ||||||
| 								SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); | 								SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); | ||||||
|  |  | ||||||
|  | 	DynamicWindowTitler window_titler(window); | ||||||
|  |  | ||||||
| 	SDL_GLContext gl_context = nullptr; | 	SDL_GLContext gl_context = nullptr; | ||||||
| 	if(window) { | 	if(window) { | ||||||
| 		gl_context = SDL_GL_CreateContext(window); | 		gl_context = SDL_GL_CreateContext(window); | ||||||
| @@ -628,6 +656,7 @@ int main(int argc, char *argv[]) { | |||||||
| 					// Use ctrl+escape to release the mouse (if captured). | 					// Use ctrl+escape to release the mouse (if captured). | ||||||
| 					if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) { | 					if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) { | ||||||
| 						SDL_SetRelativeMouseMode(SDL_FALSE); | 						SDL_SetRelativeMouseMode(SDL_FALSE); | ||||||
|  | 						window_titler.set_mouse_is_captured(false); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					// Capture ctrl+shift+d as a take-a-screenshot command. | 					// Capture ctrl+shift+d as a take-a-screenshot command. | ||||||
| @@ -734,6 +763,7 @@ int main(int argc, char *argv[]) { | |||||||
| 				case SDL_MOUSEBUTTONDOWN: | 				case SDL_MOUSEBUTTONDOWN: | ||||||
| 					if(uses_mouse && !SDL_GetRelativeMouseMode()) { | 					if(uses_mouse && !SDL_GetRelativeMouseMode()) { | ||||||
| 						SDL_SetRelativeMouseMode(SDL_TRUE); | 						SDL_SetRelativeMouseMode(SDL_TRUE); | ||||||
|  | 						window_titler.set_mouse_is_captured(true); | ||||||
| 						break; | 						break; | ||||||
| 					} | 					} | ||||||
| 				case SDL_MOUSEBUTTONUP: { | 				case SDL_MOUSEBUTTONUP: { | ||||||
|   | |||||||
| @@ -188,6 +188,12 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
|  |  | ||||||
| 							case BusStep::Action::AdvancePrefetch: | 							case BusStep::Action::AdvancePrefetch: | ||||||
| 								prefetch_queue_.halves.high = prefetch_queue_.halves.low; | 								prefetch_queue_.halves.high = prefetch_queue_.halves.low; | ||||||
|  |  | ||||||
|  | 								// During prefetch advance seems to be the only time the interrupt inputs are sampled; | ||||||
|  | 								// TODO: determine whether this really happens on *every* advance. | ||||||
|  | 								if(bus_interrupt_level_ > interrupt_level_) { | ||||||
|  | 									pending_interrupt_level_ = bus_interrupt_level_; | ||||||
|  | 								} | ||||||
| 							break; | 							break; | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| @@ -199,6 +205,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
| 					// If an interrupt (TODO: or reset) has finally arrived that will be serviced, | 					// If an interrupt (TODO: or reset) has finally arrived that will be serviced, | ||||||
| 					// exit the STOP. | 					// exit the STOP. | ||||||
| 					if(bus_interrupt_level_ > interrupt_level_) { | 					if(bus_interrupt_level_ > interrupt_level_) { | ||||||
|  | 						pending_interrupt_level_ = bus_interrupt_level_; | ||||||
| 						execution_state_ = ExecutionState::BeginInterrupt; | 						execution_state_ = ExecutionState::BeginInterrupt; | ||||||
| 						continue; | 						continue; | ||||||
| 					} | 					} | ||||||
| @@ -234,12 +241,6 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
| 				continue; | 				continue; | ||||||
|  |  | ||||||
| 				case ExecutionState::BeginInterrupt: | 				case ExecutionState::BeginInterrupt: | ||||||
| #ifdef LOG_TRACE |  | ||||||
| //					should_log = true; |  | ||||||
| 					if(should_log) { |  | ||||||
| 						printf("\n\nInterrupt\n\n"); |  | ||||||
| 					} |  | ||||||
| #endif |  | ||||||
| 					active_program_ = nullptr; | 					active_program_ = nullptr; | ||||||
| 					active_micro_op_ = interrupt_micro_ops_; | 					active_micro_op_ = interrupt_micro_ops_; | ||||||
| 					execution_state_ = ExecutionState::Executing; | 					execution_state_ = ExecutionState::Executing; | ||||||
| @@ -260,7 +261,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
| 						// Either the micro-operations for this instruction have been exhausted, or | 						// Either the micro-operations for this instruction have been exhausted, or | ||||||
| 						// no instruction was ongoing. Either way, do a standard instruction operation. | 						// no instruction was ongoing. Either way, do a standard instruction operation. | ||||||
|  |  | ||||||
| 						if(bus_interrupt_level_ > interrupt_level_) { | 						if(pending_interrupt_level_) { | ||||||
| 							execution_state_ = ExecutionState::BeginInterrupt; | 							execution_state_ = ExecutionState::BeginInterrupt; | ||||||
| 							break; | 							break; | ||||||
| 						} | 						} | ||||||
| @@ -1055,51 +1056,56 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
| 										break; | 										break; | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
| 									int32_t dividend = int32_t(destination()->full); | 									const int32_t signed_dividend = int32_t(destination()->full); | ||||||
| 									int32_t divisor = s_extend16(source()->halves.low.full); | 									const int32_t signed_divisor = s_extend16(source()->halves.low.full); | ||||||
| 									const int64_t quotient = int64_t(dividend) / int64_t(divisor); | 									const auto result_sign = | ||||||
|  | 										( (0 <= signed_dividend) - (signed_dividend < 0) ) * | ||||||
|  | 										( (0 <= signed_divisor) - (signed_divisor < 0) ); | ||||||
|  |  | ||||||
|  | 									const uint32_t dividend = uint32_t(abs(signed_dividend)); | ||||||
|  | 									const uint32_t divisor = uint32_t(abs(signed_divisor)); | ||||||
|  |  | ||||||
| 									int cycles_expended = 12;	// Covers the nn nnn n to get beyond the sign test. | 									int cycles_expended = 12;	// Covers the nn nnn n to get beyond the sign test. | ||||||
| 									if(dividend < 0) { | 									if(signed_dividend < 0) { | ||||||
| 										cycles_expended += 2;	// An additional microycle applies if the dividend is negative. | 										cycles_expended += 2;	// An additional microycle applies if the dividend is negative. | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
| 									// Check for overflow. If it exists, work here is already done. | 									// Check for overflow. If it exists, work here is already done. | ||||||
| 									if(quotient > 32767 || quotient < -32768) { | 									const auto quotient = dividend / divisor; | ||||||
|  | 									if(quotient > 32767) { | ||||||
| 										overflow_flag_ = 1; | 										overflow_flag_ = 1; | ||||||
| 										set_next_microcycle_length(HalfCycles(3*2*2)); | 										set_next_microcycle_length(HalfCycles(6*2*2)); | ||||||
|  |  | ||||||
| 										// These are officially undefined for results that overflow, so the below is a guess. | 										// These are officially undefined for results that overflow, so the below is a guess. | ||||||
| 										zero_result_ = decltype(zero_result_)(divisor & 0xffff); | 										zero_result_ = decltype(zero_result_)(dividend); | ||||||
| 										negative_flag_ = zero_result_ & 0x8000; | 										negative_flag_ = zero_result_ & 0x8000; | ||||||
|  |  | ||||||
| 										break; | 										break; | ||||||
| 									} | 									} | ||||||
|  |  | ||||||
| 									zero_result_ = decltype(zero_result_)(quotient); | 									const uint16_t remainder = uint16_t(signed_dividend % signed_divisor); | ||||||
|  | 									const int signed_quotient = result_sign*int(quotient); | ||||||
|  | 									destination()->halves.high.full = remainder; | ||||||
|  | 									destination()->halves.low.full = uint16_t(signed_quotient); | ||||||
|  |  | ||||||
|  | 									zero_result_ = decltype(zero_result_)(signed_quotient); | ||||||
| 									negative_flag_ = zero_result_ & 0x8000; | 									negative_flag_ = zero_result_ & 0x8000; | ||||||
| 									overflow_flag_ = 0; | 									overflow_flag_ = 0; | ||||||
|  |  | ||||||
| 									// TODO: check sign rules here; am I necessarily giving the remainder the correct sign? | 									// Algorithm here: there is a fixed cost per unset bit | ||||||
| 									// (and, if not, am I counting it in the correct direction?) | 									// in the first 15 bits of the unsigned quotient. | ||||||
| 									const uint16_t remainder = uint16_t(dividend % divisor); | 									auto positive_quotient_bits = ~quotient & 0xfffe; | ||||||
| 									destination()->halves.high.full = remainder; |  | ||||||
| 									destination()->halves.low.full = uint16_t(quotient); |  | ||||||
|  |  | ||||||
| 									// Algorithm here: there is a fixed three-microcycle cost per bit set |  | ||||||
| 									// in the unsigned quotient; there is an additional microcycle for |  | ||||||
| 									// every bit that is set. Also, since the possibility of overflow |  | ||||||
| 									// was already dealt with, it's now a smaller number. |  | ||||||
| 									int positive_quotient_bits = int(abs(quotient)) & 0xfffe; |  | ||||||
| 									convert_to_bit_count_16(positive_quotient_bits); | 									convert_to_bit_count_16(positive_quotient_bits); | ||||||
| 									cycles_expended += 2 * positive_quotient_bits; | 									cycles_expended += 2 * positive_quotient_bits; | ||||||
|  |  | ||||||
| 									// There's then no way to terminate the loop that isn't at least six cycles long. | 									// There's then no way to terminate the loop that isn't at least ten cycles long; | ||||||
| 									cycles_expended += 6; | 									// there's also a fixed overhead per bit. The two together add up to the 104 below. | ||||||
|  | 									cycles_expended += 104; | ||||||
|  |  | ||||||
| 									if(divisor < 0) { | 									// This picks up at 'No more bits' in yacht.txt's diagram. | ||||||
|  | 									if(signed_divisor < 0) { | ||||||
| 										cycles_expended += 2; | 										cycles_expended += 2; | ||||||
| 									} else if(dividend < 0) { | 									} else if(signed_dividend < 0) { | ||||||
| 										cycles_expended += 4; | 										cycles_expended += 4; | ||||||
| 									} | 									} | ||||||
| 									set_next_microcycle_length(HalfCycles(cycles_expended * 2)); | 									set_next_microcycle_length(HalfCycles(cycles_expended * 2)); | ||||||
| @@ -1960,10 +1966,13 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces | |||||||
|  |  | ||||||
| 							// Mutate neessary internal state — effective_address_[0] is exposed | 							// Mutate neessary internal state — effective_address_[0] is exposed | ||||||
| 							// on the data bus as the accepted interrupt number during the interrupt | 							// on the data bus as the accepted interrupt number during the interrupt | ||||||
| 							// acknowledge cycle, with the low bit set since a real 68000 uses the lower | 							// acknowledge cycle, with all other bits set, including the low bit as | ||||||
| 							// data strobe to collect the corresponding vector byte. | 							// a real 68000 uses the lower data strobe to collect the corresponding vector byte. | ||||||
| 							accepted_interrupt_level_ = interrupt_level_ = bus_interrupt_level_; | 							// | ||||||
| 							effective_address_[0].full = 1 | uint32_t(accepted_interrupt_level_ << 1); | 							// Cf. M68000 8-/16-/32-BIT MICROPROCESSORS USER'S MANUAL 5.1.4. | ||||||
|  | 							accepted_interrupt_level_ = interrupt_level_ = pending_interrupt_level_; | ||||||
|  | 							pending_interrupt_level_ = 0; | ||||||
|  | 							effective_address_[0].full = 0xfffffff1 | uint32_t(accepted_interrupt_level_ << 1); | ||||||
|  |  | ||||||
| 							// Recede the program counter to where it would have been were there no | 							// Recede the program counter to where it would have been were there no | ||||||
| 							// prefetch; that's where the reading stream should pick up upon RTE. | 							// prefetch; that's where the reading stream should pick up upon RTE. | ||||||
|   | |||||||
| @@ -62,6 +62,11 @@ class ProcessorStorage { | |||||||
| 		bool bus_acknowledge_ = false; | 		bool bus_acknowledge_ = false; | ||||||
| 		bool halt_ = false; | 		bool halt_ = false; | ||||||
|  |  | ||||||
|  | 		// Holds the interrupt level that should be serviced at the next instruction | ||||||
|  | 		// dispatch, if any. | ||||||
|  | 		int pending_interrupt_level_ = 0; | ||||||
|  | 		// Holds the interrupt level that is currently being serviced. | ||||||
|  | 		// TODO: surely this doesn't need to be distinct from the pending_interrupt_level_? | ||||||
| 		int accepted_interrupt_level_ = 0; | 		int accepted_interrupt_level_ = 0; | ||||||
| 		bool is_starting_interrupt_ = false; | 		bool is_starting_interrupt_ = false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ namespace Z80 { | |||||||
| /* | /* | ||||||
| 	The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register. | 	The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register. | ||||||
| */ | */ | ||||||
| enum Register { | enum class Register { | ||||||
| 	ProgramCounter, | 	ProgramCounter, | ||||||
| 	StackPointer, | 	StackPointer, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,13 +18,12 @@ It currently contains emulations of the: | |||||||
| * Atari 2600; | * Atari 2600; | ||||||
| * ColecoVision; | * ColecoVision; | ||||||
| * Commodore Vic-20 (and Commodore 1540/1); | * Commodore Vic-20 (and Commodore 1540/1); | ||||||
|  | * Macintosh 512ke and Plus; | ||||||
| * MSX 1; | * MSX 1; | ||||||
| * Oric 1/Atmos; | * Oric 1/Atmos; | ||||||
| * Sega Master System; and | * Sega Master System; and | ||||||
| * Sinclair ZX80/81. | * Sinclair ZX80/81. | ||||||
|  |  | ||||||
| Work is in progress on the Macintosh 512ke; it is presently very experimental. |  | ||||||
|  |  | ||||||
| ## Single-click Loading | ## Single-click Loading | ||||||
|  |  | ||||||
| Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible. | Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible. | ||||||
|   | |||||||
| @@ -21,6 +21,9 @@ | |||||||
| namespace Storage { | namespace Storage { | ||||||
| namespace Disk { | namespace Disk { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Models a flopy disk. | ||||||
|  | */ | ||||||
| class Disk { | class Disk { | ||||||
| 	public: | 	public: | ||||||
| 		virtual ~Disk() {} | 		virtual ~Disk() {} | ||||||
|   | |||||||
| @@ -20,8 +20,8 @@ using namespace Storage::Disk; | |||||||
|  |  | ||||||
| Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads): | Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads): | ||||||
| 	Storage::TimedEventLoop(input_clock_rate), | 	Storage::TimedEventLoop(input_clock_rate), | ||||||
| 	rotational_multiplier_(60.0f / float(revolutions_per_minute)), |  | ||||||
| 	available_heads_(number_of_heads) { | 	available_heads_(number_of_heads) { | ||||||
|  | 	set_rotation_speed(revolutions_per_minute); | ||||||
|  |  | ||||||
| 	const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count()); | 	const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count()); | ||||||
| 	std::default_random_engine randomiser(seed); | 	std::default_random_engine randomiser(seed); | ||||||
|   | |||||||
| @@ -138,7 +138,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { | |||||||
| 		void set_event_delegate(EventDelegate *); | 		void set_event_delegate(EventDelegate *); | ||||||
|  |  | ||||||
| 		// As per Sleeper. | 		// As per Sleeper. | ||||||
| 		ClockingHint::Preference preferred_clocking() override; | 		ClockingHint::Preference preferred_clocking() final; | ||||||
|  |  | ||||||
| 		/// Adds an activity observer; it'll be notified of disk activity. | 		/// Adds an activity observer; it'll be notified of disk activity. | ||||||
| 		/// The caller can specify whether to add an LED based on disk motor. | 		/// The caller can specify whether to add an LED based on disk motor. | ||||||
|   | |||||||
							
								
								
									
										301
									
								
								Storage/MassStorage/Encodings/MacintoshVolume.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								Storage/MassStorage/Encodings/MacintoshVolume.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,301 @@ | |||||||
|  | // | ||||||
|  | //  MacintoshVolume.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MacintoshVolume.hpp" | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | using namespace Storage::MassStorage::Encodings::Macintosh; | ||||||
|  |  | ||||||
|  | void Mapper::set_drive_type(DriveType drive_type, size_t number_of_blocks) { | ||||||
|  | 	drive_type_ = drive_type; | ||||||
|  | 	number_of_blocks_ = number_of_blocks; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t Mapper::get_number_of_blocks() { | ||||||
|  | 	return number_of_blocks_ + 0x60; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ssize_t Mapper::to_source_address(size_t address) { | ||||||
|  | 	return ssize_t(address) - 0x60; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<uint8_t> Mapper::convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data) { | ||||||
|  | 	// Addresses greater than or equal to zero map to the actual disk image. | ||||||
|  | 	if(source_address >= 0) return source_data; | ||||||
|  |  | ||||||
|  | 	// Switch to mapping relative to 0, for personal sanity. | ||||||
|  | 	source_address += 0x60; | ||||||
|  |  | ||||||
|  | 	// Block 0 is the device descriptor, which lists the total number of blocks, | ||||||
|  | 	// and provides an offset to the driver. | ||||||
|  | 	if(!source_address) { | ||||||
|  | 		uint32_t total_device_blocks = uint32_t(number_of_blocks_ + 0x60); | ||||||
|  |  | ||||||
|  | 		/* The driver descriptor. */ | ||||||
|  | 		std::vector<uint8_t> driver_description = { | ||||||
|  | 			0x45, 0x52,		/* device signature */ | ||||||
|  | 			0x02, 0x00,		/* block size, in bytes */ | ||||||
|  |  | ||||||
|  | 			uint8_t(total_device_blocks >> 24), | ||||||
|  | 			uint8_t(total_device_blocks >> 16), | ||||||
|  | 			uint8_t(total_device_blocks >> 8), | ||||||
|  | 			uint8_t(total_device_blocks), | ||||||
|  | 							/* number of blocks on device */ | ||||||
|  |  | ||||||
|  | 			0x00, 0x01,		/* reserved (formerly: device type) */ | ||||||
|  | 			0x00, 0x01,		/* reserved (formerly: device ID) */ | ||||||
|  | 			0x00, 0x00, | ||||||
|  | 			0x00, 0x00,		/* reserved ('sbData', no further explanation given) */ | ||||||
|  |  | ||||||
|  | 			0x00, 0x01,		/* number of device descriptor entries */ | ||||||
|  | 			0x00, 0x00, | ||||||
|  | 			0x00, 0x40,		/* first device descriptor's starting block */ | ||||||
|  | 			0x00, 0x0a,		/* size of device driver */ | ||||||
|  | 			0x00, 0x01,		/* | ||||||
|  | 								More modern documentation: operating system (MacOS = 1) | ||||||
|  | 								Inside Macintosh IV: system type (Mac Plus = 1) | ||||||
|  | 							*/ | ||||||
|  | 		}; | ||||||
|  | 		driver_description.resize(512); | ||||||
|  |  | ||||||
|  | 		return driver_description; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Blocks 1, 2 and 3 contain parts of the partition map. | ||||||
|  | 	if(source_address < 4) { | ||||||
|  | 		struct Partition { | ||||||
|  | 			const char *name, *type; | ||||||
|  | 			uint32_t start_block, size; | ||||||
|  | 			uint8_t status; | ||||||
|  | 		} partitions[3] = { | ||||||
|  | 			{	"MacOS",		"Apple_HFS",			0x60,	uint32_t(number_of_blocks_),	0xb7	}, | ||||||
|  | 			{	"Apple",		"Apple_partition_map",	0x01,	0x3f,							0x37	}, | ||||||
|  | 			{	"Macintosh",	"Apple_Driver",			0x40,	0x20,							0x7f	}, | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> partition(512); | ||||||
|  |  | ||||||
|  | 		// Fill in the fixed fields. | ||||||
|  | 		partition[0] = 'P';	partition[1] = 'M';	/* Signature. */ | ||||||
|  | 		partition[7] = 3;						/* Number of partitions. */ | ||||||
|  |  | ||||||
|  | 		const Partition &details = partitions[source_address-1]; | ||||||
|  |  | ||||||
|  | 		partition[8] = uint8_t(details.start_block >> 24); | ||||||
|  | 		partition[9] = uint8_t(details.start_block >> 16); | ||||||
|  | 		partition[10] = uint8_t(details.start_block >> 8); | ||||||
|  | 		partition[11] = uint8_t(details.start_block); | ||||||
|  |  | ||||||
|  | 		partition[84] = partition[12] = uint8_t(details.size >> 24); | ||||||
|  | 		partition[85] = partition[13] = uint8_t(details.size >> 16); | ||||||
|  | 		partition[86] = partition[14] = uint8_t(details.size >> 8); | ||||||
|  | 		partition[87] = partition[15] = uint8_t(details.size); | ||||||
|  |  | ||||||
|  | 		// 32 bytes are allocated for each of the following strings. | ||||||
|  | 		memcpy(&partition[16], details.name, strlen(details.name)); | ||||||
|  | 		memcpy(&partition[48], details.type, strlen(details.type)); | ||||||
|  |  | ||||||
|  | 		partition[91] = details.status; | ||||||
|  |  | ||||||
|  | 		// The third entry in this constructed partition map is the driver; | ||||||
|  | 		// add some additional details. | ||||||
|  | 		if(source_address == 3) { | ||||||
|  | 			partition[98] = 0x13; | ||||||
|  | 			partition[99] = 0x9e;	/* This version of the driver code is 0x139e bytes long. */ | ||||||
|  |  | ||||||
|  | 			partition[118] = 0x84; | ||||||
|  | 			partition[119] = 0xb9;	/* Driver checksum. */ | ||||||
|  |  | ||||||
|  | 			memcpy(&partition[120], "68000", strlen("68000"));	/* Driver is for the 68000. */ | ||||||
|  |  | ||||||
|  | 			// Various non-zero values that Apple HD SC Tool wrote are below; they are | ||||||
|  | 			// documented as reserved officially, so I don't know their meaning. | ||||||
|  | 			partition[137] = 0x01; | ||||||
|  | 			partition[138] = 0x06; | ||||||
|  | 			partition[143] = 0x01; | ||||||
|  | 			partition[147] = 0x02; | ||||||
|  | 			partition[149] = 0x07; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return partition; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// The ten blocks starting at 0x40 contain the SCSI driver. | ||||||
|  | 	if(source_address >= 0x40 && source_address < 0x40 + 10) { | ||||||
|  | 		// This is the body of the SCSI driver. | ||||||
|  | 		const uint8_t driver[] = { | ||||||
|  | 			0x60, 0x00, 0x01, 0xba, 0x23, 0x24, 0x00, 0x01, 0x00, 0x27, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x13, 0x9e, 0x00, 0x00, 0xda, 0xe7, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, | ||||||
|  | 			0x00, 0x7c, 0x00, 0x68, 0x00, 0x72, 0x00, 0x5e, 0x07, 0x2e, 0x53, 0x43, 0x53, 0x49, 0x30, 0x30, 0x41, 0xbd, 0x30, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x1f, 0x60, 0x00, | ||||||
|  | 			0x02, 0x12, 0x41, 0xfa, 0xff, 0xf4, 0x20, 0xaf, 0x00, 0x04, 0x4e, 0x75, 0x20, 0x3a, 0xff, 0xea, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x7a, 0xff, 0xe2, 0x4e, 0x75, 0x41, 0xfa, 0xff, 0xd8, 0x30, 0xaf, | ||||||
|  | 			0x00, 0x06, 0x4e, 0x75, 0x48, 0x7a, 0x00, 0x4e, 0x20, 0x1f, 0x4e, 0x75, 0x70, 0x00, 0x31, 0x40, 0x00, 0x10, 0x60, 0x2a, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0x4a, 0x60, 0x1c, 0x2f, 0x08, | ||||||
|  | 			0x2f, 0x09, 0x4e, 0xba, 0x07, 0x82, 0x60, 0x12, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x08, 0xca, 0x60, 0x08, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0xf2, 0x22, 0x5f, 0x20, 0x5f, 0x4a, 0x40, | ||||||
|  | 			0x67, 0x04, 0x31, 0xc0, 0x01, 0x42, 0x08, 0x28, 0x00, 0x09, 0x00, 0x06, 0x66, 0x04, 0x2f, 0x38, 0x08, 0xfc, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, | ||||||
|  | 			0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, | ||||||
|  | 			0x80, 0x00, 0x00, 0x01, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, | ||||||
|  | 			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | ||||||
|  | 			0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x53, 0x43, 0x53, 0x49, 0x20, 0x30, 0x00, 0x48, 0xe7, 0x3f, 0x3e, | ||||||
|  | 			0x49, 0xfa, 0xfe, 0x50, 0x26, 0x14, 0x42, 0x94, 0x70, 0x00, 0x42, 0x42, 0x28, 0x3a, 0xfe, 0x40, 0x53, 0x44, 0x49, 0xfa, 0xfe, 0x2c, 0x14, 0x1c, 0xd0, 0x42, 0xe3, 0x58, 0x51, 0xcc, 0xff, 0xf8, | ||||||
|  | 			0x4a, 0x40, 0x66, 0x02, 0x53, 0x40, 0x49, 0xfa, 0xfe, 0x2a, 0x28, 0x80, 0xb6, 0x80, 0x67, 0x0e, 0x49, 0xfa, 0xfe, 0x20, 0x38, 0x83, 0x4c, 0xdf, 0x7c, 0xfc, 0x70, 0xff, 0x4e, 0x75, 0x28, 0x48, | ||||||
|  | 			0x41, 0xfa, 0xfd, 0xfe, 0x20, 0x3a, 0xfe, 0x08, 0xa0, 0x20, 0x70, 0x06, 0x22, 0x78, 0x01, 0x1c, 0xd2, 0xfc, 0x00, 0x9c, 0x24, 0x21, 0x67, 0x1e, 0x24, 0x42, 0x20, 0x52, 0x24, 0x50, 0x0c, 0xaa, | ||||||
|  | 			0x41, 0xbd, 0x30, 0xb0, 0x00, 0x1a, 0x66, 0x0e, 0xb6, 0xaa, 0xff, 0xfc, 0x66, 0x08, 0x41, 0xfa, 0xfd, 0xd0, 0x4e, 0xea, 0x00, 0x26, 0x51, 0xc8, 0xff, 0xdc, 0x4a, 0x85, 0x6b, 0x14, 0x20, 0x78, | ||||||
|  | 			0x02, 0xa6, 0x20, 0x50, 0xd0, 0xfc, 0x1f, 0x40, 0x2f, 0x38, 0x01, 0x18, 0xa0, 0x57, 0x21, 0xdf, 0x01, 0x18, 0x70, 0x20, 0xd0, 0x45, 0x38, 0x00, 0x46, 0x40, 0xa0, 0x3d, 0xe5, 0x44, 0x22, 0x78, | ||||||
|  | 			0x01, 0x1c, 0x20, 0x71, 0x40, 0x00, 0x2c, 0x50, 0xa0, 0x29, 0x41, 0xfa, 0xfd, 0xaa, 0x2c, 0x88, 0x3d, 0x58, 0x00, 0x04, 0x2d, 0x58, 0x00, 0x22, 0x3d, 0x58, 0x00, 0x26, 0x08, 0xee, 0x00, 0x05, | ||||||
|  | 			0x00, 0x05, 0x08, 0xae, 0x00, 0x06, 0x00, 0x05, 0x70, 0x01, 0x08, 0x05, 0x00, 0x1e, 0x67, 0x02, 0x70, 0x00, 0x2f, 0x00, 0x2f, 0x0e, 0x4e, 0xba, 0x00, 0x3c, 0x50, 0x8f, 0x4a, 0x40, 0x67, 0x02, | ||||||
|  | 			0x70, 0xe9, 0x4c, 0xdf, 0x7c, 0xfc, 0x4e, 0x75, 0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x01, 0x3f, 0x3c, 0x00, 0x03, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75, | ||||||
|  | 			0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x04, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xf4, 0x48, 0xe7, 0x3e, 0x30, 0x26, 0x6e, 0x00, 0x08, | ||||||
|  | 			0x24, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0xfd, 0x66, 0x4a, 0x80, 0x67, 0x0c, 0x4e, 0xba, 0xfd, 0x64, 0x2d, 0x40, 0xff, 0xf6, 0x60, 0x00, 0x00, 0x9a, 0x4e, 0xba, 0x0e, 0xde, 0x28, 0x00, 0x4e, 0xba, | ||||||
|  | 			0x0e, 0xe8, 0x2f, 0x00, 0x4e, 0xba, 0x0e, 0xd8, 0x4e, 0xba, 0x0f, 0x92, 0x26, 0x00, 0x2f, 0x04, 0x4e, 0xba, 0x0e, 0xcc, 0x4a, 0x83, 0x50, 0x8f, 0x66, 0x06, 0x70, 0xff, 0x60, 0x00, 0x02, 0x9c, | ||||||
|  | 			0x2f, 0x03, 0x4e, 0xba, 0x0f, 0x48, 0x2f, 0x03, 0x4e, 0xba, 0xfd, 0x18, 0x4e, 0xba, 0xfd, 0x24, 0x2d, 0x40, 0xff, 0xf6, 0x20, 0x38, 0x02, 0xae, 0x50, 0x80, 0x24, 0x40, 0x1b, 0x52, 0xfc, 0x70, | ||||||
|  | 			0x50, 0x8f, 0x66, 0x06, 0x42, 0x2d, 0xfc, 0x74, 0x60, 0x14, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x0c, 0x2d, 0x00, 0x03, 0xfc, 0x70, 0x65, 0x06, 0x1b, 0x7c, 0x00, 0x02, 0xfc, 0x70, 0x42, 0x6d, | ||||||
|  | 			0xfc, 0x88, 0x42, 0xa7, 0x4e, 0xba, 0xfc, 0xf4, 0x42, 0x6d, 0xfc, 0x8c, 0x42, 0x2d, 0xfc, 0x78, 0x78, 0x00, 0x58, 0x8f, 0x60, 0x0e, 0x20, 0x04, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x42, 0x70, | ||||||
|  | 			0x08, 0x00, 0x52, 0x84, 0x70, 0x08, 0xb0, 0x84, 0x62, 0x00, 0xff, 0xec, 0x42, 0x2d, 0xfc, 0x7c, 0x36, 0x2b, 0x00, 0x18, 0x46, 0x43, 0x04, 0x43, 0x00, 0x20, 0x72, 0x00, 0x32, 0x03, 0x20, 0x01, | ||||||
|  | 			0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x4e, 0xba, 0xfc, 0x94, 0x27, 0x40, 0x00, 0x14, 0x42, 0xa7, | ||||||
|  | 			0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x62, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x7a, 0x01, 0x2d, 0x45, 0xff, 0xfa, 0x78, 0x01, 0x50, 0x8f, 0x60, 0x00, 0x01, 0x50, 0x48, 0x6e, | ||||||
|  | 			0xfd, 0xf6, 0x2f, 0x04, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x08, 0x58, 0x3a, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x67, 0x08, 0x0c, 0x45, 0xff, 0xfc, | ||||||
|  | 			0x66, 0x00, 0x01, 0x28, 0x0c, 0x6e, 0x50, 0x4d, 0xfd, 0xf6, 0x66, 0x00, 0x01, 0x1e, 0x70, 0x01, 0xb0, 0x84, 0x66, 0x06, 0x2d, 0x6e, 0xfd, 0xfa, 0xff, 0xfa, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x4e, | ||||||
|  | 			0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x42, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x44, 0x72, 0x66, 0x36, 0x0c, 0xae, 0x00, 0x01, 0x06, 0x00, 0xfe, 0x7e, | ||||||
|  | 			0x66, 0x2c, 0x72, 0x01, 0x3d, 0x41, 0xff, 0xfe, 0x70, 0x00, 0x30, 0x03, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x31, 0xae, 0xfe, 0x8c, 0x08, 0x00, 0x35, 0x6e, 0xfe, 0x84, 0x00, 0x2c, 0x15, 0x6e, | ||||||
|  | 			0xfe, 0x89, 0x00, 0x25, 0x15, 0x6e, 0xfe, 0x8b, 0x00, 0x27, 0x60, 0x00, 0x00, 0xbe, 0x4a, 0x46, 0x66, 0x00, 0x00, 0xb8, 0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x00, | ||||||
|  | 			0x00, 0xaa, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x48, 0x46, 0x66, 0x00, 0x00, 0x9c, 0x7c, 0x01, 0x7a, 0x08, 0x20, 0x78, 0x03, 0x0a, 0x60, 0x0e, 0x30, 0x28, 0x00, 0x06, 0xb0, 0x45, | ||||||
|  | 			0x66, 0x04, 0x52, 0x45, 0x60, 0xee, 0x20, 0x50, 0x20, 0x08, 0x66, 0x00, 0xff, 0xee, 0x42, 0x52, 0x08, 0x2e, 0x00, 0x05, 0xfe, 0x51, 0x67, 0x04, 0x70, 0x00, 0x60, 0x06, 0x20, 0x3c, 0x00, 0x00, | ||||||
|  | 			0x00, 0x80, 0x15, 0x40, 0x00, 0x02, 0x15, 0x7c, 0x00, 0x08, 0x00, 0x03, 0x15, 0x7c, 0x00, 0x01, 0x00, 0x04, 0x42, 0x2a, 0x00, 0x05, 0x35, 0x7c, 0x00, 0x01, 0x00, 0x0a, 0x42, 0xaa, 0x00, 0x06, | ||||||
|  | 			0x42, 0x6a, 0x00, 0x10, 0x35, 0x6e, 0xfe, 0x4c, 0x00, 0x12, 0x20, 0x2e, 0xfe, 0x4a, 0x72, 0x10, 0xe2, 0xa8, 0x35, 0x40, 0x00, 0x14, 0x20, 0x2e, 0xfe, 0x46, 0xd0, 0xae, 0xfd, 0xfe, 0x25, 0x40, | ||||||
|  | 			0x00, 0x18, 0x25, 0x40, 0x00, 0x1c, 0x25, 0x6e, 0xfe, 0x4a, 0x00, 0x20, 0x35, 0x45, 0x00, 0x16, 0x48, 0x6a, 0x00, 0x06, 0x70, 0x00, 0x30, 0x05, 0x2f, 0x00, 0x30, 0x2b, 0x00, 0x18, 0x48, 0xc0, | ||||||
|  | 			0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x98, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x84, 0xb8, 0xae, 0xff, 0xfa, 0x63, 0x00, 0xfe, 0xac, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x06, 0x7a, 0xfe, 0x60, 0x00, 0x00, 0x6e, | ||||||
|  | 			0x4a, 0x46, 0x66, 0x04, 0x42, 0x45, 0x60, 0x64, 0x70, 0x00, 0x30, 0x03, 0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x70, 0x01, 0x12, 0x03, 0xe3, 0xa0, 0x36, 0x00, 0x4a, 0x82, | ||||||
|  | 			0x67, 0x06, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x8c, 0x4a, 0x6d, 0xfc, 0x88, 0x66, 0x28, 0x3b, 0x7c, 0x00, 0x01, 0xfc, 0x64, 0x41, 0xfa, 0x04, 0x58, 0x2b, 0x48, 0xfc, 0x66, 0x3b, 0x7c, 0x00, 0x65, | ||||||
|  | 			0xfc, 0x6a, 0x42, 0x6d, 0xfc, 0x6c, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0xc0, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x7c, 0x58, 0x8f, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d, | ||||||
|  | 			0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0xb4, 0x42, 0x45, 0x58, 0x8f, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x0c, 0xe4, 0x30, 0x05, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xfd, 0xd8, | ||||||
|  | 			0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x20, 0x24, 0x6f, 0x00, 0x14, 0x20, 0x2f, 0x00, 0x18, 0x4e, 0xba, 0xfa, 0x80, 0x28, 0x00, 0x34, 0x2a, 0x00, 0x18, 0x46, 0x42, 0x04, 0x42, 0x00, 0x20, | ||||||
|  | 			0x70, 0x01, 0x12, 0x02, 0xe3, 0xa0, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d, 0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0x64, 0x36, 0x2d, 0xfc, 0x88, 0x48, 0x78, 0x03, 0x08, | ||||||
|  | 			0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x48, 0x70, 0x18, 0x06, 0x4e, 0xba, | ||||||
|  | 			0x0c, 0x04, 0x4a, 0x43, 0x4f, 0xef, 0x00, 0x0c, 0x66, 0x22, 0x4a, 0x2d, 0xfc, 0x7c, 0x67, 0x0a, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0x20, 0x58, 0x8f, 0x4a, 0x2d, 0xfc, 0x78, 0x67, 0x0c, | ||||||
|  | 			0x45, 0xfa, 0x04, 0x40, 0x2f, 0x0a, 0x4e, 0xba, 0xfc, 0x78, 0x58, 0x8f, 0x2f, 0x04, 0x4e, 0xba, 0x0c, 0x46, 0x4a, 0x43, 0x58, 0x8f, 0x66, 0x2a, 0x4e, 0xba, 0x0b, 0x7e, 0x24, 0x00, 0x4e, 0xba, | ||||||
|  | 			0x0b, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0x0b, 0x78, 0x4e, 0xba, 0xf9, 0xe2, 0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x3c, 0x2f, 0x02, 0x4e, 0xba, 0x0b, 0x68, 0x42, 0xa7, 0x4e, 0xba, 0xf9, 0xc6, 0x4f, 0xef, | ||||||
|  | 			0x00, 0x10, 0x70, 0x00, 0x4c, 0xdf, 0x04, 0x1c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3e, 0x38, 0x24, 0x2e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf9, 0xb6, 0x2d, 0x40, | ||||||
|  | 			0xff, 0xf2, 0x28, 0x42, 0x3c, 0x2c, 0x00, 0x18, 0x46, 0x46, 0x04, 0x46, 0x00, 0x20, 0x32, 0x06, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, | ||||||
|  | 			0x42, 0x40, 0xd2, 0x80, 0x47, 0xed, 0xfc, 0xa0, 0xd7, 0xc1, 0x78, 0x00, 0x0c, 0x2a, 0x00, 0x03, 0x00, 0x07, 0x57, 0xc4, 0x44, 0x04, 0x28, 0x42, 0x2d, 0x6c, 0x00, 0x10, 0xff, 0xfc, 0x70, 0x09, | ||||||
|  | 			0x26, 0x2e, 0xff, 0xfc, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xfc, 0x2d, 0x6a, 0x00, 0x24, 0xff, 0xf8, 0x70, 0x09, 0x26, 0x2e, 0xff, 0xf8, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xf8, 0x30, 0x2b, 0x00, 0x16, | ||||||
|  | 			0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xe4, 0x20, 0x2a, 0x00, 0x24, 0x02, 0x80, 0x00, 0x00, 0x01, 0xff, 0x66, 0x00, 0x00, 0x18, 0x4a, 0xab, 0x00, 0x1c, 0x67, 0x16, | ||||||
|  | 			0x20, 0x2b, 0x00, 0x20, 0x22, 0x2e, 0xff, 0xf8, 0xd2, 0xae, 0xff, 0xfc, 0xb0, 0x81, 0x64, 0x06, 0x76, 0xce, 0x60, 0x00, 0x00, 0xba, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x4a, 0x30, | ||||||
|  | 			0x08, 0x00, 0x67, 0x20, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x42, 0x30, 0x08, 0x00, 0x70, 0x00, 0x10, 0x2d, 0xfc, 0x74, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, | ||||||
|  | 			0x03, 0xca, 0x50, 0x8f, 0x08, 0x2a, 0x00, 0x06, 0x00, 0x2d, 0x67, 0x0a, 0x4a, 0x44, 0x66, 0x06, 0x42, 0x43, 0x60, 0x00, 0x00, 0x6c, 0x42, 0x6e, 0xff, 0xf6, 0x7a, 0xff, 0x2f, 0x2a, 0x00, 0x20, | ||||||
|  | 			0x20, 0x2b, 0x00, 0x1c, 0xd0, 0xae, 0xff, 0xfc, 0x2f, 0x00, 0x2f, 0x2e, 0xff, 0xf8, 0x30, 0x04, 0x48, 0xc0, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x04, 0xae, 0x36, 0x00, | ||||||
|  | 			0x4f, 0xef, 0x00, 0x14, 0x67, 0x3a, 0x53, 0x43, 0x66, 0x10, 0x2f, 0x2b, 0x00, 0x28, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x42, 0x50, 0x8f, 0xba, 0xab, 0x00, 0x28, 0x66, 0x06, | ||||||
|  | 			0x52, 0x6e, 0xff, 0xf6, 0x60, 0x08, 0x42, 0x6e, 0xff, 0xf6, 0x2a, 0x2b, 0x00, 0x28, 0x0c, 0x6e, 0x00, 0x04, 0xff, 0xf6, 0x6d, 0x00, 0xff, 0xa6, 0x76, 0xdc, 0x42, 0xaa, 0x00, 0x28, 0x60, 0x0e, | ||||||
|  | 			0x20, 0x2a, 0x00, 0x24, 0x25, 0x40, 0x00, 0x28, 0x20, 0x42, 0xd1, 0xa8, 0x00, 0x10, 0x2f, 0x2e, 0xff, 0xf2, 0x4e, 0xba, 0x0a, 0xa2, 0x30, 0x03, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x1c, 0x7c, | ||||||
|  | 			0xff, 0xd0, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xfc, 0x48, 0xe7, 0x3c, 0x30, 0x26, 0x6e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf8, 0x3a, 0x2a, 0x00, 0x38, 0x2b, 0x00, 0x18, | ||||||
|  | 			0x46, 0x44, 0x04, 0x44, 0x00, 0x20, 0x30, 0x04, 0x48, 0xc0, 0x24, 0x00, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0xc4, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0x42, 0x42, 0xd0, 0x82, 0x41, 0xed, 0xfc, 0xa0, | ||||||
|  | 			0xd1, 0xc0, 0x42, 0x43, 0x0c, 0x6a, 0x00, 0x41, 0x00, 0x1a, 0x66, 0x44, 0x4a, 0x2d, 0xfc, 0x78, 0x66, 0x34, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x9f, 0x4e, 0xba, 0x09, 0xda, 0x24, 0x00, | ||||||
|  | 			0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x95, 0x4e, 0xba, 0x09, 0xcc, 0xb4, 0x80, 0x4f, 0xef, 0x00, 0x10, 0x67, 0x12, 0x45, 0xfa, 0x02, 0x0c, 0x2f, 0x0a, 0x4e, 0xba, 0xfa, 0x2c, 0x1b, 0x7c, | ||||||
|  | 			0x00, 0x01, 0xfc, 0x78, 0x58, 0x8f, 0x02, 0x6b, 0x4f, 0xff, 0x00, 0x04, 0x60, 0x00, 0x00, 0xb2, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xa2, | ||||||
|  | 			0x30, 0x2a, 0x00, 0x1a, 0x0c, 0x40, 0x00, 0x07, 0x6d, 0x06, 0x6e, 0x1e, 0x60, 0x00, 0x00, 0x7e, 0x0c, 0x40, 0x00, 0x05, 0x6d, 0x00, 0x00, 0x88, 0x6e, 0x04, 0x60, 0x00, 0x00, 0x84, 0x0c, 0x40, | ||||||
|  | 			0x00, 0x06, 0x66, 0x00, 0x00, 0x7a, 0x60, 0x00, 0x00, 0x78, 0x0c, 0x40, 0x00, 0x11, 0x6d, 0x00, 0x00, 0x6e, 0x6e, 0x02, 0x60, 0x08, 0x0c, 0x40, 0x00, 0x15, 0x66, 0x62, 0x60, 0x18, 0x43, 0xea, | ||||||
|  | 			0x00, 0x1c, 0x0c, 0x51, 0x00, 0x01, 0x66, 0x06, 0x42, 0xa8, 0x00, 0x1c, 0x60, 0x52, 0x21, 0x68, 0x00, 0x18, 0x00, 0x1c, 0x60, 0x4a, 0x4e, 0xba, 0xf7, 0x6c, 0x2d, 0x40, 0xff, 0xfc, 0x20, 0x2e, | ||||||
|  | 			0xff, 0xfc, 0x06, 0x80, 0x00, 0x00, 0x01, 0x00, 0x5c, 0x80, 0x32, 0x04, 0x48, 0xc1, 0x74, 0x30, 0xd2, 0x82, 0x26, 0x40, 0x16, 0x81, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfc, 0x48, 0x6a, | ||||||
|  | 			0x00, 0x1c, 0x4e, 0xba, 0x08, 0x02, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x14, 0x70, 0x00, 0x30, 0x28, 0x00, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba, 0x08, 0xdc, 0x50, 0x8f, 0x76, 0xef, | ||||||
|  | 			0x2f, 0x05, 0x4e, 0xba, 0x09, 0x52, 0x34, 0x03, 0x48, 0xc2, 0x58, 0x8f, 0x20, 0x02, 0x4c, 0xee, 0x0c, 0x3c, 0xfd, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x30, 0x26, 0x6f, 0x00, 0x14, | ||||||
|  | 			0x24, 0x6f, 0x00, 0x18, 0x42, 0x42, 0x4e, 0xba, 0xf6, 0xea, 0x26, 0x00, 0x32, 0x2b, 0x00, 0x18, 0x46, 0x41, 0x04, 0x41, 0x00, 0x20, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, | ||||||
|  | 			0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x04, 0x74, 0xc8, 0x60, 0x1e, 0x30, 0x2a, | ||||||
|  | 			0x00, 0x1a, 0x51, 0x40, 0x66, 0x14, 0x48, 0x78, 0x00, 0x16, 0x48, 0x50, 0x48, 0x6a, 0x00, 0x1c, 0x4e, 0xba, 0x07, 0x74, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x02, 0x74, 0xee, 0x2f, 0x03, 0x4e, 0xba, | ||||||
|  | 			0x08, 0xd6, 0x30, 0x02, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xdf, 0x0c, 0x0c, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x00, 0x4e, 0xba, 0xf6, 0x7e, 0x28, 0x00, 0x3b, 0x7c, 0x00, 0x65, 0xfc, 0x6a, 0x4a, 0x6d, | ||||||
|  | 			0xfc, 0x8c, 0x67, 0x58, 0x36, 0x3c, 0x00, 0x80, 0x74, 0x07, 0x72, 0x00, 0x32, 0x2d, 0xfc, 0x8c, 0x30, 0x03, 0x48, 0xc0, 0xc2, 0x80, 0x67, 0x38, 0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, | ||||||
|  | 			0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x70, 0x00, 0x30, 0x30, 0x18, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba, | ||||||
|  | 			0x07, 0xf6, 0x4a, 0x80, 0x50, 0x8f, 0x66, 0x08, 0x30, 0x03, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x8c, 0x30, 0x03, 0xe2, 0x40, 0x36, 0x00, 0x53, 0x42, 0x6c, 0x00, 0xff, 0xb0, 0x4a, 0x2d, 0xfc, 0x74, | ||||||
|  | 			0x66, 0x32, 0x22, 0x38, 0x02, 0xae, 0x20, 0x38, 0x0c, 0x54, 0x02, 0x80, 0x00, 0xff, 0xff, 0xff, 0xb2, 0x80, 0x63, 0x20, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x42, 0x42, 0x30, 0x02, 0x48, 0xc0, | ||||||
|  | 			0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x52, 0x42, 0x0c, 0x42, 0x00, 0x08, 0x6d, 0x00, 0xff, 0xea, 0x2f, 0x04, 0x4e, 0xba, 0x08, 0x1e, 0x58, 0x8f, 0x4c, 0xdf, 0x00, 0x1c, | ||||||
|  | 			0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3c, 0x00, 0x4e, 0xba, 0xf5, 0xc6, 0x2a, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x1b, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x06, 0xa2, | ||||||
|  | 			0x70, 0x01, 0x12, 0x2d, 0xfc, 0x70, 0xe3, 0xa0, 0x38, 0x00, 0x74, 0x00, 0x4f, 0xef, 0x00, 0x0c, 0x20, 0x02, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x36, 0x30, 0x08, 0x00, 0x70, 0x00, 0x30, 0x04, | ||||||
|  | 			0x72, 0x00, 0x32, 0x03, 0xc0, 0x81, 0x67, 0x46, 0x4a, 0x82, 0x67, 0x00, 0x00, 0x0e, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80, 0x00, 0x00, 0x40, 0x00, 0x66, 0x34, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80, | ||||||
|  | 			0x00, 0x00, 0x80, 0x00, 0x67, 0x08, 0x1d, 0x7c, 0x00, 0x01, 0xff, 0xfb, 0x60, 0x04, 0x42, 0x2e, 0xff, 0xfb, 0x48, 0x78, 0x03, 0x84, 0x42, 0xa7, 0x42, 0xa7, 0x42, 0xa7, 0x2f, 0x02, 0x48, 0x78, | ||||||
|  | 			0x00, 0x06, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x02, 0x70, 0x4f, 0xef, 0x00, 0x1c, 0x52, 0x82, 0x70, 0x08, 0xb0, 0x82, 0x6e, 0x00, 0xff, 0x9a, 0x2f, 0x05, 0x4e, 0xba, 0x07, 0x7a, 0x58, 0x8f, | ||||||
|  | 			0x4c, 0xee, 0x00, 0x3c, 0xff, 0xe8, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x00, 0x22, 0x2f, 0x00, 0x0c, 0x26, 0x2f, 0x00, 0x10, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, | ||||||
|  | 			0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x22, 0x48, 0x70, 0x01, 0xb0, 0x83, 0x66, 0x1a, 0x70, 0x00, 0x10, 0x29, 0x00, 0x27, 0x72, 0x01, 0x14, 0x2d, | ||||||
|  | 			0xfc, 0x70, 0xe5, 0xa1, 0xc0, 0x81, 0x67, 0x08, 0x13, 0x7c, 0x00, 0x01, 0x00, 0x26, 0x60, 0x04, 0x42, 0x29, 0x00, 0x26, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x30, 0x4a, 0x83, 0x67, 0x00, 0x00, 0x08, | ||||||
|  | 			0x4a, 0x29, 0x00, 0x25, 0x66, 0x16, 0x42, 0x29, 0x00, 0x24, 0x23, 0x7c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x3a, 0x60, 0x00, 0x00, 0xaa, 0x13, 0x69, 0x00, 0x25, | ||||||
|  | 			0x00, 0x24, 0x70, 0x00, 0x10, 0x29, 0x00, 0x24, 0x0c, 0x40, 0x00, 0x01, 0x6d, 0x00, 0x00, 0x96, 0x6e, 0x02, 0x60, 0x08, 0x55, 0x40, 0x66, 0x00, 0x00, 0x8c, 0x60, 0x22, 0x23, 0x7c, 0x00, 0x00, | ||||||
|  | 			0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x3a, 0x70, 0xf6, 0x23, 0x40, 0x00, 0x3c, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x44, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x2e, 0x60, 0x66, 0x70, 0x00, | ||||||
|  | 			0x30, 0x29, 0x00, 0x2c, 0x23, 0x40, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x3a, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x3c, 0x41, 0xe9, 0x00, 0x46, 0x23, 0x48, 0x00, 0x40, 0x33, 0x7c, | ||||||
|  | 			0x00, 0x01, 0x00, 0x44, 0x20, 0x3c, 0x00, 0x00, 0x02, 0x00, 0x72, 0x00, 0x32, 0x29, 0x00, 0x2c, 0x90, 0x81, 0x23, 0x40, 0x00, 0x4a, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x4e, 0x41, 0xe9, 0x00, 0x46, | ||||||
|  | 			0x23, 0x48, 0x00, 0x50, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x54, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x58, 0x70, 0xd8, 0x23, 0x40, 0x00, 0x5a, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x62, 0x33, 0x7c, | ||||||
|  | 			0x00, 0x04, 0x00, 0x2e, 0x4c, 0xdf, 0x00, 0x0c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3c, 0x20, 0x38, 0x2e, 0x00, 0x0a, 0x36, 0x2e, 0x00, 0x0e, 0x24, 0x2e, 0x00, 0x10, 0x4a, 0x43, | ||||||
|  | 			0x67, 0x04, 0x70, 0x0a, 0x60, 0x02, 0x70, 0x08, 0x48, 0x78, 0x00, 0x06, 0x2f, 0x00, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x04, 0xcc, 0x72, 0x00, 0x32, 0x04, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, | ||||||
|  | 			0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x25, 0x6e, 0x00, 0x18, 0x00, 0x32, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x00, 0x00, 0xc8, | ||||||
|  | 			0x20, 0x2e, 0x00, 0x14, 0x72, 0x10, 0xe2, 0xa8, 0x1d, 0x40, 0xff, 0xf3, 0x41, 0xee, 0xff, 0xf4, 0x30, 0xae, 0x00, 0x16, 0x4a, 0x2a, 0x00, 0x24, 0x66, 0x04, 0x7a, 0x01, 0x60, 0x28, 0x0c, 0x82, | ||||||
|  | 			0x00, 0x00, 0x01, 0x00, 0x63, 0x08, 0x2a, 0x3c, 0x00, 0x00, 0x01, 0x00, 0x60, 0x02, 0x2a, 0x02, 0x72, 0x00, 0x32, 0x2a, 0x00, 0x2e, 0xd2, 0x81, 0x20, 0x01, 0xe5, 0x81, 0xd2, 0x80, 0x20, 0x05, | ||||||
|  | 			0x2a, 0x00, 0x25, 0x80, 0x18, 0x36, 0x1d, 0x45, 0xff, 0xf6, 0x48, 0x78, 0x2a, 0x30, 0x70, 0x00, 0x10, 0x2a, 0x00, 0x26, 0x2f, 0x00, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x48, 0x6a, 0x00, 0x30, | ||||||
|  | 			0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x00, 0x68, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x1c, 0x67, 0x44, 0x0c, 0x40, 0x00, 0x02, 0x67, 0x04, | ||||||
|  | 			0x48, 0xc0, 0x60, 0x4a, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xf8, 0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x4e, 0xba, 0x01, 0x58, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xf9, | ||||||
|  | 			0x60, 0x2c, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfb, 0x48, 0x6a, 0x00, 0x28, 0x4e, 0xba, 0x03, 0xd6, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xfa, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x10, 0x94, 0x85, | ||||||
|  | 			0x20, 0x05, 0xd1, 0xae, 0x00, 0x14, 0x4a, 0x82, 0x62, 0x00, 0xff, 0x36, 0x70, 0x00, 0x4c, 0xee, 0x04, 0x3c, 0xff, 0xdc, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3e, 0x00, | ||||||
|  | 			0x26, 0x2e, 0x00, 0x08, 0x38, 0x2e, 0x00, 0x0e, 0x3a, 0x2e, 0x00, 0x12, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x4e, 0xba, 0x03, 0xcc, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x1e, | ||||||
|  | 			0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe6, 0x55, 0x8f, 0x4e, 0xba, 0x04, 0x00, 0x30, 0x1f, 0x08, 0x00, 0x00, 0x06, 0x67, 0xd2, 0x60, 0xf0, 0x55, 0x8f, | ||||||
|  | 			0x3f, 0x05, 0x4e, 0xba, 0x03, 0xa8, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x06, 0x70, 0xfe, 0x60, 0x00, 0x00, 0xb0, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x2f, 0x03, 0x3f, 0x04, 0x4e, 0xba, | ||||||
|  | 			0x03, 0x96, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x12, 0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe2, 0x7c, 0xfd, 0x60, 0x52, 0x4a, 0xae, 0x00, 0x14, | ||||||
|  | 			0x67, 0x4c, 0x4a, 0x6e, 0x00, 0x1e, 0x67, 0x24, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x92, 0x60, 0x0a, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, | ||||||
|  | 			0x4e, 0xba, 0x03, 0x7c, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x60, 0x22, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x5a, 0x60, 0x0a, 0x55, 0x8f, | ||||||
|  | 			0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x44, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x55, 0x8f, 0x48, 0x6e, 0xff, 0xfa, 0x48, 0x6e, 0xff, 0xfc, 0x2f, 0x2e, 0x00, 0x20, 0x4e, 0xba, 0x03, 0x22, | ||||||
|  | 			0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x04, 0x70, 0xfc, 0x60, 0x16, 0x4a, 0x46, 0x67, 0x0c, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x67, 0x04, 0x30, 0x06, 0x60, 0x04, 0x30, 0x2e, 0xff, 0xfa, | ||||||
|  | 			0x48, 0xc0, 0x4c, 0xee, 0x00, 0x7c, 0xff, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xe4, 0x48, 0xe7, 0x38, 0x00, 0x36, 0x2e, 0x00, 0x0a, 0x28, 0x2e, 0x00, 0x0c, 0x34, 0x2e, 0x00, 0x12, | ||||||
|  | 			0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x03, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0x02, 0x92, 0x1d, 0x42, 0xff, 0xea, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x2d, 0x44, 0xff, 0xee, 0x70, 0x00, | ||||||
|  | 			0x30, 0x02, 0x2d, 0x40, 0xff, 0xf2, 0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x48, 0x78, 0x02, 0x58, 0x42, 0xa7, 0x42, 0xa7, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x03, 0x48, 0xc0, 0x2f, 0x00, 0x48, 0x78, | ||||||
|  | 			0x00, 0x06, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0xfe, 0x90, 0x4f, 0xef, 0x00, 0x28, 0x4c, 0xee, 0x00, 0x1c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf4, 0x48, 0xe7, 0x3e, 0x30, | ||||||
|  | 			0x24, 0x2e, 0x00, 0x08, 0x26, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0x02, 0xce, 0x2d, 0x40, 0xff, 0xf6, 0x4e, 0xba, 0x02, 0xd6, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xc6, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba, | ||||||
|  | 			0x02, 0xd8, 0x24, 0x40, 0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x66, 0x1e, 0x4e, 0xba, 0x02, 0xc2, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xaa, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba, 0x02, 0xbc, 0x24, 0x40, | ||||||
|  | 			0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x67, 0x00, 0x01, 0x16, 0x2f, 0x0a, 0x4e, 0xba, 0x02, 0xbe, 0x26, 0x52, 0x20, 0x4b, 0x41, 0xe8, 0x02, 0x00, 0x2d, 0x48, 0xff, 0xfc, 0x42, 0x6e, 0xff, 0xfa, | ||||||
|  | 			0x58, 0x8f, 0x42, 0x45, 0x42, 0x46, 0x42, 0x44, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0xd2, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, | ||||||
|  | 			0x67, 0x20, 0x53, 0x40, 0x66, 0x18, 0x52, 0x46, 0xba, 0x44, 0x66, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x8e, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x45, | ||||||
|  | 			0x60, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x78, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6d, 0x00, 0xff, 0xac, 0x4a, 0x45, | ||||||
|  | 			0x67, 0x00, 0x00, 0x9c, 0x4a, 0x46, 0x67, 0x00, 0x00, 0x96, 0x42, 0x44, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x6e, 0x30, 0x00, | ||||||
|  | 			0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0x00, 0x28, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x52, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, | ||||||
|  | 			0x66, 0x00, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6c, 0x52, 0x60, 0xbe, 0x52, 0x6e, 0xff, 0xfa, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x6e, 0x00, 0x00, 0x44, 0x2f, 0x03, 0x2f, 0x02, | ||||||
|  | 			0x4e, 0xba, 0x00, 0x56, 0x50, 0x8f, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x14, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, | ||||||
|  | 			0x00, 0x1e, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfb, 0xf8, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0xff, 0x06, 0x2f, 0x0a, | ||||||
|  | 			0x4e, 0xba, 0x01, 0xa0, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x01, 0x74, 0x50, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xd4, 0x48, 0xe7, 0x30, 0x00, | ||||||
|  | 			0x34, 0x2e, 0x00, 0x0a, 0x26, 0x2e, 0x00, 0x0c, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x07, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0x00, 0xaa, 0x48, 0x78, 0x00, 0x08, 0x42, 0xa7, 0x48, 0x6e, | ||||||
|  | 			0xff, 0xd6, 0x4e, 0xba, 0x00, 0x9c, 0x1d, 0x7c, 0x00, 0x04, 0xff, 0xd9, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x41, 0xee, 0xff, 0xd6, 0x2d, 0x48, 0xff, 0xee, 0x70, 0x08, 0x2d, 0x40, 0xff, 0xf2, | ||||||
|  | 			0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x41, 0xee, 0xff, 0xda, 0x20, 0x83, 0x48, 0x78, 0x0e, 0x10, 0x42, 0xa7, 0x48, 0x78, 0x00, 0x01, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x02, 0x48, 0xc0, 0x2f, 0x00, | ||||||
|  | 			0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0xfc, 0x8e, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x34, 0x67, 0x26, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xe4, 0x30, 0x02, 0x48, 0xc0, | ||||||
|  | 			0x2f, 0x00, 0x4e, 0xba, 0xfd, 0x88, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xff, 0x60, 0x0a, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xe6, 0x60, 0x02, 0x70, 0x00, 0x4c, 0xee, 0x00, 0x0c, | ||||||
|  | 			0xff, 0xcc, 0x4e, 0x5e, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x2f, 0x00, 0x0c, 0x60, 0x04, 0x10, 0xd9, 0x53, 0x80, 0x4a, 0x80, 0x66, 0x00, 0xff, 0xf8, 0x4e, 0x75, | ||||||
|  | 			0x22, 0x6f, 0x00, 0x04, 0x10, 0x2f, 0x00, 0x0b, 0x32, 0x2f, 0x00, 0x0e, 0x48, 0xc1, 0x60, 0x06, 0x20, 0x41, 0xd1, 0xc9, 0x42, 0x10, 0x53, 0x81, 0x4a, 0x81, 0x6c, 0x00, 0xff, 0xf4, 0x12, 0x80, | ||||||
|  | 			0x4e, 0x75, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x01, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x02, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x03, 0x2f, 0x08, 0xac, 0x15, | ||||||
|  | 			0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x04, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x05, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x06, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, | ||||||
|  | 			0x3f, 0x3c, 0x00, 0x08, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x09, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x0a, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x6f, 0x00, 0x0c, | ||||||
|  | 			0x30, 0x2f, 0x00, 0x0a, 0x48, 0x40, 0x30, 0x2f, 0x00, 0x06, 0xa0, 0x4e, 0x4e, 0x75, 0x70, 0x00, 0x31, 0xc0, 0x02, 0x20, 0x20, 0x08, 0x4e, 0x75, 0xa1, 0x1a, 0x4e, 0xfa, 0xff, 0xf4, 0x20, 0x6f, | ||||||
|  | 			0x00, 0x04, 0xa0, 0x1b, 0x4e, 0xfa, 0xff, 0xea, 0x20, 0x78, 0x02, 0xa6, 0x4e, 0xfa, 0xff, 0xe0, 0x20, 0x78, 0x02, 0xaa, 0x4e, 0xfa, 0xff, 0xd8, 0x20, 0x2f, 0x00, 0x04, 0xa1, 0x22, 0x4e, 0xfa, | ||||||
|  | 			0xff, 0xd0, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x23, 0x4e, 0xfa, 0xff, 0xc6, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x29, 0x4e, 0xfa, 0xff, 0xbc, 0x30, 0x6f, 0x00, 0x06, 0x20, 0x2f, 0x00, 0x08, 0xa0, 0x2f, | ||||||
|  | 			0x48, 0xc0, 0x4e, 0x75, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x6f, 0x00, 0x04, 0x2f, 0x02, 0xa9, 0x6e, 0x24, 0x1f, 0x48, 0xc0, 0x4e, 0x75, 0x30, 0x2f, 0x00, 0x06, 0x12, 0x2f, 0x00, 0x0b, 0x4a, 0x01, | ||||||
|  | 			0x67, 0x04, 0xa7, 0x46, 0x60, 0x02, 0xa3, 0x46, 0x20, 0x08, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x33, 0x48, 0xc0, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x34, 0x48, 0xc0, 0x4e, 0x75, | ||||||
|  | 			0x4e, 0xba, 0x00, 0x58, 0x06, 0x80, 0x00, 0x00, 0x00, 0x20, 0x4e, 0x75, 0x2f, 0x0d, 0x20, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x66, 0x0c, 0x20, 0x6f, 0x00, 0x08, 0x70, 0x07, 0x20, 0xdd, 0x51, 0xc8, | ||||||
|  | 			0xff, 0xfc, 0x2a, 0x6f, 0x00, 0x08, 0x4e, 0xba, 0x00, 0x3a, 0x2a, 0x5f, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x4e, 0xba, 0xff, 0xc2, | ||||||
|  | 			0xa1, 0x1e, 0x2f, 0x08, 0x4e, 0xba, 0x00, 0x14, 0xd0, 0x9f, 0x4e, 0x75, 0x4e, 0xba, 0x00, 0x0c, 0x20, 0x6f, 0x00, 0x04, 0x91, 0xc0, 0xa0, 0x1f, 0x4e, 0x75, 0x41, 0xfa, 0x00, 0xce, 0x20, 0x10, | ||||||
|  | 			0x4e, 0x75, 0x48, 0xe7, 0xff, 0xf0, 0x42, 0x47, 0x41, 0xfa, 0x00, 0xc0, 0x22, 0x18, 0x6f, 0x00, 0x00, 0xa6, 0x2a, 0x18, 0x66, 0x04, 0x2a, 0x0d, 0x9a, 0x81, 0x26, 0x45, 0x24, 0x4b, 0xe2, 0x81, | ||||||
|  | 			0x60, 0x02, 0x42, 0x5a, 0x51, 0xc9, 0xff, 0xfc, 0x30, 0x18, 0x3e, 0x18, 0x60, 0x00, 0x00, 0x84, 0x78, 0x00, 0x18, 0x18, 0x22, 0x04, 0x02, 0x01, 0x00, 0x0f, 0x08, 0x04, 0x00, 0x04, 0x67, 0x0e, | ||||||
|  | 			0xe1, 0x41, 0x12, 0x18, 0x08, 0x81, 0x00, 0x0b, 0x67, 0x04, 0xe1, 0x81, 0x12, 0x18, 0x74, 0x01, 0x08, 0x04, 0x00, 0x07, 0x67, 0x16, 0x14, 0x18, 0x08, 0x82, 0x00, 0x07, 0x67, 0x0e, 0xe1, 0x42, | ||||||
|  | 			0x14, 0x18, 0x08, 0x82, 0x00, 0x0e, 0x67, 0x04, 0xe1, 0x82, 0x14, 0x18, 0x7c, 0x02, 0x4e, 0xba, 0x00, 0x4c, 0x08, 0x85, 0x00, 0x0f, 0x67, 0x04, 0xe1, 0x85, 0x1a, 0x18, 0x43, 0xf3, 0x58, 0x00, | ||||||
|  | 			0x24, 0x49, 0x08, 0x04, 0x00, 0x05, 0x67, 0x1c, 0x2c, 0x01, 0x4e, 0xba, 0x00, 0x30, 0x0c, 0x41, 0x00, 0x02, 0x67, 0x06, 0x6d, 0x08, 0xdb, 0x92, 0x60, 0x0e, 0xdb, 0x52, 0x60, 0x0a, 0xdb, 0x12, | ||||||
|  | 			0x60, 0x06, 0x12, 0xd8, 0x51, 0xc9, 0xff, 0xfc, 0x08, 0x04, 0x00, 0x06, 0x67, 0x04, 0x26, 0x0d, 0xd7, 0x92, 0x51, 0xc8, 0xff, 0x7c, 0x4c, 0xdf, 0x0f, 0xff, 0x4e, 0x75, 0x7a, 0x00, 0x60, 0x04, | ||||||
|  | 			0xe1, 0x8d, 0x1a, 0x18, 0x51, 0xce, 0xff, 0xfa, 0x4e, 0x75, 0x00, 0x00, 0x03, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x8a, 0x25, 0x41, 0x35, 0x69, 0x80, 0x00, | ||||||
|  | 			0x03, 0x7e, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x19, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x53, 0x43, 0x53, 0x49, 0x20, 0x64, 0x72, 0x69, 0x76, | ||||||
|  | 			0x65, 0x73, 0x2e, 0x2c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2e, 0x2e, 0x2e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, | ||||||
|  | 			0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const auto offset = (source_address - 0x40) * 512; | ||||||
|  | 		return std::vector<uint8_t>(&driver[offset], &driver[offset + 512]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Default: return an empty block. | ||||||
|  | 	return std::vector<uint8_t>(512); | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								Storage/MassStorage/Encodings/MacintoshVolume.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Storage/MassStorage/Encodings/MacintoshVolume.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | // | ||||||
|  | //  MacintoshVolume.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MacintoshVolume_hpp | ||||||
|  | #define MacintoshVolume_hpp | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  | #include <cstdint> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <unistd.h> | ||||||
|  |  | ||||||
|  | namespace Storage { | ||||||
|  | namespace MassStorage { | ||||||
|  | namespace Encodings { | ||||||
|  | namespace Macintosh { | ||||||
|  |  | ||||||
|  | enum class DriveType { | ||||||
|  | 	SCSI | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	On the Macintosh life is slightly complicated by Apple's | ||||||
|  | 	decision to include device drivers on mass storage drives | ||||||
|  | 	themselves — therefore a mass-storage device that is | ||||||
|  | 	connected by SCSI will have different preliminary data on it | ||||||
|  | 	than the same volume connected by ATA or as an HD20. | ||||||
|  |  | ||||||
|  | 	Mass storage devices that respond to @c Volume can be made | ||||||
|  | 	to provide the proper whole-volume encoding necessary to | ||||||
|  | 	impersonate different types of Macintosh drive. | ||||||
|  | */ | ||||||
|  | class Volume { | ||||||
|  | 	public: | ||||||
|  | 		virtual void set_drive_type(DriveType type) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A Mapper can used by a mass-storage device that knows the | ||||||
|  | 	contents of an HFS or MFS partition to provide the conversion | ||||||
|  | 	necessary to a particular type of drive. | ||||||
|  | */ | ||||||
|  | class Mapper { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Sets the drive type to map to and the number of blocks in the underlying partition. | ||||||
|  | 		*/ | ||||||
|  | 		void set_drive_type(DriveType, size_t number_of_blocks); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Maps from a mass-storage device address to an address | ||||||
|  | 			in the underlying [H/M]FS partition. | ||||||
|  | 		*/ | ||||||
|  | 		ssize_t to_source_address(size_t address); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Converts from a source data block to one properly encoded for the drive type. | ||||||
|  |  | ||||||
|  | 			Expected usage: | ||||||
|  |  | ||||||
|  | 				const size_t source_address = mapper.to_source_address(unit_address); | ||||||
|  | 				if(is_in_range_for_partition(source_address)) { | ||||||
|  | 					return mapper.convert_source_block(source_address, get_block_contents(source_address)); | ||||||
|  | 				} else { | ||||||
|  | 					return mapper.convert_source_block(source_address); | ||||||
|  | 				} | ||||||
|  | 		*/ | ||||||
|  | 		std::vector<uint8_t> convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data = {}); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns The total number of blocks on the entire volume. | ||||||
|  | 		*/ | ||||||
|  | 		size_t get_number_of_blocks(); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		DriveType drive_type_; | ||||||
|  | 		size_t number_of_blocks_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MacintoshVolume_hpp */ | ||||||
							
								
								
									
										56
									
								
								Storage/MassStorage/Formats/HFV.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Storage/MassStorage/Formats/HFV.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | // | ||||||
|  | //  HFV.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "HFV.hpp" | ||||||
|  |  | ||||||
|  | using namespace Storage::MassStorage; | ||||||
|  |  | ||||||
|  | HFV::HFV(const std::string &file_name) : file_(file_name) { | ||||||
|  | 	// Is the file a multiple of 512 bytes in size and larger than a floppy disk? | ||||||
|  | 	const auto file_size = file_.stats().st_size; | ||||||
|  | 	if(file_size & 511 || file_size <= 800*1024) throw std::exception(); | ||||||
|  |  | ||||||
|  | 	// TODO: check filing system for MFS, HFS or HFS+. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t HFV::get_block_size() { | ||||||
|  | 	return 512; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t HFV::get_number_of_blocks() { | ||||||
|  | 	return mapper_.get_number_of_blocks(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<uint8_t> HFV::get_block(size_t address) { | ||||||
|  | 	const auto written = writes_.find(address); | ||||||
|  | 	if(written != writes_.end()) return written->second; | ||||||
|  |  | ||||||
|  | 	const auto source_address = mapper_.to_source_address(address); | ||||||
|  | 	if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) { | ||||||
|  | 		const long file_offset = long(get_block_size()) * long(source_address); | ||||||
|  | 		file_.seek(file_offset, SEEK_SET); | ||||||
|  | 		return mapper_.convert_source_block(source_address, file_.read(get_block_size())); | ||||||
|  | 	} else { | ||||||
|  | 		return mapper_.convert_source_block(source_address); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HFV::set_block(size_t address, const std::vector<uint8_t> &contents) { | ||||||
|  | 	const auto source_address = mapper_.to_source_address(address); | ||||||
|  | 	if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) { | ||||||
|  | 		const long file_offset = long(get_block_size()) * long(source_address); | ||||||
|  | 		file_.seek(file_offset, SEEK_SET); | ||||||
|  | 		file_.write(contents); | ||||||
|  | 	} else { | ||||||
|  | 		writes_[address] = contents; | ||||||
|  |  	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HFV::set_drive_type(Encodings::Macintosh::DriveType drive_type) { | ||||||
|  | 	mapper_.set_drive_type(drive_type, size_t(file_.stats().st_size) / get_block_size()); | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								Storage/MassStorage/Formats/HFV.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Storage/MassStorage/Formats/HFV.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | // | ||||||
|  | //  HFV.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 25/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef HFV_hpp | ||||||
|  | #define HFV_hpp | ||||||
|  |  | ||||||
|  | #include "../MassStorageDevice.hpp" | ||||||
|  | #include "../../FileHolder.hpp" | ||||||
|  | #include "../Encodings/MacintoshVolume.hpp" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | namespace Storage { | ||||||
|  | namespace MassStorage { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Provides a @c MassStorageDevice containing an HFV image, which is a sector dump of | ||||||
|  | 	the HFS volume of a Macintosh drive that is not the correct size to be a floppy disk. | ||||||
|  | */ | ||||||
|  | class HFV: public MassStorageDevice, public Encodings::Macintosh::Volume { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Constructs an HFV with the contents of the file named @c file_name. | ||||||
|  | 			Raises an exception if the file name doesn't appear to identify a valid | ||||||
|  | 			Macintosh mass storage image. | ||||||
|  | 		*/ | ||||||
|  | 		HFV(const std::string &file_name); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		FileHolder file_; | ||||||
|  | 		Encodings::Macintosh::Mapper mapper_; | ||||||
|  |  | ||||||
|  | 		/* MassStorageDevices overrides. */ | ||||||
|  | 		size_t get_block_size() final; | ||||||
|  | 		size_t get_number_of_blocks() final; | ||||||
|  | 		std::vector<uint8_t> get_block(size_t address) final; | ||||||
|  | 		void set_block(size_t address, const std::vector<uint8_t> &) final; | ||||||
|  |  | ||||||
|  | 		/* Encodings::Macintosh::Volume overrides. */ | ||||||
|  | 		void set_drive_type(Encodings::Macintosh::DriveType) final; | ||||||
|  |  | ||||||
|  | 		std::map<size_t, std::vector<uint8_t>> writes_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* HFV_hpp */ | ||||||
							
								
								
									
										9
									
								
								Storage/MassStorage/MassStorageDevice.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Storage/MassStorage/MassStorageDevice.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | // | ||||||
|  | //  MassStorageDevice.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "MassStorageDevice.hpp" | ||||||
							
								
								
									
										61
									
								
								Storage/MassStorage/MassStorageDevice.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Storage/MassStorage/MassStorageDevice.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | // | ||||||
|  | //  MassStorageDevice.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 21/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef MassStorageDevice_hpp | ||||||
|  | #define MassStorageDevice_hpp | ||||||
|  |  | ||||||
|  | #include <cstddef> | ||||||
|  | #include <cstdint> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace Storage { | ||||||
|  | namespace MassStorage { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A mass storage device is usually: | ||||||
|  |  | ||||||
|  | 		* large; | ||||||
|  | 		* fixed; and | ||||||
|  | 		* part of a class with a very wide array of potential speeds and timings. | ||||||
|  |  | ||||||
|  | 	Within this emulator, mass storage devices don't attempt to emulate | ||||||
|  | 	any specific medium, they just offer block-based access to a | ||||||
|  | 	linearly-addressed store. | ||||||
|  | */ | ||||||
|  | class MassStorageDevice { | ||||||
|  | 	public: | ||||||
|  | 		virtual ~MassStorageDevice() {} | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns The size of each individual block. | ||||||
|  | 		*/ | ||||||
|  | 		virtual size_t get_block_size() = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Block addresses run from 0 to n. The total number of blocks, n, | ||||||
|  | 			therefore provides the range of valid addresses. | ||||||
|  |  | ||||||
|  | 			@returns The total number of blocks on the device. | ||||||
|  | 		*/ | ||||||
|  | 		virtual size_t get_number_of_blocks() = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns The current contents of the block at @c address. | ||||||
|  | 		*/ | ||||||
|  | 		virtual std::vector<uint8_t> get_block(size_t address) = 0; | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets new contents for the block at @c address. | ||||||
|  | 		*/ | ||||||
|  | 		virtual void set_block(size_t address, const std::vector<uint8_t> &) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* MassStorageDevice_hpp */ | ||||||
							
								
								
									
										86
									
								
								Storage/MassStorage/SCSI/DirectAccessDevice.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Storage/MassStorage/SCSI/DirectAccessDevice.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | // | ||||||
|  | //  DirectAccessDevice.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "DirectAccessDevice.hpp" | ||||||
|  | #include "../../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | using namespace SCSI; | ||||||
|  |  | ||||||
|  | void DirectAccessDevice::set_storage(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device) { | ||||||
|  | 	device_ = device; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DirectAccessDevice::read(const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 	if(!device_) return false; | ||||||
|  |  | ||||||
|  | 	const auto specs = state.read_write_specs(); | ||||||
|  | 	LOG("Read: " << specs.number_of_blocks << " from " << specs.address); | ||||||
|  |  | ||||||
|  | 	std::vector<uint8_t> output = device_->get_block(specs.address); | ||||||
|  | 	for(uint32_t offset = 1; offset < specs.number_of_blocks; ++offset) { | ||||||
|  | 		const auto next_block = device_->get_block(specs.address + offset); | ||||||
|  | 		std::copy(next_block.begin(), next_block.end(), std::back_inserter(output)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	responder.send_data(std::move(output), [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 		responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DirectAccessDevice::write(const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 	if(!device_) return false; | ||||||
|  |  | ||||||
|  | 	const auto specs = state.read_write_specs(); | ||||||
|  |  | ||||||
|  | 	responder.receive_data(device_->get_block_size() * specs.number_of_blocks, [this, specs] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 		const auto received_data = state.received_data(); | ||||||
|  | 		const auto block_size = ssize_t(device_->get_block_size()); | ||||||
|  | 		for(uint32_t offset = 0; offset < specs.number_of_blocks; ++offset) { | ||||||
|  | 			// TODO: clean up this gross inefficiency when std::span is standard. | ||||||
|  | 			std::vector<uint8_t> sub_vector(received_data.begin() + ssize_t(offset)*block_size, received_data.begin() + ssize_t(offset+1)*block_size); | ||||||
|  | 			this->device_->set_block(specs.address + offset, sub_vector); | ||||||
|  | 		} | ||||||
|  | 		responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DirectAccessDevice::read_capacity(const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 	const auto final_block = device_->get_number_of_blocks() - 1; | ||||||
|  | 	const auto block_size = device_->get_block_size(); | ||||||
|  | 	std::vector<uint8_t> data = { | ||||||
|  | 		uint8_t(final_block >> 24), | ||||||
|  | 		uint8_t(final_block >> 16), | ||||||
|  | 		uint8_t(final_block >> 8), | ||||||
|  | 		uint8_t(final_block >> 0), | ||||||
|  |  | ||||||
|  | 		uint8_t(block_size >> 24), | ||||||
|  | 		uint8_t(block_size >> 16), | ||||||
|  | 		uint8_t(block_size >> 8), | ||||||
|  | 		uint8_t(block_size >> 0), | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	responder.send_data(std::move(data), [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 		responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Target::Executor::Inquiry DirectAccessDevice::inquiry_values() { | ||||||
|  | 	return Inquiry("Apple", "ProFile", "1");	// All just guesses. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DirectAccessDevice::format_unit(const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 	// Formatting: immediate. | ||||||
|  | 	responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								Storage/MassStorage/SCSI/DirectAccessDevice.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Storage/MassStorage/SCSI/DirectAccessDevice.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // | ||||||
|  | //  DirectAccessDevice.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 22/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef SCSI_DirectAccessDevice_hpp | ||||||
|  | #define SCSI_DirectAccessDevice_hpp | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  | #include "../MassStorageDevice.hpp" | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | namespace SCSI { | ||||||
|  |  | ||||||
|  | class DirectAccessDevice: public Target::Executor { | ||||||
|  | 	public: | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the backing storage exposed by this direct-access device. | ||||||
|  | 		*/ | ||||||
|  | 		void set_storage(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device); | ||||||
|  |  | ||||||
|  | 		/* SCSI commands. */ | ||||||
|  | 		bool read(const Target::CommandState &, Target::Responder &); | ||||||
|  | 		bool write(const Target::CommandState &, Target::Responder &); | ||||||
|  | 		Inquiry inquiry_values(); | ||||||
|  | 		bool read_capacity(const Target::CommandState &, Target::Responder &); | ||||||
|  | 		bool format_unit(const Target::CommandState &, Target::Responder &); | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		std::shared_ptr<Storage::MassStorage::MassStorageDevice> device_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* SCSI_DirectAccessDevice_hpp */ | ||||||
							
								
								
									
										113
									
								
								Storage/MassStorage/SCSI/SCSI.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Storage/MassStorage/SCSI/SCSI.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | // | ||||||
|  | //  SCSI.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 12/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "SCSI.hpp" | ||||||
|  |  | ||||||
|  | using namespace SCSI; | ||||||
|  |  | ||||||
|  | Bus::Bus(HalfCycles clock_rate) { | ||||||
|  | 	cycles_to_time_ = 1.0 / double(clock_rate.as_int()); | ||||||
|  |  | ||||||
|  | 	// NB: note that the dispatch times below are **ORDERED** | ||||||
|  | 	// from least to greatest. Each box should contain the number | ||||||
|  | 	// of whole clock periods it will take to get the the first | ||||||
|  | 	// discrete moment after the required delay interval has been met. | ||||||
|  | 	dispatch_times_[0] = 1 + int(CableSkew / cycles_to_time_); | ||||||
|  | 	dispatch_times_[1] = 1 + int(DeskewDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[2] = 1 + int(BusFreeDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[3] = 1 + int(BusSettleDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[4] = 1 + int(BusClearDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[5] = 1 + int(BusSetDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[6] = 1 + int(ArbitrationDelay / cycles_to_time_); | ||||||
|  | 	dispatch_times_[7] = 1 + int(ResetHoldTime / cycles_to_time_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t Bus::add_device() { | ||||||
|  | 	const auto slot = device_states_.size(); | ||||||
|  | 	device_states_.push_back(DefaultBusState); | ||||||
|  | 	return slot; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Bus::set_device_output(size_t device, BusState output) { | ||||||
|  | 	if(device_states_[device] == output) return; | ||||||
|  | 	device_states_[device] = output; | ||||||
|  |  | ||||||
|  | 	const auto previous_state = state_; | ||||||
|  | 	state_ = DefaultBusState; | ||||||
|  | 	for(auto state: device_states_) { | ||||||
|  | 		state_ |= state; | ||||||
|  | 	} | ||||||
|  | 	if(state_ == previous_state) return; | ||||||
|  |  | ||||||
|  | 	if(activity_observer_ && (state_^previous_state)&SCSI::Line::Busy) { | ||||||
|  | 		activity_observer_->set_led_status("SCSI", state_&SCSI::Line::Busy); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | //	printf("SCSI bus: %02x %c%c%c%c%c%c%c%c%c%c\n", | ||||||
|  | //		state_ & 0xff, | ||||||
|  | //		(state_ & Line::Parity) ? 'p' : '-', | ||||||
|  | //		(state_ & Line::SelectTarget) ? 's' : '-', | ||||||
|  | //		(state_ & Line::Attention) ? 't' : '-', | ||||||
|  | //		(state_ & Line::Control) ? 'c' : '-', | ||||||
|  | //		(state_ & Line::Busy) ? 'b' : '-', | ||||||
|  | //		(state_ & Line::Acknowledge) ? 'a' : '-', | ||||||
|  | //		(state_ & Line::Reset) ? 'r' : '-', | ||||||
|  | //		(state_ & Line::Input) ? 'i' : '-', | ||||||
|  | //		(state_ & Line::Message) ? 'm' : '-', | ||||||
|  | //		(state_ & Line::Request) ? 'q' : '-' | ||||||
|  | //	); | ||||||
|  |  | ||||||
|  | 	bool was_asleep = preferred_clocking() == ClockingHint::Preference::None; | ||||||
|  | 	dispatch_index_ = 0; | ||||||
|  | 	time_in_state_ = HalfCycles(0); | ||||||
|  | 	if(was_asleep) update_clocking_observer(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Bus::set_activity_observer(Activity::Observer *observer) { | ||||||
|  | 	activity_observer_ = observer; | ||||||
|  | 	activity_observer_->register_led("SCSI"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BusState Bus::get_state() { | ||||||
|  | 	return state_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Bus::add_observer(Observer *observer) { | ||||||
|  | 	observers_.push_back(observer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ClockingHint::Preference Bus::preferred_clocking() { | ||||||
|  | 	return (dispatch_index_ < dispatch_times_.size()) ? ClockingHint::Preference::RealTime : ClockingHint::Preference::None; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Bus::update_observers() { | ||||||
|  | 	const auto time_elapsed = double(time_in_state_.as_int()) * cycles_to_time_; | ||||||
|  | 	for(auto &observer: observers_) { | ||||||
|  | 		observer->scsi_bus_did_change(this, state_, time_elapsed); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Bus::run_for(HalfCycles time) { | ||||||
|  | 	if(dispatch_index_ < dispatch_times_.size()) { | ||||||
|  | 		time_in_state_ += time; | ||||||
|  |  | ||||||
|  | 		const auto old_index = dispatch_index_; | ||||||
|  | 		const auto time_as_int = time_in_state_.as_int(); | ||||||
|  | 		while(time_as_int >= dispatch_times_[dispatch_index_] && dispatch_index_ < dispatch_times_.size()) { | ||||||
|  | 			++dispatch_index_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(dispatch_index_ != old_index) { | ||||||
|  | 			update_observers(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(preferred_clocking() == ClockingHint::Preference::None) { | ||||||
|  | 			update_clocking_observer(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										162
									
								
								Storage/MassStorage/SCSI/SCSI.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								Storage/MassStorage/SCSI/SCSI.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | |||||||
|  | // | ||||||
|  | //  SCSI.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 12/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef SCSI_hpp | ||||||
|  | #define SCSI_hpp | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <limits> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "../../../ClockReceiver/ClockReceiver.hpp" | ||||||
|  | #include "../../../ClockReceiver/ClockingHintSource.hpp" | ||||||
|  | #include "../../../Activity/Source.hpp" | ||||||
|  |  | ||||||
|  | namespace SCSI { | ||||||
|  |  | ||||||
|  | typedef int BusState; | ||||||
|  |  | ||||||
|  | static const BusState DefaultBusState = 0; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	SCSI bus state is encoded entirely within an int. | ||||||
|  | 	Bits correlate mostly but not exactly to the real SCSI bus. | ||||||
|  |  | ||||||
|  | 	TODO: validate levels below. The bus uses open collector logic, | ||||||
|  | 	so active low needs to be respected. | ||||||
|  | */ | ||||||
|  | enum Line: BusState { | ||||||
|  | 	/// Provides the value currently on the data lines. | ||||||
|  | 	Data 			= 0xff, | ||||||
|  | 	/// Parity of the data lines. | ||||||
|  | 	Parity		 	= 1 << 8, | ||||||
|  | 	/// Set if the SEL line is currently selecting a target. | ||||||
|  | 	/// Reset if it is selecting an initiator. | ||||||
|  | 	SelectTarget	= 1 << 9, | ||||||
|  | 	/// Set to indicate an attention condition. Reset otherwise. | ||||||
|  | 	Attention		= 1 << 10, | ||||||
|  | 	/// Set if control is on the bus. Reset if data is on the bus. | ||||||
|  | 	Control			= 1 << 11, | ||||||
|  | 	/// Set if the bus is busy. Reset otherwise. | ||||||
|  | 	Busy			= 1 << 12, | ||||||
|  | 	/// Set if acknowledging a data transfer request. Reset otherwise. | ||||||
|  | 	Acknowledge		= 1 << 13, | ||||||
|  | 	/// Set if a bus reset is being requested. Reset otherwise. | ||||||
|  | 	Reset			= 1 << 14, | ||||||
|  | 	/// Set if data is currently an input to the initiator. Reset if it is an output. | ||||||
|  | 	Input			= 1 << 15, | ||||||
|  | 	/// Set during the message phase. Reset otherwise. | ||||||
|  | 	Message			= 1 << 16, | ||||||
|  | 	/// Set if requesting a data transfer. Reset otherwise. | ||||||
|  | 	Request			= 1 << 17, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #define us(x)	(x) / 1000000.0 | ||||||
|  | #define ns(x)	(x) / 1000000000.0 | ||||||
|  |  | ||||||
|  | /// The minimum amount of time that reset must be held for. | ||||||
|  | constexpr double ResetHoldTime		= us(25.0); | ||||||
|  |  | ||||||
|  | /// The minimum amount of time a SCSI device must wait after asserting ::Busy | ||||||
|  | /// until the data bus can be inspected to see whether arbitration has been won. | ||||||
|  | constexpr double ArbitrationDelay	= us(1.7); | ||||||
|  |  | ||||||
|  | /// The maximum amount of time a SCSI device can take from a detection that the | ||||||
|  | /// bus is free until it asserts ::Busy and its ID for the purposes of arbitration. | ||||||
|  | constexpr double BusSetDelay		= us(1.1); | ||||||
|  |  | ||||||
|  | /// The maximum amount of time a SCSI device is permitted to take to stop driving | ||||||
|  | /// all bus signals after: (i) the release of ::Busy ushering in a bus free phase; | ||||||
|  | /// or (ii) some other device has asserted ::Select during an arbitration phase. | ||||||
|  | constexpr double BusClearDelay		= ns(650.0); | ||||||
|  |  | ||||||
|  | /// The minimum amount of time to wait for the bus to settle after changing | ||||||
|  | /// "certain control signals". TODO: which? | ||||||
|  | constexpr double BusSettleDelay		= ns(450.0); | ||||||
|  |  | ||||||
|  | /// The minimum amount of time a SCSI must wait from detecting that the bus is free | ||||||
|  | /// and asserting ::Busy if starting an arbitration phase. | ||||||
|  | constexpr double BusFreeDelay		= ns(100.0); | ||||||
|  |  | ||||||
|  | /// The minimum amount of time required for deskew of "certain signals". TODO: which? | ||||||
|  | constexpr double DeskewDelay		= ns(45.0); | ||||||
|  |  | ||||||
|  | /// The maximum amount of time that propagation of a SCSI bus signal can take between | ||||||
|  | /// any two devices. | ||||||
|  | constexpr double CableSkew			= ns(10.0); | ||||||
|  |  | ||||||
|  | #undef ns | ||||||
|  | #undef us | ||||||
|  |  | ||||||
|  | class Bus: public ClockingHint::Source, public Activity::Source { | ||||||
|  | 	public: | ||||||
|  | 		Bus(HalfCycles clock_rate); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Adds a device to the bus, returning the index it should use | ||||||
|  | 			to refer to itself in subsequent calls to set_device_output. | ||||||
|  | 		*/ | ||||||
|  | 		size_t add_device(); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Sets the current output for @c device. | ||||||
|  | 		*/ | ||||||
|  | 		void set_device_output(size_t device, BusState output); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			@returns the current state of the bus. | ||||||
|  | 		*/ | ||||||
|  | 		BusState get_state(); | ||||||
|  |  | ||||||
|  | 		struct Observer { | ||||||
|  | 			/// Reports to an observer that the bus changed from a previous state to @c new_state, | ||||||
|  | 			/// along with the time since that change was observed. The time is in seconds, and is | ||||||
|  | 			/// intended for comparison with the various constants defined at namespace scope: | ||||||
|  | 			/// ArbitrationDelay et al. Observers will be notified each time one of the thresholds | ||||||
|  | 			/// defined by those constants is crossed. | ||||||
|  | 			virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0; | ||||||
|  | 		}; | ||||||
|  | 		/*! | ||||||
|  | 			Adds an observer. | ||||||
|  | 		*/ | ||||||
|  | 		void add_observer(Observer *); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors, | ||||||
|  | 			and `run_for` is the way that time propagates within this emulator. So please permit this | ||||||
|  | 			fiction. | ||||||
|  | 		*/ | ||||||
|  | 		void run_for(HalfCycles); | ||||||
|  |  | ||||||
|  | 		/*! | ||||||
|  | 			Forces a `scsi_bus_did_change` propagation now. | ||||||
|  | 		*/ | ||||||
|  | 		void update_observers(); | ||||||
|  |  | ||||||
|  | 		// As per ClockingHint::Source. | ||||||
|  | 		ClockingHint::Preference preferred_clocking() final; | ||||||
|  |  | ||||||
|  | 		// Fulfilling public Activity::Source. | ||||||
|  | 		void set_activity_observer(Activity::Observer *observer) final; | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		HalfCycles time_in_state_; | ||||||
|  | 		double cycles_to_time_ = 1.0; | ||||||
|  | 		size_t dispatch_index_ = 0; | ||||||
|  | 		std::array<int, 8> dispatch_times_; | ||||||
|  |  | ||||||
|  | 		std::vector<BusState> device_states_; | ||||||
|  | 		BusState state_ = DefaultBusState; | ||||||
|  | 		std::vector<Observer *> observers_; | ||||||
|  |  | ||||||
|  | 		Activity::Observer *activity_observer_ = nullptr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* SCSI_hpp */ | ||||||
							
								
								
									
										93
									
								
								Storage/MassStorage/SCSI/Target.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Storage/MassStorage/SCSI/Target.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | // | ||||||
|  | //  Target.cpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 17/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Target.hpp" | ||||||
|  |  | ||||||
|  | using namespace SCSI::Target; | ||||||
|  |  | ||||||
|  | CommandState::CommandState(const std::vector<uint8_t> &data, const std::vector<uint8_t> &received) : data_(data), received_(received) {} | ||||||
|  |  | ||||||
|  | uint32_t CommandState::address() const { | ||||||
|  | 	switch(data_.size()) { | ||||||
|  | 		default:	return 0; | ||||||
|  | 		case 6: | ||||||
|  | 			return | ||||||
|  | 				(uint32_t(data_[1]) << 16) | | ||||||
|  | 				(uint32_t(data_[2]) << 8) | | ||||||
|  | 				uint32_t(data_[3]); | ||||||
|  | 		case 10: | ||||||
|  | 		case 12: | ||||||
|  | 			return | ||||||
|  | 				(uint32_t(data_[1]) << 24) | | ||||||
|  | 				(uint32_t(data_[2]) << 16) | | ||||||
|  | 				(uint32_t(data_[3]) << 8) | | ||||||
|  | 				uint32_t(data_[4]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint16_t CommandState::number_of_blocks() const { | ||||||
|  | 	switch(data_.size()) { | ||||||
|  | 		default:	return 0; | ||||||
|  | 		case 6: | ||||||
|  | 			return uint16_t(data_[4]); | ||||||
|  | 		case 10: | ||||||
|  | 			return uint16_t((data_[7] << 8) | data_[8]); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CommandState::ReadWrite CommandState::read_write_specs() const { | ||||||
|  | 	ReadWrite specs; | ||||||
|  |  | ||||||
|  | 	specs.address = address(); | ||||||
|  | 	specs.number_of_blocks = number_of_blocks(); | ||||||
|  | 	if(!specs.number_of_blocks && (data_.size() == 6)) { | ||||||
|  | 		specs.number_of_blocks = 256; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return specs; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t CommandState::allocated_inquiry_bytes() const { | ||||||
|  | 	// 0 means 256 bytes allocated for inquiry. | ||||||
|  | 	return size_t(((data_[4] - 1) & 0xff) + 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CommandState::ModeSense CommandState::mode_sense_specs() const { | ||||||
|  | 	ModeSense specs; | ||||||
|  |  | ||||||
|  | 	specs.exclude_block_descriptors = (data_[1] & 0x08); | ||||||
|  | 	specs.page_control_values = ModeSense::PageControlValues(data_[2] >> 5); | ||||||
|  | 	specs.page_code = data_[2] & 0x3f; | ||||||
|  | 	specs.subpage_code = data_[3]; | ||||||
|  | 	specs.allocated_bytes = number_of_blocks(); | ||||||
|  |  | ||||||
|  | 	return specs; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CommandState::ReadBuffer CommandState::read_buffer_specs() const { | ||||||
|  | 	ReadBuffer specs; | ||||||
|  |  | ||||||
|  | 	specs.mode = ReadBuffer::Mode(data_[1]&7); | ||||||
|  | 	if(specs.mode > ReadBuffer::Mode::Reserved) specs.mode = ReadBuffer::Mode::Reserved; | ||||||
|  | 	specs.buffer_id = data_[2]; | ||||||
|  | 	specs.buffer_offset = uint32_t((data_[3] << 16) | (data_[4] << 8) | data_[5]); | ||||||
|  | 	specs.buffer_length = uint32_t((data_[6] << 16) | (data_[7] << 8) | data_[8]); | ||||||
|  |  | ||||||
|  | 	return specs; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CommandState::ModeSelect CommandState::mode_select_specs() const { | ||||||
|  | 	ModeSelect specs; | ||||||
|  |  | ||||||
|  | 	specs.parameter_list_length = number_of_blocks(); | ||||||
|  | 	specs.content_is_vendor_specific = !(data_[1] & 0x10); | ||||||
|  | 	specs.revert_to_default = (data_[1] & 0x02); | ||||||
|  | 	specs.save_pages = (data_[1] & 0x01); | ||||||
|  |  | ||||||
|  | 	return specs; | ||||||
|  | } | ||||||
							
								
								
									
										402
									
								
								Storage/MassStorage/SCSI/Target.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								Storage/MassStorage/SCSI/Target.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,402 @@ | |||||||
|  | // | ||||||
|  | //  Target.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 17/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef SCSI_Target_hpp | ||||||
|  | #define SCSI_Target_hpp | ||||||
|  |  | ||||||
|  | #include "SCSI.hpp" | ||||||
|  | #include "../../../Outputs/Log.hpp" | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cstring> | ||||||
|  | #include <functional> | ||||||
|  |  | ||||||
|  | namespace SCSI { | ||||||
|  | namespace Target { | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Encapsulates the arguments supplied for a target SCSI command during | ||||||
|  | 	the command phase plus any other data read since then. | ||||||
|  | */ | ||||||
|  | class CommandState { | ||||||
|  | 	public: | ||||||
|  | 		CommandState(const std::vector<uint8_t> &command, const std::vector<uint8_t> &received); | ||||||
|  |  | ||||||
|  | 		// For read and write commands. | ||||||
|  | 		struct ReadWrite { | ||||||
|  | 			uint32_t address, number_of_blocks; | ||||||
|  | 		}; | ||||||
|  | 		ReadWrite read_write_specs() const; | ||||||
|  |  | ||||||
|  | 		// For inquiry commands. | ||||||
|  | 		size_t allocated_inquiry_bytes() const; | ||||||
|  |  | ||||||
|  | 		// For mode sense commands. | ||||||
|  | 		struct ModeSense { | ||||||
|  | 			bool exclude_block_descriptors = false; | ||||||
|  | 			enum class PageControlValues { | ||||||
|  | 				Current = 0, | ||||||
|  | 				Changeable = 1, | ||||||
|  | 				Default = 2, | ||||||
|  | 				Saved = 3 | ||||||
|  | 			} page_control_values = PageControlValues::Current; | ||||||
|  | 			uint8_t page_code; | ||||||
|  | 			uint8_t subpage_code; | ||||||
|  | 			uint16_t allocated_bytes; | ||||||
|  | 		}; | ||||||
|  | 		ModeSense mode_sense_specs() const; | ||||||
|  |  | ||||||
|  | 		struct ModeSelect { | ||||||
|  | 			bool content_is_vendor_specific = true; | ||||||
|  | 			bool revert_to_default = false; | ||||||
|  | 			bool save_pages = false; | ||||||
|  | 			uint16_t parameter_list_length = 0; | ||||||
|  | 		}; | ||||||
|  | 		ModeSelect mode_select_specs() const; | ||||||
|  |  | ||||||
|  | 		struct ReadBuffer { | ||||||
|  | 			enum class Mode { | ||||||
|  | 				CombinedHeaderAndData = 0, | ||||||
|  | 				VendorSpecific = 1, | ||||||
|  | 				Data = 2, | ||||||
|  | 				Descriptor = 3, | ||||||
|  | 				Reserved = 4 | ||||||
|  | 			} mode = Mode::CombinedHeaderAndData; | ||||||
|  | 			uint8_t buffer_id = 0; | ||||||
|  | 			uint32_t buffer_offset = 0, buffer_length = 0; | ||||||
|  | 		}; | ||||||
|  | 		ReadBuffer read_buffer_specs() const; | ||||||
|  |  | ||||||
|  | 		const std::vector<uint8_t> &received_data() const { | ||||||
|  | 			return received_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		uint32_t address() const; | ||||||
|  | 		uint16_t number_of_blocks() const; | ||||||
|  | 		const std::vector<uint8_t> &data_; | ||||||
|  | 		const std::vector<uint8_t> &received_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A Responder is supplied both (i) to the initial call-in to an Executor; and | ||||||
|  | 	(ii) to all continuations provided by that Executor. It allows the next | ||||||
|  | 	set of bus interactions to be dictated. | ||||||
|  | */ | ||||||
|  | struct Responder { | ||||||
|  | 	using continuation = std::function<void(const CommandState &, Responder &)>; | ||||||
|  |  | ||||||
|  | 	enum class Status { | ||||||
|  | 		Good						= 0x00, | ||||||
|  | 		CheckCondition				= 0x02, | ||||||
|  | 		ConditionMet				= 0x04, | ||||||
|  | 		Busy						= 0x08, | ||||||
|  | 		Intermediate				= 0x10, | ||||||
|  | 		IntermediateConditionMet	= 0x14, | ||||||
|  | 		ReservationConflict			= 0x18, | ||||||
|  | 		CommandTerminated			= 0x22, | ||||||
|  | 		TaskSetFull					= 0x28, | ||||||
|  | 		ACAActive					= 0x30, | ||||||
|  | 		TaskAborted					= 0x40 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	enum class Message { | ||||||
|  | 		CommandComplete				= 0x00 | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	/*! | ||||||
|  | 		Causes the SCSI device to send @c data to the initiator and | ||||||
|  | 		call @c next when done. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void send_data(std::vector<uint8_t> &&data, continuation next) = 0; | ||||||
|  | 	/*! | ||||||
|  | 		Causes the SCSI device to receive @c length bytes from the initiator and | ||||||
|  | 		call @c next when done. The bytes will be accessible via the CommandInput object. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void receive_data(size_t length, continuation next) = 0; | ||||||
|  | 	/*! | ||||||
|  | 		Communicates the supplied status to the initiator. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void send_status(Status, continuation next) = 0; | ||||||
|  | 	/*! | ||||||
|  | 		Communicates the supplied message to the initiator. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void send_message(Message, continuation next) = 0; | ||||||
|  | 	/*! | ||||||
|  | 		Ends the SCSI command. | ||||||
|  | 	*/ | ||||||
|  | 	virtual void end_command() = 0; | ||||||
|  | 	/*! | ||||||
|  | 		Terminates a SCSI command, sending the proper sequence of status and message phases. | ||||||
|  | 	*/ | ||||||
|  | 	void terminate_command(Status status) { | ||||||
|  | 		send_status(status, [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 			responder.send_message(Target::Responder::Message::CommandComplete, [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 				responder.end_command(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	Executors contain device-specific logic; when the target has completed | ||||||
|  | 	the command phase it will call the appropriate method on its executor, | ||||||
|  | 	supplying it with the command's arguments. | ||||||
|  |  | ||||||
|  | 	If you implement a method, you should push a result and return @c true. | ||||||
|  | 	Return @c false if you do not implement a method (or, just inherit from | ||||||
|  | 	the basic executor below, and don't implement anything you don't support). | ||||||
|  | */ | ||||||
|  | struct Executor { | ||||||
|  | 	/* Group 0 commands. */ | ||||||
|  | 	bool test_unit_ready(const CommandState &, Responder &responder)		{ | ||||||
|  | 		/* "Returns zero status if addressed unit is powered on and ready. */ | ||||||
|  | 		responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	bool rezero_unit(const CommandState &, Responder &)			{	return false;	} | ||||||
|  | 	bool request_sense(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool format_unit(const CommandState &, Responder &)			{	return false;	} | ||||||
|  | 	bool seek(const CommandState &, Responder &)				{	return false;	} | ||||||
|  | 	bool reserve_unit(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool release_unit(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool read_diagnostic(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool write_diagnostic(const CommandState &, Responder &)	{	return false;	} | ||||||
|  |  | ||||||
|  | 	/// Mode sense: the default implementation will call into the appropriate | ||||||
|  | 	/// structured getter. | ||||||
|  | 	bool mode_sense(const CommandState &state, Responder &responder) { | ||||||
|  | 		const auto specs = state.mode_sense_specs(); | ||||||
|  | 		std::vector<uint8_t> response = { | ||||||
|  | 			specs.page_code, | ||||||
|  | 			uint8_t(specs.allocated_bytes) | ||||||
|  | 		}; | ||||||
|  | 		switch(specs.page_code) { | ||||||
|  | 			default: | ||||||
|  | 				printf("Unknown mode sense page code %02x\n", specs.page_code); | ||||||
|  | 				response.resize(specs.allocated_bytes); | ||||||
|  | 			break; | ||||||
|  |  | ||||||
|  | 			case 0x30: | ||||||
|  | 				response.resize(34); | ||||||
|  | 				strcpy(reinterpret_cast<char *>(&response[14]), "APPLE COMPUTER, INC");	// This seems to be required to satisfy the Apple HD SC Utility. | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(specs.allocated_bytes < response.size()) { | ||||||
|  | 			response.resize(specs.allocated_bytes); | ||||||
|  | 		} | ||||||
|  | 		responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 			responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool mode_select(const CommandState &state, Responder &responder) { | ||||||
|  | 		const auto specs = state.mode_select_specs(); | ||||||
|  |  | ||||||
|  | 		responder.receive_data(specs.parameter_list_length, [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 			// TODO: parse data according to current sense mode. | ||||||
|  | 			responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/// Inquiry: the default implementation will call the structured version and | ||||||
|  | 	/// package appropriately. | ||||||
|  | 	struct Inquiry { | ||||||
|  | 		enum class DeviceType { | ||||||
|  | 			DirectAccess = 0, | ||||||
|  | 			SequentialAccess = 1, | ||||||
|  | 			Printer = 2, | ||||||
|  | 			Processor = 3, | ||||||
|  | 			WriteOnceMultipleRead = 4, | ||||||
|  | 			ReadOnlyDirectAccess = 5, | ||||||
|  | 			Scanner = 6, | ||||||
|  | 			OpticalMemory = 7, | ||||||
|  | 			MediumChanger = 8, | ||||||
|  | 			Communications = 9, | ||||||
|  | 		} device_type = DeviceType::DirectAccess; | ||||||
|  | 		bool is_removeable = false; | ||||||
|  | 		uint8_t iso_standard = 0, ecma_standard = 0, ansi_standard = 0; | ||||||
|  | 		bool supports_asynchronous_events = false; | ||||||
|  | 		bool supports_terminate_io_process = false; | ||||||
|  | 		bool supports_relative_addressing = false; | ||||||
|  | 		bool supports_synchronous_transfer = true; | ||||||
|  | 		bool supports_linked_commands = false; | ||||||
|  | 		bool supports_command_queing = false; | ||||||
|  | 		bool supports_soft_reset = false; | ||||||
|  | 		char vendor_identifier[9] = ""; | ||||||
|  | 		char product_identifier[17] = ""; | ||||||
|  | 		char product_revision_level[5] = ""; | ||||||
|  |  | ||||||
|  | 		Inquiry(const char *vendor, const char *product, const char *revision) { | ||||||
|  | 			assert(strlen(vendor) <= 8); | ||||||
|  | 			assert(strlen(product) <= 16); | ||||||
|  | 			assert(strlen(revision) <= 4); | ||||||
|  | 			strcpy(vendor_identifier, vendor); | ||||||
|  | 			strcpy(product_identifier, product); | ||||||
|  | 			strcpy(product_revision_level, revision); | ||||||
|  | 		} | ||||||
|  | 		Inquiry() = default; | ||||||
|  | 	}; | ||||||
|  | 	Inquiry inquiry_values() { | ||||||
|  | 		return Inquiry(); | ||||||
|  | 	} | ||||||
|  | 	bool inquiry(const CommandState &state, Responder &responder) { | ||||||
|  | 		const Inquiry inq = inquiry_values(); | ||||||
|  |  | ||||||
|  | 		// Set up the easy fields. | ||||||
|  | 		std::vector<uint8_t> response = { | ||||||
|  | 			uint8_t(inq.device_type), | ||||||
|  | 			uint8_t(inq.is_removeable ? 0x80 : 0x00), | ||||||
|  | 			uint8_t((inq.iso_standard << 5) | (inq.ecma_standard << 3) | (inq.ansi_standard)), | ||||||
|  | 			uint8_t((inq.supports_asynchronous_events ? 0x80 : 0x00) | (inq.supports_terminate_io_process ? 0x40 : 0x00) | 0x02), | ||||||
|  | 			32,		/* Additional length: 36 - 4. */ | ||||||
|  | 			0x00,	/* Reserved. */ | ||||||
|  | 			0x00,	/* Reserved. */ | ||||||
|  | 			uint8_t( | ||||||
|  | 				(inq.supports_relative_addressing ? 0x80 : 0x00) | | ||||||
|  | 				/* b6: supports 32-bit data; b5: supports 16-bit data. */ | ||||||
|  | 				(inq.supports_synchronous_transfer ? 0x10 : 0x00) | | ||||||
|  | 				(inq.supports_linked_commands ? 0x08 : 0x00) | | ||||||
|  | 				/* b3: reserved. */ | ||||||
|  | 				(inq.supports_command_queing ? 0x02 : 0x00) | | ||||||
|  | 				(inq.supports_soft_reset ? 0x01 : 0x00) | ||||||
|  | 			), | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Space for the vendor ID. */ | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  | 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Space for the product ID. */ | ||||||
|  | 			0x00, 0x00, 0x00, 0x00							/* Space for the revision level. */ | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		auto copy_string = [] (uint8_t *destination, const char *source, size_t length) -> void { | ||||||
|  | 			// Copy as much of the string as will fit, and pad with spaces. | ||||||
|  | 			uint8_t *end = reinterpret_cast<uint8_t *>(stpncpy(reinterpret_cast<char *>(destination), source, length)); | ||||||
|  | 			while(end < destination + length) { | ||||||
|  | 				*end = ' '; | ||||||
|  | 				++end; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 		copy_string(&response[8], inq.vendor_identifier, 8); | ||||||
|  | 		copy_string(&response[16], inq.product_identifier, 16); | ||||||
|  | 		copy_string(&response[32], inq.product_revision_level, 4); | ||||||
|  |  | ||||||
|  | 		// Truncate if requested. | ||||||
|  | 		const auto allocated_bytes = state.allocated_inquiry_bytes(); | ||||||
|  | 		if(allocated_bytes < response.size()) { | ||||||
|  | 			response.resize(allocated_bytes); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) { | ||||||
|  | 			responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* Group 0/1 commands. */ | ||||||
|  | 	bool read(const CommandState &, Responder &)				{	return false;	} | ||||||
|  | 	bool write(const CommandState &, Responder &)				{	return false;	} | ||||||
|  |  | ||||||
|  | 	/* Group 1 commands. */ | ||||||
|  | 	bool read_capacity(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool write_and_verify(const CommandState &, Responder &)	{	return false;	} | ||||||
|  | 	bool verify(const CommandState &, Responder &)				{	return false;	} | ||||||
|  | 	bool search_data_equal(const CommandState &, Responder &)	{	return false;	} | ||||||
|  | 	bool search_data_high(const CommandState &, Responder &)	{	return false;	} | ||||||
|  | 	bool search_data_low(const CommandState &, Responder &)		{	return false;	} | ||||||
|  | 	bool read_buffer(const CommandState &state, Responder &responder) { | ||||||
|  | 		// Since I have no idea what earthly function READ BUFFER is meant to allow, | ||||||
|  | 		// the default implementation just returns an empty buffer of the requested size. | ||||||
|  | 		const auto specs = state.read_buffer_specs(); | ||||||
|  | 		responder.send_data(std::vector<uint8_t>(specs.buffer_length), [] (const Target::CommandState &, Target::Responder &responder) { | ||||||
|  | 			responder.terminate_command(Target::Responder::Status::Good); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/*  Group 5 commands. */ | ||||||
|  | 	bool set_block_limits(const CommandState &, Responder &)	{	return false;	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  | 	A template for any SCSI target; provides the necessary bus glue to | ||||||
|  | 	receive and respond to commands. Specific targets should be implemented | ||||||
|  | 	as Executors. | ||||||
|  | */ | ||||||
|  | template <typename Executor> class Target: public Bus::Observer, public Responder { | ||||||
|  | 	public: | ||||||
|  | 		/*! | ||||||
|  | 			Instantiates a target attached to @c bus, | ||||||
|  | 			with SCSI ID @c scsi_id — a number in the range 0 to 7. | ||||||
|  |  | ||||||
|  | 			Received commands will be handed to the Executor to perform. | ||||||
|  | 		*/ | ||||||
|  | 		Target(Bus &bus, int scsi_id); | ||||||
|  |  | ||||||
|  | 		inline Executor *operator->() { | ||||||
|  | 			return &executor_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	private: | ||||||
|  | 		Executor executor_; | ||||||
|  |  | ||||||
|  | 		// Bus::Observer. | ||||||
|  | 		void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) final; | ||||||
|  |  | ||||||
|  | 		// Responder | ||||||
|  | 		void send_data(std::vector<uint8_t> &&data, continuation next) final; | ||||||
|  | 		void receive_data(size_t length, continuation next) final; | ||||||
|  | 		void send_status(Status, continuation next) final; | ||||||
|  | 		void send_message(Message, continuation next) final; | ||||||
|  | 		void end_command() final; | ||||||
|  |  | ||||||
|  | 		// Instance storage. | ||||||
|  | 		Bus &bus_; | ||||||
|  | 		const BusState scsi_id_mask_; | ||||||
|  | 		const size_t scsi_bus_device_id_; | ||||||
|  |  | ||||||
|  | 		enum class Phase { | ||||||
|  | 			AwaitingSelection, | ||||||
|  | 			Command, | ||||||
|  | 			ReceivingData, | ||||||
|  | 			SendingData, | ||||||
|  | 			SendingStatus, | ||||||
|  | 			SendingMessage | ||||||
|  | 		} phase_ = Phase::AwaitingSelection; | ||||||
|  | 		BusState bus_state_ = DefaultBusState; | ||||||
|  |  | ||||||
|  | 		void set_device_output(BusState state) { | ||||||
|  | 			expected_control_state_ = state & (Line::Control | Line::Input | Line::Message); | ||||||
|  | 			bus_.set_device_output(scsi_bus_device_id_, state); | ||||||
|  | 		} | ||||||
|  | 		BusState expected_control_state_ = DefaultBusState; | ||||||
|  |  | ||||||
|  | 		void begin_command(uint8_t first_byte); | ||||||
|  | 		std::vector<uint8_t> command_; | ||||||
|  | 		Status status_; | ||||||
|  | 		Message message_; | ||||||
|  | 		size_t command_pointer_ = 0; | ||||||
|  | 		bool dispatch_command(); | ||||||
|  |  | ||||||
|  | 		std::vector<uint8_t> data_; | ||||||
|  | 		size_t data_pointer_ = 0; | ||||||
|  |  | ||||||
|  | 		continuation next_function_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #include "TargetImplementation.hpp" | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif /* SCSI_Target_hpp */ | ||||||
							
								
								
									
										280
									
								
								Storage/MassStorage/SCSI/TargetImplementation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								Storage/MassStorage/SCSI/TargetImplementation.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | |||||||
|  | // | ||||||
|  | //  TargetImplementation.hpp | ||||||
|  | //  Clock Signal | ||||||
|  | // | ||||||
|  | //  Created by Thomas Harte on 19/08/2019. | ||||||
|  | //  Copyright © 2019 Thomas Harte. All rights reserved. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | template <typename Executor> Target<Executor>::Target(Bus &bus, int scsi_id) : | ||||||
|  | 	bus_(bus), | ||||||
|  | 	scsi_id_mask_(BusState(1 << scsi_id)), | ||||||
|  | 	scsi_bus_device_id_(bus.add_device()) { | ||||||
|  | 	bus.add_observer(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) { | ||||||
|  | 	/* | ||||||
|  | 		"The target determines that it is selected when the SEL# signal | ||||||
|  | 		and its SCSI ID bit are active and the BSY# and I#/O signals | ||||||
|  | 		are false. It then asserts the signal within a selection abort | ||||||
|  | 		time." | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
|  | 	// Wait for deskew, at the very least. | ||||||
|  | 	if(time_since_change < SCSI::DeskewDelay) return; | ||||||
|  |  | ||||||
|  | 	// A reset always takes precedence over anything else ongoing. | ||||||
|  | 	if(new_state & Line::Reset) { | ||||||
|  | 		phase_ = Phase::AwaitingSelection; | ||||||
|  | 		bus_state_ = DefaultBusState; | ||||||
|  | 		set_device_output(bus_state_); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch(phase_) { | ||||||
|  | 		/* | ||||||
|  | 			While awaiting selection the SCSI target is passively watching the bus waiting for its ID | ||||||
|  | 			to be set during a target selection. It will segue automatically from there to the command | ||||||
|  | 			phase regardless of its executor. | ||||||
|  | 		*/ | ||||||
|  | 		case Phase::AwaitingSelection: | ||||||
|  | 			if( | ||||||
|  | 				(new_state & scsi_id_mask_) && | ||||||
|  | 				((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget) | ||||||
|  | 			) { | ||||||
|  | 				phase_ = Phase::Command; | ||||||
|  | 				command_.resize(0); | ||||||
|  | 				command_pointer_ = 0; | ||||||
|  | 				bus_state_ |= Line::Busy;	// Initiate the command phase: request a command byte. | ||||||
|  | 				set_device_output(bus_state_); | ||||||
|  | 			} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 			In the command phase, the target will stream an appropriate number of bytes for the command | ||||||
|  | 			it is being offered, before giving the executor a chance to handle the command. If the target | ||||||
|  | 			supports this command, it becomes responsible for the appropriate next phase transition. If it | ||||||
|  | 			reports that it doesn't support that command, a suitable response is automatically dispatched. | ||||||
|  | 		*/ | ||||||
|  | 		case Phase::Command: | ||||||
|  | 			// Wait for select to be disabled before beginning the control phase proper. | ||||||
|  | 			if((new_state & Line::SelectTarget)) return; | ||||||
|  |  | ||||||
|  | 			bus_state_ |= Line::Control; | ||||||
|  |  | ||||||
|  | 			switch(new_state & (Line::Request | Line::Acknowledge)) { | ||||||
|  | 				// If request and acknowledge are both enabled, grab a byte and cancel the request. | ||||||
|  | 				case Line::Request | Line::Acknowledge: | ||||||
|  | 					bus_state_ &= ~Line::Request; | ||||||
|  |  | ||||||
|  | 					if(command_.empty()) { | ||||||
|  | 						begin_command(uint8_t(new_state)); | ||||||
|  |  | ||||||
|  | 						// TODO: if(command_.empty()) signal_error_somehow(); | ||||||
|  | 					} else { | ||||||
|  | 						command_[command_pointer_] = uint8_t(new_state); | ||||||
|  | 						++command_pointer_; | ||||||
|  | 						if(command_pointer_ == command_.size()) { | ||||||
|  | 							if(!dispatch_command()) { | ||||||
|  | 								// This is just a guess for now; I don't know how SCSI | ||||||
|  | 								// devices are supposed to respond if they don't support | ||||||
|  | 								// a command. | ||||||
|  | 								terminate_command(Responder::Status::TaskAborted); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				// The reset of request has caused the initiator to reset acknowledge, so it is now | ||||||
|  | 				// safe to request the next byte. | ||||||
|  | 				case 0: | ||||||
|  | 					bus_state_ |= Line::Request; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				default: break; | ||||||
|  | 			} | ||||||
|  | 			set_device_output(bus_state_); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case Phase::ReceivingData: | ||||||
|  | 			switch(new_state & (Line::Request | Line::Acknowledge)) { | ||||||
|  | 				case Line::Request | Line::Acknowledge: | ||||||
|  | 					bus_state_ &= ~Line::Request; | ||||||
|  |  | ||||||
|  | 					data_[data_pointer_] = uint8_t(new_state); | ||||||
|  | 					++data_pointer_; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 0: | ||||||
|  | 					if(data_pointer_ == data_.size()) { | ||||||
|  | 						next_function_(CommandState(command_, data_), *this); | ||||||
|  | 					} else { | ||||||
|  | 						bus_state_ |= Line::Request; | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			set_device_output(bus_state_); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		case Phase::SendingData: | ||||||
|  | 		case Phase::SendingStatus: | ||||||
|  | 		case Phase::SendingMessage: | ||||||
|  | 			switch(new_state & (Line::Request | Line::Acknowledge)) { | ||||||
|  | 				case Line::Request | Line::Acknowledge: | ||||||
|  | 					bus_state_ &= ~(Line::Request | 0xff); | ||||||
|  |  | ||||||
|  | 					++data_pointer_; | ||||||
|  | 				break; | ||||||
|  |  | ||||||
|  | 				case 0: | ||||||
|  | 					if( | ||||||
|  | 						(phase_ == Phase::SendingMessage && data_pointer_ == 1) || | ||||||
|  | 						(phase_ == Phase::SendingStatus && data_pointer_ == 1) || | ||||||
|  | 						(phase_ == Phase::SendingData && data_pointer_ == data_.size()) | ||||||
|  | 					) { | ||||||
|  | 						next_function_(CommandState(command_, data_), *this); | ||||||
|  | 					} else { | ||||||
|  | 						bus_state_ |= Line::Request; | ||||||
|  | 						bus_state_ &= ~0xff; | ||||||
|  |  | ||||||
|  | 						switch(phase_) { | ||||||
|  | 							case Phase::SendingData: 	bus_state_ |= data_[data_pointer_];	break; | ||||||
|  | 							case Phase::SendingStatus:	bus_state_ |= BusState(status_);	break; | ||||||
|  | 							default: | ||||||
|  | 							case Phase::SendingMessage:	bus_state_ |= BusState(message_);	break; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 			set_device_output(bus_state_); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::begin_command(uint8_t first_byte) { | ||||||
|  | 	// The logic below is valid for SCSI-1. TODO: other SCSIs. | ||||||
|  | 	switch(first_byte >> 5) { | ||||||
|  | 		default: break; | ||||||
|  | 		case 0:	command_.resize(6);		break;	// Group 0 commands: 6 bytes long. | ||||||
|  | 		case 1:	command_.resize(10);	break;	// Group 1 commands: 10 bytes long. | ||||||
|  | 		case 5:	command_.resize(12);	break;	// Group 5 commands: 12 bytes long. | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Store the first byte if it was recognised. | ||||||
|  | 	if(!command_.empty()) { | ||||||
|  | 		command_[0] = first_byte; | ||||||
|  | 		command_pointer_ = 1; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> bool Target<Executor>::dispatch_command() { | ||||||
|  |  | ||||||
|  | 	CommandState arguments(command_, data_); | ||||||
|  |  | ||||||
|  | #define G0(x)	x | ||||||
|  | #define G1(x)	(0x20|x) | ||||||
|  | #define G5(x)	(0xa0|x) | ||||||
|  |  | ||||||
|  | 	LOG("---Command " << PADHEX(2) << int(command_[0]) << "---"); | ||||||
|  |  | ||||||
|  | 	switch(command_[0]) { | ||||||
|  | 		default:		return false; | ||||||
|  |  | ||||||
|  | 		case G0(0x00):	return executor_.test_unit_ready(arguments, *this); | ||||||
|  | 		case G0(0x01):	return executor_.rezero_unit(arguments, *this); | ||||||
|  | 		case G0(0x03):	return executor_.request_sense(arguments, *this); | ||||||
|  | 		case G0(0x04):	return executor_.format_unit(arguments, *this); | ||||||
|  | 		case G0(0x08):	return executor_.read(arguments, *this); | ||||||
|  | 		case G0(0x0a):	return executor_.write(arguments, *this); | ||||||
|  | 		case G0(0x0b):	return executor_.seek(arguments, *this); | ||||||
|  | 		case G0(0x12):	return executor_.inquiry(arguments, *this); | ||||||
|  | 		case G0(0x15):	return executor_.mode_select(arguments, *this); | ||||||
|  | 		case G0(0x16):	return executor_.reserve_unit(arguments, *this); | ||||||
|  | 		case G0(0x17):	return executor_.release_unit(arguments, *this); | ||||||
|  | 		case G0(0x1a):	return executor_.mode_sense(arguments, *this); | ||||||
|  | 		case G0(0x1c):	return executor_.read_diagnostic(arguments, *this); | ||||||
|  | 		case G0(0x1d):	return executor_.write_diagnostic(arguments, *this); | ||||||
|  |  | ||||||
|  | 		case G1(0x05):	return executor_.read_capacity(arguments, *this); | ||||||
|  | 		case G1(0x08):	return executor_.read(arguments, *this); | ||||||
|  | 		case G1(0x0a):	return executor_.write(arguments, *this); | ||||||
|  | 		case G1(0x0e):	return executor_.write_and_verify(arguments, *this); | ||||||
|  | 		case G1(0x0f):	return executor_.verify(arguments, *this); | ||||||
|  | 		case G1(0x11):	return executor_.search_data_equal(arguments, *this); | ||||||
|  | 		case G1(0x10):	return executor_.search_data_high(arguments, *this); | ||||||
|  | 		case G1(0x12):	return executor_.search_data_low(arguments, *this); | ||||||
|  | 		case G1(0x1c):	return executor_.read_buffer(arguments, *this); | ||||||
|  | 		case G1(0x15):	return executor_.mode_select(arguments, *this); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		case G5(0x09):	return executor_.set_block_limits(arguments, *this); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #undef G0 | ||||||
|  | #undef G1 | ||||||
|  | #undef G5 | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::send_data(std::vector<uint8_t> &&data, continuation next) { | ||||||
|  | 	// Data out phase: control and message all reset, input set. | ||||||
|  | 	bus_state_ &= ~(Line::Control | Line::Input | Line::Message); | ||||||
|  | 	bus_state_ |= Line::Input; | ||||||
|  |  | ||||||
|  | 	phase_ = Phase::SendingData; | ||||||
|  | 	next_function_ = next; | ||||||
|  | 	data_ = std::move(data); | ||||||
|  |  | ||||||
|  | 	data_pointer_ = 0; | ||||||
|  | 	set_device_output(bus_state_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::receive_data(size_t length, continuation next) { | ||||||
|  | 	// Data out phase: control, input and message all reset. | ||||||
|  | 	bus_state_ &= ~(Line::Control | Line::Input | Line::Message); | ||||||
|  |  | ||||||
|  | 	phase_ = Phase::ReceivingData; | ||||||
|  | 	next_function_ = next; | ||||||
|  | 	data_.resize(length); | ||||||
|  |  | ||||||
|  | 	data_pointer_ = 0; | ||||||
|  | 	set_device_output(bus_state_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::send_status(Status status, continuation next) { | ||||||
|  | 	// Status phase: message reset, control and input set. | ||||||
|  | 	bus_state_ &= ~(Line::Control | Line::Input | Line::Message); | ||||||
|  | 	bus_state_ |= Line::Input | Line::Control; | ||||||
|  |  | ||||||
|  | 	status_ = status; | ||||||
|  | 	phase_ = Phase::SendingStatus; | ||||||
|  | 	next_function_ = next; | ||||||
|  |  | ||||||
|  | 	data_pointer_ = 0; | ||||||
|  | 	set_device_output(bus_state_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::send_message(Message message, continuation next) { | ||||||
|  | 	// Message in phase: message, control and input set. | ||||||
|  | 	bus_state_ |= Line::Message | Line::Control | Line::Input; | ||||||
|  |  | ||||||
|  | 	message_ = message; | ||||||
|  | 	phase_ = Phase::SendingMessage; | ||||||
|  | 	next_function_ = next; | ||||||
|  |  | ||||||
|  | 	data_pointer_ = 0; | ||||||
|  | 	set_device_output(bus_state_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename Executor> void Target<Executor>::end_command() { | ||||||
|  | 	// TODO: was this a linked command? | ||||||
|  |  | ||||||
|  | 	// Release all bus lines and return to awaiting selection. | ||||||
|  | 	phase_ = Phase::AwaitingSelection; | ||||||
|  | 	bus_state_ = DefaultBusState; | ||||||
|  | 	set_device_output(bus_state_); | ||||||
|  |  | ||||||
|  | 	LOG("---Done---"); | ||||||
|  | } | ||||||
| @@ -8,6 +8,8 @@ | |||||||
|  |  | ||||||
| #include "CSW.hpp" | #include "CSW.hpp" | ||||||
|  |  | ||||||
|  | #include "../../FileHolder.hpp" | ||||||
|  |  | ||||||
| #include <cassert> | #include <cassert> | ||||||
|  |  | ||||||
| using namespace Storage::Tape; | using namespace Storage::Tape; | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ | |||||||
| #define CSW_hpp | #define CSW_hpp | ||||||
|  |  | ||||||
| #include "../Tape.hpp" | #include "../Tape.hpp" | ||||||
| #include "../../FileHolder.hpp" |  | ||||||
|  |  | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|   | |||||||
| @@ -128,7 +128,7 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source { | |||||||
|  |  | ||||||
| 	They can also provide a delegate to be notified upon any change in the input level. | 	They can also provide a delegate to be notified upon any change in the input level. | ||||||
| */ | */ | ||||||
| class BinaryTapePlayer: public TapePlayer { | class BinaryTapePlayer : public TapePlayer { | ||||||
| 	public: | 	public: | ||||||
| 		BinaryTapePlayer(int input_clock_rate); | 		BinaryTapePlayer(int input_clock_rate); | ||||||
| 		void set_motor_control(bool enabled); | 		void set_motor_control(bool enabled); | ||||||
| @@ -145,7 +145,7 @@ class BinaryTapePlayer: public TapePlayer { | |||||||
| 		}; | 		}; | ||||||
| 		void set_delegate(Delegate *delegate); | 		void set_delegate(Delegate *delegate); | ||||||
|  |  | ||||||
| 		ClockingHint::Preference preferred_clocking() override; | 		ClockingHint::Preference preferred_clocking() final; | ||||||
|  |  | ||||||
| 	protected: | 	protected: | ||||||
| 		Delegate *delegate_ = nullptr; | 		Delegate *delegate_ = nullptr; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user