1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

138 Commits

Author SHA1 Message Date
Thomas Harte
b609ce6fcb Merge pull request #658 from TomHarte/Afterburner
Minor TMS timing correction.
2019-09-28 23:50:28 -04:00
Thomas Harte
929475d31e Minor correction: round down, not up. 2019-09-28 23:49:32 -04:00
Thomas Harte
f14d98452e Merge pull request #657 from TomHarte/DiskIIAgain
Corrects time propagation for Apple II cards
2019-09-28 23:40:07 -04:00
Thomas Harte
9d17d48bca Ensures cycles_per_revolution_ is populated for fixed-rate drives. 2019-09-28 23:23:15 -04:00
Thomas Harte
4ac3839185 Seeks to ensure that card transitions between real-time and just-in-time don't break timing. 2019-09-28 18:34:04 -04:00
Thomas Harte
c089d1cd09 Improves text; nobody normal knows that this is "a view". 2019-09-24 22:52:08 -04:00
Thomas Harte
cb85ec25cc Merge pull request #656 from TomHarte/MacMenu
Adds Macintosh choice to File -> New...
2019-09-24 22:50:43 -04:00
Thomas Harte
fbf95ec2b8 Removes the now empty local namespace. 2019-09-24 22:48:47 -04:00
Thomas Harte
6adca98f34 Adds Macintosh choice to File -> New... 2019-09-24 22:48:34 -04:00
Thomas Harte
48f4d8b875 Merge pull request #655 from TomHarte/EventProtocol
Rationalises protocol for application-level event theft.
2019-09-24 22:32:22 -04:00
Thomas Harte
7758f9d0a9 Improves nomenclature. 2019-09-24 22:31:36 -04:00
Thomas Harte
7112f0336c Rationalises protocol for application-level event theft. 2019-09-24 22:31:20 -04:00
Thomas Harte
298694a881 Merge pull request #654 from TomHarte/GoodbyeFastTapeProtocol
Eliminates fast loading Objective-C/Swift protocol.
2019-09-24 20:22:08 -04:00
Thomas Harte
7ff4594f09 Eliminates fast loading Objective-C/Swift protocol.
A very long time ago, when each machine had its own Objective-C class, this protocol was used to indicate which machines support that feature. It no longer communicates anything.
2019-09-24 20:13:09 -04:00
Thomas Harte
e8bd538182 Merge pull request #653 from TomHarte/CommandCapture
Allows machines to declare modifiers as 'essential'.
2019-09-22 14:04:06 -04:00
Thomas Harte
8489e8650f Attempts another draft of not inundating the user with open file dialogues. 2019-09-22 13:59:31 -04:00
Thomas Harte
114f81941e Completes the wiring necessary for capture of the command key.
At least when coupled with mouse capture.
2019-09-22 13:53:38 -04:00
Thomas Harte
077c7d767f Shifts essential modifiers up to the Keyboard class.
I had forgotten that mappers are not exposed.
2019-09-22 13:48:50 -04:00
Thomas Harte
8f88addf9f Establishes an interface for requesting shortcut theft. Not yet implemented. 2019-09-22 13:15:35 -04:00
Thomas Harte
f28c124039 Adds a route to divert key events before they reach Cocoa.
If you were to use this, it would have the effect of disabling in-app keyboard short-cuts in favour of routing keys to the emulated machine.
2019-09-22 13:15:01 -04:00
Thomas Harte
a416bc0058 Adds an interface allowing keyboard mappers to declare modifiers that are 'essential'.
i.e. ones that, if not delivered reliably, will cause the related machine to behave unexpectedly.
2019-09-22 13:14:09 -04:00
Thomas Harte
e78b1dcf3c Merge pull request #652 from TomHarte/Xcode11
Updates to Xcode11 recommended project settings.
2019-09-22 12:19:39 -04:00
Thomas Harte
8a14f5d814 Updates to Xcode11 recommended project settings.
The updated compiler also flagged a potential issue with CPU::Z80::Register not being a namespace re: 'Refresh' versus CPU::Z80::PartialMachineCycle. I don't entirely see it, but this fixes the problem.

I also finally figured out what the compiler was trying to tell me about ROMRequester.xib.
2019-09-22 12:13:56 -04:00
Thomas Harte
e5f983fbac Merge pull request #651 from TomHarte/MacOpenWindow
Shows the auto-open dialogue only at most once.
2019-09-21 18:01:54 -04:00
Thomas Harte
3e639e96e7 Shows the auto-open dialogue only at most once. 2019-09-21 18:01:16 -04:00
Thomas Harte
61993f0687 Merge pull request #650 from TomHarte/DeadReference
Removes dead reference to video from VIAPortHandler.
2019-09-21 17:40:22 -04:00
Thomas Harte
5f16fa8c08 Removes dead reference to video from VIAPortHandler. 2019-09-21 17:39:45 -04:00
Thomas Harte
dcea9c9ab2 Merge pull request #649 from TomHarte/BetterTiming
Implements every-other-cycle-during-pixels RAM timing.
2019-09-21 17:35:28 -04:00
Thomas Harte
e7bf0799b6 Implements every-other-cycle-during-pixels RAM timing. 2019-09-21 17:25:20 -04:00
Thomas Harte
e760421f6f Merge pull request #648 from TomHarte/QuickBoot
Adds the option to skip the Mac Plus memory test
2019-09-19 22:47:07 -04:00
Thomas Harte
8ea4c17315 Completes Mac GUI wiring. 2019-09-19 22:37:23 -04:00
Thomas Harte
2e24da4614 Implements quick booting, and edges towards exposing it on the Mac.
It should already work in kiosk mode.
2019-09-19 22:32:12 -04:00
Thomas Harte
e46601872b Establishes that the Macintosh offers the quick-boot option. 2019-09-19 21:50:39 -04:00
Thomas Harte
6d0e41b760 Adds "quick boot" as a standard option.
I assume I'm going to reuse this.
2019-09-19 19:41:43 -04:00
Thomas Harte
5a82df837d Merge pull request #647 from TomHarte/SCSIActivity
Adds the SCSI bus as an Activity::Source.
2019-09-19 19:33:06 -04:00
Thomas Harte
776b819a5a Adds the SCSI bus as an Activity::Source. 2019-09-19 19:31:22 -04:00
Thomas Harte
1783f6c84b Update README.md
Corrects alphabetical order.
2019-09-18 22:11:37 -04:00
Thomas Harte
2ef2c73efe The early Macintoshes are less experimental now. 2019-09-18 22:11:04 -04:00
Thomas Harte
55e003ccc1 Merge pull request #646 from TomHarte/LinuxFixes
Corrects hasty merge for more standards-compliant targets
2019-09-18 22:07:57 -04:00
Thomas Harte
3d54d55dbb Adds missing #include for assert. 2019-09-18 22:06:13 -04:00
Thomas Harte
72c0a631f7 Moves includes to correct file. 2019-09-18 22:04:54 -04:00
Thomas Harte
1608a90d5d Takes another stab at finding ssize_t. 2019-09-18 22:03:51 -04:00
Thomas Harte
4f8a45a6ce Adds #include for ssize_t. 2019-09-18 22:02:59 -04:00
Thomas Harte
4f0f1dcf18 Corrects accidental use of #import. 2019-09-18 21:53:22 -04:00
Thomas Harte
839e51d92d Adds newest files to SConstruct. 2019-09-18 21:49:57 -04:00
Thomas Harte
e470cf23d8 Merge pull request #645 from TomHarte/SCSI
Adds basic SCSI support, along with a Mac Plus to use it.
2019-09-18 21:45:48 -04:00
Thomas Harte
8d4a96683a Reduces output noise. 2019-09-18 21:41:29 -04:00
Thomas Harte
f53411a319 Removes local NDEBUG. 2019-09-18 21:35:26 -04:00
Thomas Harte
128a1da626 Enables write support. 2019-09-18 20:18:02 -04:00
Thomas Harte
962275c22a Removes clock for NCR 5380.
It doesn't have one in real life, and now can live off the time counting that occurs on the SCSI bus.
2019-09-18 20:17:47 -04:00
Thomas Harte
3002ac8a4a Adds mapping of READ(8) size 0 to size 256. 2019-09-17 21:59:32 -04:00
Thomas Harte
ff43674638 Corrects partition map: string fields are 32 bytes long. 2019-09-17 21:46:14 -04:00
Thomas Harte
2f6c366668 Makes a concerted effort at properly wrapping a hard disk image. 2019-09-17 21:30:04 -04:00
Thomas Harte
2ce1f0a3b1 Implements multi-sector read/write.
This once again unblocks Apple HD SC Setup. Progress!
2019-09-16 22:20:42 -04:00
Thomas Harte
210129c3a1 Updated as per Inside Macintosh IV.
Of which I now own a copy.
2019-09-16 21:31:43 -04:00
Thomas Harte
934901447a Adds a temporary version of block access writing.
Whatever I'm doing, it's still not correct. The Macintosh ostensibly appears to 0-fill the direct-access device, then reads a sector back and hangs.
2019-09-15 22:06:45 -04:00
Thomas Harte
960b289e70 Edges closer towards proper DMA operation.
Specifically: differentiates the three kinds of DMA operation. Still doesn't act correctly with regard to DACK though, and leaves the bus instantaneously improperly formed. Which I'm tempted to try to fix on the target side by properly obeying delays.
2019-09-15 15:03:06 -04:00
Thomas Harte
243e40cd79 Adds signalling of DACK. 2019-09-14 13:48:33 -04:00
Thomas Harte
c849188016 Adds format and write to the SCSI target.
Now I think I need to switch back to the 5380 to ensure proper DMA mode interactions when writing.
2019-09-12 21:58:09 -04:00
Thomas Harte
87e8dade2f Implements READ BUFFER to do, you know, *something*. Plus READ CAPACITY.
The HD SC utility now offers up drive 6 for formatting. That's progress.
2019-09-11 21:52:02 -04:00
Thomas Harte
6fc5b4e825 Simplifies INQUIRY for future targets; implements enough of SENSE MODE to advance.
The HD SC setup utility is now looking to read buffer.
2019-09-08 21:59:56 -04:00
Thomas Harte
00ce7f8ae0 Takes a first shot at INQUIRY. 2019-09-07 22:04:44 -04:00
Thomas Harte
6e0e9afe2f Fixed: to post a message, I want message in, not message out. 2019-09-07 13:35:38 -04:00
Thomas Harte
cb0d994827 Provides empty data for the unimplemented sectors. 2019-09-07 13:17:53 -04:00
Thomas Harte
bee782234a Ensures no state transitions while acknowledge is still asserted. 2019-09-07 13:17:34 -04:00
Thomas Harte
64dad35026 Decreases logging, at least temporarily. 2019-09-03 22:40:32 -04:00
Thomas Harte
cbd1a8cf78 Factors out command termination, adds a default implementation of test unit ready. 2019-09-03 22:40:18 -04:00
Thomas Harte
a4ab0afce3 Takes a shot at completing a full SCSI interaction. 2019-09-03 21:15:30 -04:00
Thomas Harte
1c7e0f3c9d Fixes control line modification by the 5380 and SCSI target command chaining.
So now I'm back to trying to guess how a SCSI command terminates re: the relative meanings of a message phase and a status phase.
2019-09-02 23:14:37 -04:00
Thomas Harte
318cdb41ea Adds SCSI bus clocking to the Macintosh, and fixes its internal counting. 2019-09-02 16:03:33 -04:00
Thomas Harte
2f8e31bc8b Takes a first bash at implementing the new SCSI::Bus timing infrastructure. 2019-09-02 13:00:01 -04:00
Thomas Harte
310c722cc0 Starts a transition to bus-level knowledge of SCSI-specific bus timing thresholds.
The idea being that bus attachees need not all count time for themselves. They can be very plain finite state machines.

New semantics are not yet implemented within the Bus. The plan is to do that, remove the internal counting of time within the NCR, then adjust the Target to be more explicitly stateful.
2019-08-31 21:44:22 -04:00
Thomas Harte
25956bd90f Mildly improves temporary logging.
A deckchair shuffle, at best.
2019-08-26 22:35:11 -04:00
Thomas Harte
1a60ced61b Starts trying to deal with creating a whole volume from merely a partition. 2019-08-25 23:03:54 -04:00
Thomas Harte
081316c071 Adds some additional commentary as this takes shape. 2019-08-25 17:46:05 -04:00
Thomas Harte
eafbc12cc1 Ensures a clean entry into the command phase. 2019-08-25 17:43:14 -04:00
Thomas Harte
ca08716c52 Introduces real hard disk images to the nascent world of SCSI. 2019-08-25 17:03:41 -04:00
Thomas Harte
30cef1ee22 Adds write support. 2019-08-25 15:16:35 -04:00
Thomas Harte
5598802439 Makes the static analyser aware of mass-storage devices. 2019-08-25 15:10:09 -04:00
Thomas Harte
1c6720b0db Adds new class to Xcode project. 2019-08-25 15:09:43 -04:00
Thomas Harte
404b088199 Adds a trivial mass-storage device, for Macintosh HFV volumes. 2019-08-25 15:09:27 -04:00
Thomas Harte
7d61df238a Localises #include. 2019-08-25 15:09:04 -04:00
Thomas Harte
c86db12f1c Starts implementing DMA support on the 5380.
The Macintosh doesn't actually use the DMA signals, but uses pseudo-DMA mode so they nevertheless need to be appropriate.
2019-08-24 22:47:11 -04:00
Thomas Harte
ce2e85af8b Adds missing bus state callouts. 2019-08-22 23:27:00 -04:00
Thomas Harte
2d82855f26 Attempts to provide a data out phase. 2019-08-22 23:16:58 -04:00
Thomas Harte
faec516a2c Starts pushing towards figuring out a proper infrastructure for mass storage. 2019-08-21 23:22:58 -04:00
Thomas Harte
8e274ec5d0 Merge branch 'master' into SCSI 2019-08-21 22:38:18 -04:00
Thomas Harte
bb1a0a0b76 Sketches out further SCSI infrastructure. 2019-08-21 22:37:39 -04:00
Thomas Harte
252650808d Starts seeking to unbind SCSI bus logic and command performance. 2019-08-19 22:47:01 -04:00
Thomas Harte
e3d9254555 Implements phase-match bit.
Seemingly causing the command phase to proceed.
2019-08-18 23:15:54 -04:00
Thomas Harte
90cf99b626 Takes a wild swings at speeding up startup.
With no success.
2019-08-18 22:40:16 -04:00
Thomas Harte
955e909e61 Attempts to nudge the command phase further towards functioning. 2019-08-18 22:39:27 -04:00
Thomas Harte
8339e2044c Switches to proper SCSI terminology and better attempts a command phase. 2019-08-18 15:10:07 -04:00
Thomas Harte
0e0c789b02 Starts attempting to introduce a direct access device.
Without having access to the SCSI-1 standard, a lot of this is guesswork.
2019-08-17 23:43:42 -04:00
Thomas Harte
7e001c1d03 Corrects data line loading.
Also adds some extra temporary logging. Outstanding question: why is ATN not being signalled? Is SEL enough?
2019-08-17 21:30:59 -04:00
Thomas Harte
9047932b81 Corrected basic error. Arbitration now seems to succeed.
This is seemingly followed by a pattern of signalling BUSY+SEL followed by just SEL with the various other potential device IDs in turn. To which nothing ever responds as currently implemented.
2019-08-15 23:28:30 -04:00
Thomas Harte
f668e4a54c Makes an attempt at getting the 5380 past arbitration.
Not entirely successful. Also gets a bit smarter with `final` on ClockingHint::Sources.
2019-08-15 23:14:40 -04:00
Thomas Harte
ce1c96d68c Starts thinking out the mechanics of emulating a SCSI-1 bus. 2019-08-13 23:09:11 -04:00
Thomas Harte
0f67e490e8 Adjusts NCR address decoding to produce a more plausible initial interaction. 2019-08-11 22:43:25 -04:00
Thomas Harte
895c315fa5 Increases the Mac Plus too 4mb. 2019-08-11 21:41:12 -04:00
Thomas Harte
a90a74a512 Stubs in just enough of the 5380 to get a Mac Plus too boot. 2019-08-11 20:55:20 -04:00
Thomas Harte
3e1286cbef Merge pull request #644 from MaddTheSane/patch-1
Update CSMachine.mm
2019-08-11 19:25:16 -04:00
Thomas Harte
949c1e1668 Adds an empty shell for what will be my 5380 implementation. 2019-08-10 23:53:52 -04:00
Thomas Harte
bbd4e4d3dc Enhances memory map fidelity to allow for ROM holes on the Mac Plus.
This is how the ROM detects the difference between the Plus and the 512ke, it seems.
2019-08-10 23:53:34 -04:00
C.W. Betts
4c5f596533 Update CSMachine.mm
No need to create a temporary NSNumber object to be passed to a variadic method.
2019-08-10 00:43:30 -06:00
Thomas Harte
4859d3781b Merge pull request #643 from TomHarte/Mac512
Simplifies just-in-time usage of the IWM, and the disk-speed accumulator
2019-08-07 21:51:07 -04:00
Thomas Harte
bac0461f7f Switches the drive-speed accumulator to the delegate pattern.
This allows the Macintosh to ensure that the IWM is kept up just-in-time with drive speed changes.
2019-08-07 21:39:23 -04:00
Thomas Harte
f26a200d78 Switches to a JustInTimeActor to wrap the IWM.
Also simplifies potential future usage of the IWM template.
2019-08-07 21:28:02 -04:00
Thomas Harte
28ccb7b54e Merge branch 'master' of github.com:TomHarte/CLK 2019-08-04 21:34:49 -04:00
Thomas Harte
b6e4c8209b Switches to showing 'File -> Open...' at launch.
As per the prevailing wind.
2019-08-04 21:34:30 -04:00
Thomas Harte
16548f0765 Merge pull request #642 from TomHarte/InterruptSampling
Moves timing of interrupt sampling into prefetch queue advancement.
2019-08-04 21:20:22 -04:00
Thomas Harte
6a80832140 Moves timing of interrupt sampling into prefetch queue advancement.
As per comment, that is definitely the only place it can occur; I don't know whether it always occurs there.
2019-08-04 21:06:34 -04:00
Thomas Harte
c6cf0e914b Merge pull request #641 from TomHarte/DIVSTiming
Substantially improves DIVS timing.
2019-08-04 20:46:50 -04:00
Thomas Harte
35b1a55c12 Corrects DIVS negative flag. 2019-08-04 20:36:33 -04:00
Thomas Harte
e3794c0c0e Takes a second pass at DIVS timing, seeming to correct that side of things. 2019-08-04 20:33:43 -04:00
Thomas Harte
f88dc23c71 Corrects comment. 2019-08-04 20:30:41 -04:00
Thomas Harte
0e293e4983 Relocates RAM delay test in order to scrape out a minor performance win. 2019-08-03 21:46:45 -04:00
Thomas Harte
e334abfe20 Partitions the 68000 arithmetic tests, to allow easier per-instruction execution. 2019-08-03 17:44:47 -04:00
Thomas Harte
fd2fbe0e59 Merge pull request #640 from TomHarte/InterruptSignalling
Corrects 68000 address lines during interrupt acknowledgement.
2019-08-03 15:42:15 -04:00
Thomas Harte
330b27d085 Merge branch 'master' into InterruptSignalling 2019-08-03 15:39:22 -04:00
Thomas Harte
478f2533b5 Corrects 68000 address bus during interrupt acknowledge.
All unused bits should be 1, not 0.
2019-08-03 15:38:36 -04:00
Thomas Harte
b96972a4b9 Merge pull request #639 from InvisibleUp/master
Allow for scons to run on Python 3
2019-08-03 08:51:45 -04:00
InvisibleUp
f2b083f4de Allow for scons to run on Python 3 2019-08-03 00:33:53 -04:00
Thomas Harte
80f6d665d9 Merge pull request #638 from TomHarte/SimplifiedBus
Simplifies code around Mac bus decoding.
2019-08-02 22:33:54 -04:00
Thomas Harte
a07488cf1b Introduces the Mac Plus memory map.
Albeit with no SCSI support yet.
2019-08-02 22:26:40 -04:00
Thomas Harte
d67c5145c0 Introduces RAM access delays. 2019-08-02 22:12:34 -04:00
Thomas Harte
5e76d593af Switches to table-based address decoding. 2019-08-02 21:30:04 -04:00
Thomas Harte
83393e8e91 Merge branch 'master' into SimplifiedBus 2019-08-02 21:05:45 -04:00
Thomas Harte
e08a64d455 Fixes erroneous instruction. 2019-08-02 21:04:53 -04:00
Thomas Harte
b93f9b3973 Distinguishes time advancement from bus response. 2019-08-02 19:48:41 -04:00
Thomas Harte
9c517d07d4 Merge pull request #637 from TomHarte/UserInfo
Improves Macintosh user communications
2019-08-02 17:29:44 -04:00
Thomas Harte
f45de5b87a Adds how-to-release-the-mouse instructions for Cocoa. 2019-08-02 17:07:51 -04:00
Thomas Harte
011d76175c Adds mouse release instructions for SDL. 2019-08-02 16:38:05 -04:00
Thomas Harte
96005261c7 Adds activity lights for Macintosh disk activity.
Prompting a quick fix to drives not spinning down.
2019-08-02 16:26:23 -04:00
Thomas Harte
c8177af45a Introduces missing implementation file. 2019-08-02 16:26:08 -04:00
Thomas Harte
97eff5b16d Formally distinguishes Macintosh keys from virtual keys.
Also: adds mappings for keypad keys, and corrects a couple of
long-standing capitalisation errors in my virtual key set.
2019-08-02 16:15:34 -04:00
Thomas Harte
917520fb1e Merge pull request #636 from TomHarte/PWM
Attempts more accurately to match Apple's IWM windowing logic.
2019-08-02 15:15:11 -04:00
Thomas Harte
335dda3d55 Attempts more accurately to match Apple's windowing logic. 2019-08-02 12:49:45 -04:00
86 changed files with 4147 additions and 1062 deletions

View File

@@ -10,10 +10,10 @@
#include "Target.hpp"
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.
if(media.disks.empty()) return {};
// This analyser can comprehend disks and mass-storage devices only.
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;
using Target = Analyser::Static::Macintosh::Target;

View File

@@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target {
MacPlus
};
Model model = Model::Mac512ke;
Model model = Model::MacPlus;
};
}

View File

@@ -46,6 +46,9 @@
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.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("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::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::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
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 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.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);

View File

@@ -11,9 +11,10 @@
#include "../Machines.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include <memory>
#include <string>
@@ -29,9 +30,10 @@ struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
}
};

View File

@@ -21,7 +21,7 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
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:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
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,
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:
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :

312
Components/5380/ncr5380.cpp Normal file
View 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;
}

View 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 */

View File

@@ -24,7 +24,7 @@ class BusHandler {
virtual void set_interrupt(bool irq) {}
};
class i8272: public Storage::Disk::MFMController {
class i8272 : public Storage::Disk::MFMController {
public:
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_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
protected:
virtual void select_drive(int number) = 0;

View File

@@ -100,7 +100,7 @@ class Base {
// (though, in practice, it won't happen until the next
// external slot after this number of cycles after the
// device has requested the read or write).
return 7;
return 6;
}
// Holds the main status register.

View File

@@ -26,7 +26,7 @@ namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII:
class DiskII final:
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
@@ -76,7 +76,7 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// 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.
void set_activity_observer(Activity::Observer *observer);

View File

@@ -235,23 +235,32 @@ void IWM::run_for(const Cycles cycles) {
// Activity otherwise depends on mode and motor state.
int integer_cycles = cycles.as_int();
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_]) {
while(integer_cycles--) {
drives_[active_drive_]->run_for(Cycles(1));
++cycles_since_shift_;
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
if(cycles_since_shift_ == bit_length_ + error_margin) {
propose_shift(0);
}
}
} 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);
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
}
cycles_since_shift_ += Cycles(integer_cycles);
}
break;
} break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
@@ -351,12 +360,28 @@ void IWM::propose_shift(uint8_t bit) {
// TODO: synchronous mode.
// 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);
if(shift_register_ & 0x80) {
data_register_ = shift_register_;
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) {
@@ -374,3 +399,8 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
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);
}

View File

@@ -9,8 +9,11 @@
#ifndef IWM_hpp
#define IWM_hpp
#include "../../Activity/Observer.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include <cstdint>
@@ -67,6 +70,10 @@ class IWM:
/// Connects a drive to the IWM.
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:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) override;

View File

@@ -71,7 +71,9 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
// 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) {

View File

@@ -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.
*/
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");
if(!quickload) return false;
result = quickload->value;
auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name);
if(!selection) return false;
result = selection->value;
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));
}
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;
}
@@ -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));
}
void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) {
append_bool(selection_set, "quickboot", selection);
}
// MARK: - Selection parsers
bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &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;
}
bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) {
return get_bool(selections_by_option, "quickboot", result);
}

View File

@@ -19,7 +19,8 @@ enum StandardOptions {
DisplayCompositeColour = (1 << 2),
DisplayCompositeMonochrome = (1 << 3),
QuickLoadTape = (1 << 4),
AutomaticTapeMotorControl = (1 << 5)
AutomaticTapeMotorControl = (1 << 5),
QuickBoot = (1 << 6),
};
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);
/*!
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.
@@ -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);
/*!
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 */

View File

@@ -10,13 +10,14 @@
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) {
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) {
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);
}
const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
return essential_modifiers_;
}
void Keyboard::reset_all_keys() {
std::fill(key_states_.begin(), key_states_.end(), false);
if(delegate_) delegate_->reset_all_keys(this);

View File

@@ -23,8 +23,8 @@ class Keyboard {
public:
enum class Key {
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,
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
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,
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,
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
@@ -39,10 +39,10 @@ class Keyboard {
};
/// 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.
Keyboard(const std::set<Key> &observed_keys);
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
// Host interface.
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.
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
like a complete keyboard — i.e. if a user would expect this keyboard
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();
@@ -68,6 +76,7 @@ class Keyboard {
private:
std::set<Key> observed_keys_;
std::set<Key> essential_modifiers_;
std::vector<bool> key_states_;
Delegate *delegate_ = nullptr;
bool is_exclusive_ = true;

View File

@@ -31,12 +31,12 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(F11, KeyRightSquareBracket);
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(OpenSquareBracket, KeyAt);
BIND(CloseSquareBracket, KeyLeftSquareBracket);
BIND(BackSlash, KeyBackSlash);
BIND(Backslash, KeyBackSlash);
BIND(CapsLock, KeyCapsLock);
BIND(Semicolon, KeyColon);

View File

@@ -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)));
}
void update_just_in_time_cards() {
for(const auto &card : just_in_time_cards_) {
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
if(cycles_since_card_update_ > Cycles(0)) {
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;
stretched_cycles_since_card_update_ = 0;
@@ -124,19 +126,25 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
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();
}
bool card_lists_are_dirty_ = true;
bool card_became_just_in_time_ = false;
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);
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;
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
if(old_membership != undesired.end()) undesired.erase(old_membership);
intended.push_back(card);
// Otherwise, mark the sets as dirty. It isn't safe to transition the card here,
// 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 {
@@ -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.
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::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
case Key::Backspace: value = 0x7f; break;
default: return;
}
}

View File

@@ -83,10 +83,8 @@ class Card {
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
only to cards that register for IO and/or Device accesses only.
*/
int get_select_constraints() {
int get_select_constraints() const {
return select_constraints_;
}

View File

@@ -65,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::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) {

View File

@@ -11,7 +11,7 @@
using namespace Apple::Macintosh;
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
// 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 rotation_speed = (normalised_sum * 27.08f) - 259.0f;
for(int c = 0; c < number_of_drives_; ++c) {
drives_[c]->set_rotation_speed(rotation_speed);
}
// printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum);
delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed);
}
}
void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) {
drives_[number_of_drives_] = drive;
++number_of_drives_;
}

View File

@@ -13,8 +13,6 @@
#include <cstddef>
#include <cstdint>
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
namespace Apple {
namespace Macintosh {
@@ -25,18 +23,20 @@ class DriveSpeedAccumulator {
*/
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
can be supplied. Only Macintosh DoubleDensityDrives
are supported.
Sets the delegate to receive drive speed changes.
*/
void add_drive(Apple::Macintosh::DoubleDensityDrive *drive);
void set_delegate(Delegate *delegate) {
delegate_ = delegate;;
}
private:
std::array<uint8_t, 20> samples_;
std::size_t sample_pointer_ = 0;
Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr};
int number_of_drives_ = 0;
Delegate *delegate_ = nullptr;
};
}

View 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
}
}

View File

@@ -10,6 +10,7 @@
#define Apple_Macintosh_Keyboard_hpp
#include "../../KeyboardMachine.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <mutex>
#include <vector>
@@ -17,6 +18,72 @@
namespace Apple {
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 {
public:
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
// 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(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
}
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) {
switch(command) {
case 0x10: // Inquiry.
@@ -180,22 +249,41 @@ class Keyboard {
return 0x7b; // No key transition.
}
/// Maintains the current operating mode — a record of what the
/// keyboard is doing now.
enum class Mode {
/// The keyboard is waiting to begin a transaction.
Waiting,
/// The keyboard is currently clocking in a new command.
AcceptingCommand,
/// The keyboard is waiting for the computer to indicate that it is ready for a response.
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,
PerformingCommand
} mode_ = Mode::Waiting;
/// Holds a count of progress through the current @c Mode. Exact meaning depends on mode.
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;
/// 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;
/// The current state of the serial connection's data input.
bool data_input_ = false;
/// The current clock output from this keyboard.
bool clock_output_ = false;
// TODO: improve this very, very simple implementation.
/// Guards multithread access to key_queue_.
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_;
};
@@ -203,89 +291,7 @@ class Keyboard {
Provides a mapping from idiomatic PC keys to Macintosh keys.
*/
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
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. */
}
}
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
};
}

View File

@@ -16,6 +16,7 @@
#include "RealTimeClock.hpp"
#include "Video.hpp"
#include "../../../Activity/Source.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../MediaTarget.hpp"
@@ -25,15 +26,22 @@
#include "../../../Outputs/Log.hpp"
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Configurable/StandardOptions.hpp"
//#define LOG_TRACE
#include "../../../Components/5380/ncr5380.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../../../Components/8530/z8530.hpp"
#include "../../../Components/DiskII/IWM.hpp"
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.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 "../../Utility/MemoryPacker.hpp"
@@ -48,6 +56,12 @@ const int CLOCK_RATE = 7833600;
namespace Apple {
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:
public Machine,
public CRTMachine::Machine,
@@ -55,16 +69,28 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
public MouseMachine::Machine,
public CPU::MC68000::BusHandler,
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:
using Target = Analyser::Static::Macintosh::Target;
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),
iwm_(CLOCK_RATE),
video_(ram_, audio_, drive_speed_accumulator_),
video_(audio_, drive_speed_accumulator_),
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_{
{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;
case Model::Mac512ke:
case Model::MacPlus: {
ram_size = 512*1024;
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
rom_size = 128*1024;
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);
@@ -99,7 +125,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
ram_mask_ = (ram_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.
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_);
// Randomise memory contents.
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
Memory::Fuzz(ram_);
// Attach the drives to the IWM.
iwm_.iwm.set_drive(0, &drives_[0]);
iwm_.iwm.set_drive(1, &drives_[1]);
iwm_->set_drive(0, &drives_[0]);
iwm_->set_drive(1, &drives_[1]);
// 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_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]);
if(!drives_[0].is_800k() || !drives_[1].is_800k()) {
drive_speed_accumulator_.set_delegate(this);
}
// Make sure interrupt changes from the SCC are observed.
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.
set_clock_rate(double(CLOCK_RATE));
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
// Insert any supplied media.
insert_media(target.media);
// Set the immutables of the memory map.
setup_memory_map();
}
~ConcreteMachine() {
@@ -149,96 +185,45 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// TODO: pick a delay if this is a video-clashing memory fetch.
HalfCycles delay(0);
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);
}
forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// Advance time.
advance_time(cycle.length);
// 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.
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
// 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
// a read or write.
if(!cycle.data_select_active()) return delay;
// a read or write.
//
// 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
// apply more complicated decoding logic.
if(word_address >= 0x400000) {
const int register_address = word_address >> 8;
// Grab the word-precision address being accessed.
uint16_t *memory_base = nullptr;
HalfCycles delay;
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,
// which at word precision is 0x77f0ff + register*256.
if(cycle.operation & Microcycle::Read) {
@@ -246,116 +231,139 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
} else {
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.
iwm_.flush();
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = iwm_.iwm.read(register_address);
cycle.value->halves.low = iwm_->read(register_address);
} else {
iwm_.iwm.write(register_address, cycle.value->halves.low);
iwm_->write(register_address, cycle.value->halves.low);
}
break;
case 0x780000:
// Phase read.
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
} 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) {
cycle.value->halves.low = phase_ & 7;
}
break;
case 0x480000: case 0x48f000:
case 0x580000: case 0x58f000:
// Any word access here adjusts phase.
if(cycle.operation & Microcycle::SelectWord) {
++phase_;
scsi_.write(register_address, 0xff, dma_acknowledge);
} else {
if(word_address < 0x500000) {
// A0 = 1 => reset; A0 = 0 => read.
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;
}
}
if(cycle.operation & Microcycle::SelectWord) {
scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
} else {
if(*cycle.address & 1) {
if(cycle.operation & Microcycle::Read) {
scc_.write(int(word_address), 0xff);
} else {
scc_.write(int(word_address), cycle.value->halves.low);
}
}
scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
}
}
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 {
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:
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:
cycle.value->full = memory_base[word_address];
break;
@@ -373,17 +381,6 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
break;
}
/*
Normal memory map:
000000: RAM
400000: ROM
9FFFF8+: SCC read operations
BFFFF8+: SCC write operations
DFE1FF+: IWM
EFE1FE+: VIA
*/
return delay;
}
@@ -404,6 +401,48 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
void set_rom_is_overlay(bool 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() {
@@ -416,16 +455,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
bool insert_media(const Analyser::Static::Media &media) override {
if(media.disks.empty())
if(media.disks.empty() && media.mass_storage_devices.empty())
return false;
// TODO: shouldn't allow disks to be replaced like this, as the Mac
// uses software eject. Will need to expand messaging ability of
// insert_media.
if(drives_[0].has_disk())
drives_[1].set_disk(media.disks[0]);
else
drives_[0].set_disk(media.disks[0]);
if(!media.disks.empty()) {
if(drives_[0].has_disk())
drives_[1].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;
}
@@ -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:
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>());
time_until_video_event_ = video_.get_next_sequence_point();
}
@@ -470,21 +656,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
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 {
public:
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, JustInTimeActor<IWM, HalfCycles, Cycles> &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
using Port = MOS::MOS6522::Port;
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
b2b0: audio output volume
*/
iwm_.flush();
iwm_.iwm.set_select(!!(value & 0x20));
iwm_->set_select(!!(value & 0x20));
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
machine_.set_rom_is_overlay(!!(value & 0x10));
@@ -589,16 +763,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
ConcreteMachine &machine_;
RealTimeClock &clock_;
Keyboard &keyboard_;
Video &video_;
DeferredAudio &audio_;
IWM &iwm_;
JustInTimeActor<IWM, HalfCycles, Cycles> &iwm_;
Inputs::QuadratureMouse &mouse_;
};
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
DriveSpeedAccumulator drive_speed_accumulator_;
IWM iwm_;
JustInTimeActor<IWM, HalfCycles, Cycles> iwm_;
DeferredAudio audio_;
Video video_;
@@ -610,6 +783,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
VIAPortHandler via_port_handler_;
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 real_time_clock_;
@@ -620,16 +797,61 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
bool ROM_is_overlay_ = true;
int phase_ = 1;
int ram_subcycle_ = 0;
DoubleDensityDrive drives_[2];
Inputs::QuadratureMouse mouse_;
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 rom_mask_ = 0;
uint16_t rom_[64*1024];
uint16_t ram_[256*1024];
uint16_t rom_[64*1024]; // i.e. up to 128kb in size.
std::vector<uint16_t> ram_;
};
}

View File

@@ -9,12 +9,15 @@
#ifndef Macintosh_hpp
#define Macintosh_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
namespace Apple {
namespace Macintosh {
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine {
public:
virtual ~Machine();

View File

@@ -25,8 +25,15 @@ class RealTimeClock {
public:
RealTimeClock() {
// TODO: this should persist, if possible, rather than
// being randomly initialised.
Memory::Fuzz(data_, sizeof(data_));
// being default initialised.
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_));
}
/*!

View File

@@ -12,16 +12,6 @@
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,
// 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...
// 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),
drive_speed_accumulator_(drive_speed_accumulator),
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
ram_(ram) {
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
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) {
use_alternate_screen_buffer_ = use_alternate_screen_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;
}

View File

@@ -17,6 +17,12 @@
namespace Apple {
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,
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
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.
@@ -47,10 +53,10 @@ class Video {
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.
*/
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.
@@ -61,7 +67,12 @@ class Video {
@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
@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

View File

@@ -34,7 +34,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(OpenSquareBracket, KeyAt);
BIND(CloseSquareBracket, KeyAsterisk);
BIND(BackSlash, KeyRestore);
BIND(Backslash, KeyRestore);
BIND(Hash, KeyUp);
BIND(F10, KeyUp);
@@ -59,7 +59,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(Enter, KeyReturn);
BIND(Space, KeySpace);
BIND(BackSpace, KeyDelete);
BIND(Backspace, KeyDelete);
BIND(Escape, KeyRunStop);
BIND(F1, KeyF1);

View File

@@ -41,7 +41,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(Hyphen, KeyMinus);
BIND(Delete, KeyDelete); BIND(BackSpace, KeyDelete);
BIND(Delete, KeyDelete); BIND(Backspace, KeyDelete);
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);

View File

@@ -10,7 +10,7 @@
using namespace KeyboardMachine;
MappedMachine::MappedMachine() {
MappedMachine::MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers) : keyboard_(essential_modifiers) {
keyboard_.set_delegate(this);
}

View File

@@ -11,6 +11,7 @@
#include <cstdint>
#include <string>
#include <set>
#include "../Inputs/Keyboard.hpp"
@@ -56,7 +57,7 @@ class Machine: public KeyActions {
*/
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
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.

View File

@@ -47,12 +47,12 @@ uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(FullStop, KeyFullStop);
BIND(Comma, KeyComma);
BIND(ForwardSlash, KeyForwardSlash);
BIND(BackSlash, KeyBackSlash);
BIND(Backslash, KeyBackSlash);
BIND(BackTick, KeyGrave);
BIND(Enter, KeyEnter);
BIND(Space, KeySpace);
BIND(BackSpace, KeyBackspace);
BIND(Backspace, KeyBackspace);
default: break;
}

View File

@@ -101,7 +101,7 @@ class ConcreteMachine:
audio_queue_,
sn76489_divider),
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.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));

View File

@@ -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(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(BackSlash, KeyBackSlash);
BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(Backslash, KeyBackSlash);
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(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash);

View File

@@ -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::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::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::Oric), Oric::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options()));

View File

@@ -26,7 +26,3 @@ void Memory::Fuzz(uint8_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));
}
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
Fuzz(buffer.data(), buffer.size());
}

View File

@@ -22,7 +22,9 @@ void Fuzz(uint8_t *buffer, std::size_t size);
void Fuzz(uint16_t *buffer, std::size_t size);
/// 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]));
}
}

View File

@@ -186,6 +186,7 @@
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; };
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.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 */; };
4B4B1A3C200198CA00A0F866 /* 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 */; };
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
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 */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
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 */; };
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; };
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.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 */; };
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
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 */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
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 */; };
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
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 */; };
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.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 */; };
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
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 */; };
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; };
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 */; };
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.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>"; };
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>"; };
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>"; };
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>"; };
@@ -940,6 +958,7 @@
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>"; };
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>"; };
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>"; };
@@ -950,6 +969,13 @@
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>"; };
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>"; };
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>"; };
@@ -965,6 +991,10 @@
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>"; };
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>"; };
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>"; };
@@ -1066,6 +1096,7 @@
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>"; };
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>"; };
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>"; };
@@ -1403,7 +1434,6 @@
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>"; };
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>"; };
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>"; };
@@ -1419,6 +1449,8 @@
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>"; };
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>"; };
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>"; };
@@ -1447,6 +1479,7 @@
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>"; };
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>"; };
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>"; };
@@ -1478,6 +1511,8 @@
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>"; };
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>"; };
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>"; };
@@ -1738,7 +1773,6 @@
4B2A53921D117D36003C6002 /* Machine */ = {
isa = PBXGroup;
children = (
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */,
4B2A53951D117D36003C6002 /* CSMachine.h */,
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */,
4B2A53971D117D36003C6002 /* KeyCodes.h */,
@@ -2103,6 +2137,7 @@
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */,
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */,
@@ -2168,6 +2203,7 @@
4BEE0A691D72496600532C7B /* Cartridge */,
4B8805F81DCFF6CD003085B1 /* Data */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
);
name = Storage;
@@ -2231,6 +2267,33 @@
path = Implementation;
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 */ = {
isa = PBXGroup;
children = (
@@ -2249,6 +2312,15 @@
path = Encodings/MFM;
sourceTree = "<group>";
};
4B74CF7E2312FA9C00500CE8 /* Formats */ = {
isa = PBXGroup;
children = (
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
4B74CF802312FA9C00500CE8 /* HFV.cpp */,
);
path = Formats;
sourceTree = "<group>";
};
4B77069E1EC9045B0053B588 /* Z80 */ = {
isa = PBXGroup;
children = (
@@ -2934,22 +3006,24 @@
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
isa = PBXGroup;
children = (
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */,
4BD0FBC2233706A200148981 /* CSApplication.m */,
4BB73EAD1B587A5100552FC2 /* Info.plist */,
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
4B2A538F1D117D36003C6002 /* Audio */,
4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */,
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
4B2A53921D117D36003C6002 /* Machine */,
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
4BD5F1961D1352A000631CD1 /* Updater */,
4B55CE5A1C3B7D6F0093A61B /* Views */,
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
);
path = "Clock Signal";
sourceTree = "<group>";
@@ -3108,6 +3182,7 @@
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
4BDACBE922FFA5B50045EF7E /* 5380 */,
4BD468F81D8DF4290084958B /* 1770 */,
4BC9DF4B1D04691600F44158 /* 6522 */,
4B1E85791D174DEC001EF87D /* 6532 */,
@@ -3182,6 +3257,7 @@
children = (
4B9378E222A199C600973513 /* Audio.cpp */,
4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */,
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */,
4BCE0058227CFFCA000CA200 /* Macintosh.cpp */,
4BCE005E227D39AB000CA200 /* Video.cpp */,
4B9378E322A199C600973513 /* Audio.hpp */,
@@ -3313,6 +3389,15 @@
path = ROMRequester;
sourceTree = "<group>";
};
4BDACBE922FFA5B50045EF7E /* 5380 */ = {
isa = PBXGroup;
children = (
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */,
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */,
);
path = 5380;
sourceTree = "<group>";
};
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
@@ -3503,7 +3588,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 1030;
LastUpgradeCheck = 1100;
ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = {
4B055A691FAE763F0060FFFF = {
@@ -3572,6 +3657,7 @@
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */,
4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */,
@@ -3900,6 +3986,7 @@
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
@@ -3933,6 +4020,7 @@
4B8318B322D3E540006DB630 /* Audio.cpp in Sources */,
4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */,
4B89452B201967B4007DE474 /* File.cpp in Sources */,
4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */,
4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */,
4BD424E62193B5830097291A /* Shader.cpp in Sources */,
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
@@ -3944,11 +4032,14 @@
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */,
4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
4B6AAEAE230E40250078E864 /* Target.cpp in Sources */,
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */,
@@ -3971,6 +4062,7 @@
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
@@ -4004,6 +4096,7 @@
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
@@ -4051,6 +4144,7 @@
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */,
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
4B9378E422A199C600973513 /* Audio.cpp in Sources */,
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
@@ -4060,6 +4154,7 @@
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
4B6AAEAD230E40250078E864 /* Target.cpp in Sources */,
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
@@ -4099,8 +4194,10 @@
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
@@ -4114,6 +4211,7 @@
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
@@ -4160,6 +4258,7 @@
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
@@ -4170,11 +4269,14 @@
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */,
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
@@ -4332,6 +4434,14 @@
name = OricOptions.xib;
sourceTree = "<group>";
};
4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */ = {
isa = PBXVariantGroup;
children = (
4B49F0A823346F7A0045E6A6 /* Base */,
);
name = MacintoshOptions.xib;
sourceTree = "<group>";
};
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -4407,6 +4517,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -4426,6 +4537,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -4555,6 +4667,7 @@
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -4596,6 +4709,7 @@
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

View File

@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm">
</FileRef>
<FileRef
location = "self:Clock Signal.xcodeproj">
</FileRef>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,9 +26,18 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
disableMainThreadChecker = "YES"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
codeCoverageEnabled = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
@@ -56,17 +65,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
BuildableName = "Clock Signal.app"
BlueprintName = "Clock Signal"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -78,8 +76,7 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryThreadSanitizerIssue = "YES"
stopOnEveryUBSanitizerIssue = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "NO">
<BuildableProductRunnable
@@ -92,8 +89,6 @@
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -12,15 +12,26 @@ import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
// Insert code here to initialize your application.
}
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 {
// 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
}
}

View 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>

View 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 */

View 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

View File

@@ -3,7 +3,6 @@
//
#import "CSMachine.h"
#import "CSFastLoading.h"
#import "CSAtari2600.h"
#import "CSZX8081.h"

View File

@@ -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
// is visible, though it's a little premature.
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 self.interactionMode == .notStarted {
// If a full machine exists, just continue showing it.
@@ -204,8 +209,10 @@ class MachineDocument:
openGLView.delegate = 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.shouldUsurpCommand = machine.shouldUsurpCommand
setupAudioQueueClockRate()
@@ -613,7 +620,17 @@ class MachineDocument:
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 {
let levelIndicator: NSLevelIndicator

View File

@@ -16,18 +16,29 @@ class MachinePanel: NSPanel {
return "\(self.machine.userDefaultsPrefix).\(key)"
}
// MARK: Fast Loading
var fastLoadingUserDefaultsKey: String {
return prefixedUserDefaultsKey("fastLoading")
}
@IBOutlet var fastLoadingButton: NSButton?
@IBAction func setFastLoading(_ sender: NSButton!) {
if let fastLoadingMachine = machine as? CSFastLoading {
let useFastLoadingHack = sender.state == .on
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey)
}
let useFastLoadingHack = sender.state == .on
machine.useFastLoadingHack = useFastLoadingHack
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 {
switch tag {
case 1: return .composite
@@ -49,17 +60,25 @@ class MachinePanel: NSPanel {
}
}
// MARK: Restoring user defaults
func establishStoredOptions() {
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
fastLoadingUserDefaultsKey: true,
bootQuicklyUserDefaultsKey: true,
displayTypeUserDefaultsKey: 0
])
if let fastLoadingMachine = machine as? CSFastLoading {
if let fastLoadingButton = self.fastLoadingButton {
let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey)
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
machine.useFastLoadingHack = useFastLoadingHack
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 {

View File

@@ -554,6 +554,6 @@
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<string>CSApplication</string>
</dict>
</plist>

View File

@@ -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

View File

@@ -9,7 +9,6 @@
#import <Foundation/Foundation.h>
#import "CSAudioQueue.h"
#import "CSFastLoading.h"
#import "CSOpenGLView.h"
#import "CSStaticAnalyser.h"
#import "CSJoystickManager.h"
@@ -86,6 +85,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
@property (nonatomic, assign) BOOL useQuickBootingHack;
@property (nonatomic, readonly) BOOL canInsertMedia;
@@ -93,6 +93,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
// Input control.
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
@property (nonatomic, readonly) BOOL shouldUsurpCommand;
@property (nonatomic, readonly) BOOL hasJoystick;
@property (nonatomic, readonly) BOOL hasMouse;
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;

View File

@@ -32,7 +32,7 @@
#include "../../../../Outputs/OpenGL/ScanTarget.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)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
- (void)addLED:(NSString *)led;
@@ -131,7 +131,7 @@ struct ActivityObserver: public Activity::Observer {
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s];
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
}
@end
@@ -415,7 +415,7 @@ struct ActivityObserver: public Activity::Observer {
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
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_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_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_Command, LeftMeta); BIND(VK_Shift, LeftShift);
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 {
// Assumes that the first machine in the targets list is the source of user defaults.
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();
}
- (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
- (void)addLED:(NSString *)led {

View File

@@ -219,6 +219,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
case Analyser::Machine::Atari2600: return @"Atari2600Options";
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Oric: return @"OricOptions";

View File

@@ -1,8 +1,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>
<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"/>
</dependencies>
<objects>
@@ -50,7 +50,7 @@ Gw
</connections>
</button>
<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">
<font key="font" metaFont="system"/>
<string key="title">If you use File -&gt; 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"/>
<subviews>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -76,7 +76,7 @@ Gw
</textFieldCell>
</textField>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -148,7 +148,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<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"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u">
<rect key="frame" x="15" y="74" width="574" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af">
<font key="font" usesAppearanceFont="YES"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="15" y="73" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
<font key="font" metaFont="system"/>
<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>
</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>
<constraints>
<constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/>
<constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/>
<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="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="3hY-Ca-mnR"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="5s6-87-VT6"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="3" id="KYf-GJ-Y7k"/>
<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>
</view>
</tabViewItem>
@@ -246,7 +261,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -273,7 +288,7 @@ Gw
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -309,7 +324,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -368,7 +383,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -376,7 +391,7 @@ Gw
</textFieldCell>
</textField>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -429,7 +444,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -477,7 +492,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<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">
<font key="font" metaFont="system"/>
<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="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
<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="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/>
<outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/>

View File

@@ -22,6 +22,9 @@ class MachinePicker: NSObject {
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton?
// MARK: - Macintosh properties
@IBOutlet var macintoshModelTypeButton: NSPopUpButton?
// MARK: - MSX properties
@IBOutlet var msxRegionButton: NSPopUpButton?
@IBOutlet var msxHasDiskDriveButton: NSButton?
@@ -62,6 +65,9 @@ class MachinePicker: NSObject {
// CPC settings
cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Macintosh settings
macintoshModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
// MSX settings
msxRegionButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxRegion"))
msxHasDiskDriveButton?.state = standardUserDefaults.bool(forKey: "new.msxDiskDrive") ? .on : .off
@@ -100,6 +106,9 @@ class MachinePicker: NSObject {
// CPC settings
standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel")
// Macintosh settings
standardUserDefaults.set(macintoshModelTypeButton!.selectedTag(), forKey: "new.macintoshModel")
// MSX settings
standardUserDefaults.set(msxRegionButton!.selectedTag(), forKey: "new.msxRegion")
standardUserDefaults.set(msxHasDiskDriveButton?.state == .on, forKey: "new.msxDiskDrive")
@@ -158,7 +167,13 @@ class MachinePicker: NSObject {
}
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":
let hasDiskDrive = msxHasDiskDriveButton!.state == .on

View File

@@ -1,8 +1,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>
<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"/>
</dependencies>
<objects>
@@ -25,11 +25,11 @@
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
<rect key="frame" x="18" y="148" width="444" height="102"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
<rect key="frame" x="18" y="154" width="444" height="96"/>
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
<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>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -50,7 +50,7 @@ DQ
</connections>
</button>
<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">
<font key="font" metaFont="message"/>
<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"/>
</constraints>
</view>
<point key="canvasLocation" x="139" y="147"/>
</window>
</objects>
</document>

View File

@@ -37,6 +37,19 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
*/
- (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
@protocol CSOpenGLViewResponderDelegate <NSObject>
@@ -97,8 +110,22 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
@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;
/// 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
to ensure that any retain cycles implied by the timer are resolved.

View File

@@ -7,10 +7,11 @@
//
#import "CSOpenGLView.h"
#import "CSApplication.h"
@import CoreVideo;
@import GLKit;
@interface CSOpenGLView () <NSDraggingDestination>
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationEventDelegate>
@end
@implementation CSOpenGLView {
@@ -139,23 +140,32 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
return YES;
}
- (void)keyDown:(NSEvent *)theEvent {
[self.responderDelegate keyDown:theEvent];
- (void)keyDown:(NSEvent *)event {
[self.responderDelegate keyDown:event];
}
- (void)keyUp:(NSEvent *)theEvent {
[self.responderDelegate keyUp:theEvent];
- (void)keyUp:(NSEvent *)event {
[self.responderDelegate keyUp:event];
}
- (void)flagsChanged:(NSEvent *)theEvent {
[self.responderDelegate flagsChanged:theEvent];
- (void)flagsChanged:(NSEvent *)event {
// Release the mouse upon a control + command.
if(_mouseIsCaptured &&
theEvent.modifierFlags & NSEventModifierFlagControl &&
theEvent.modifierFlags & NSEventModifierFlagCommand) {
event.modifierFlags & NSEventModifierFlagControl &&
event.modifierFlags & NSEventModifierFlagCommand) {
[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 {
@@ -223,6 +233,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
_mouseIsCaptured = NO;
CGAssociateMouseAndMouseCursorPosition(true);
[NSCursor unhide];
[self.delegate openGLViewDidReleaseMouse:self];
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil;
}
}
@@ -281,6 +293,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
_mouseIsCaptured = YES;
[NSCursor hide];
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
// an invitation to capture the cursor.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
import glob
import sys
# establish UTF-8 encoding
reload(sys)
sys.setdefaultencoding('utf-8')
# establish UTF-8 encoding for Python 2
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf-8')
# create build environment
env = Environment()
@@ -35,6 +36,7 @@ SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp')
SOURCES += glob.glob('../../Components/1770/*.cpp')
SOURCES += glob.glob('../../Components/5380/*.cpp')
SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
SOURCES += glob.glob('../../Components/6560/*.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/Track/*.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/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp')

View File

@@ -189,11 +189,11 @@ bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key)
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(LEFTBRACKET, OpenSquareBracket) BIND(RIGHTBRACKET, CloseSquareBracket)
BIND(BACKSLASH, BackSlash)
BIND(BACKSLASH, Backslash)
BIND(CAPSLOCK, CapsLock) BIND(SEMICOLON, Semicolon)
BIND(APOSTROPHE, Quote) BIND(RETURN, Enter)
@@ -302,6 +302,32 @@ std::string system_get(const char *command) {
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[]) {
@@ -351,7 +377,7 @@ int main(int argc, char *argv[]) {
}
// 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()) {
std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl;
return EXIT_FAILURE;
@@ -459,6 +485,8 @@ int main(int argc, char *argv[]) {
400, 300,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
DynamicWindowTitler window_titler(window);
SDL_GLContext gl_context = nullptr;
if(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).
if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) {
SDL_SetRelativeMouseMode(SDL_FALSE);
window_titler.set_mouse_is_captured(false);
}
// Capture ctrl+shift+d as a take-a-screenshot command.
@@ -734,6 +763,7 @@ int main(int argc, char *argv[]) {
case SDL_MOUSEBUTTONDOWN:
if(uses_mouse && !SDL_GetRelativeMouseMode()) {
SDL_SetRelativeMouseMode(SDL_TRUE);
window_titler.set_mouse_is_captured(true);
break;
}
case SDL_MOUSEBUTTONUP: {

View File

@@ -188,6 +188,12 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
case BusStep::Action::AdvancePrefetch:
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;
}
@@ -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,
// exit the STOP.
if(bus_interrupt_level_ > interrupt_level_) {
pending_interrupt_level_ = bus_interrupt_level_;
execution_state_ = ExecutionState::BeginInterrupt;
continue;
}
@@ -234,12 +241,6 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
continue;
case ExecutionState::BeginInterrupt:
#ifdef LOG_TRACE
// should_log = true;
if(should_log) {
printf("\n\nInterrupt\n\n");
}
#endif
active_program_ = nullptr;
active_micro_op_ = interrupt_micro_ops_;
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
// 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;
break;
}
@@ -1055,51 +1056,56 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
break;
}
int32_t dividend = int32_t(destination()->full);
int32_t divisor = s_extend16(source()->halves.low.full);
const int64_t quotient = int64_t(dividend) / int64_t(divisor);
const int32_t signed_dividend = int32_t(destination()->full);
const int32_t signed_divisor = s_extend16(source()->halves.low.full);
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.
if(dividend < 0) {
if(signed_dividend < 0) {
cycles_expended += 2; // An additional microycle applies if the dividend is negative.
}
// 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;
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.
zero_result_ = decltype(zero_result_)(divisor & 0xffff);
zero_result_ = decltype(zero_result_)(dividend);
negative_flag_ = zero_result_ & 0x8000;
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;
overflow_flag_ = 0;
// TODO: check sign rules here; am I necessarily giving the remainder the correct sign?
// (and, if not, am I counting it in the correct direction?)
const uint16_t remainder = uint16_t(dividend % divisor);
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;
// Algorithm here: there is a fixed cost per unset bit
// in the first 15 bits of the unsigned quotient.
auto positive_quotient_bits = ~quotient & 0xfffe;
convert_to_bit_count_16(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.
cycles_expended += 6;
// There's then no way to terminate the loop that isn't at least ten cycles long;
// 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;
} else if(dividend < 0) {
} else if(signed_dividend < 0) {
cycles_expended += 4;
}
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
// 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
// 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);
// acknowledge cycle, with all other bits set, including the low bit as
// a real 68000 uses the lower data strobe to collect the corresponding vector byte.
//
// 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
// prefetch; that's where the reading stream should pick up upon RTE.

View File

@@ -62,6 +62,11 @@ class ProcessorStorage {
bool bus_acknowledge_ = 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;
bool is_starting_interrupt_ = false;

View File

@@ -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.
*/
enum Register {
enum class Register {
ProgramCounter,
StackPointer,

View File

@@ -18,13 +18,12 @@ It currently contains emulations of the:
* Atari 2600;
* ColecoVision;
* Commodore Vic-20 (and Commodore 1540/1);
* Macintosh 512ke and Plus;
* MSX 1;
* Oric 1/Atmos;
* Sega Master System; and
* Sinclair ZX80/81.
Work is in progress on the Macintosh 512ke; it is presently very experimental.
## 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.

View File

@@ -21,6 +21,9 @@
namespace Storage {
namespace Disk {
/*!
Models a flopy disk.
*/
class Disk {
public:
virtual ~Disk() {}

View File

@@ -20,8 +20,8 @@ using namespace Storage::Disk;
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads):
Storage::TimedEventLoop(input_clock_rate),
rotational_multiplier_(60.0f / float(revolutions_per_minute)),
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());
std::default_random_engine randomiser(seed);

View File

@@ -138,7 +138,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
void set_event_delegate(EventDelegate *);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
/// Adds an activity observer; it'll be notified of disk activity.
/// The caller can specify whether to add an LED based on disk motor.

View 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);
}

View 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 */

View 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());
}

View 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 */

View 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"

View 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 */

View 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;
}

View 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 */

View 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();
}
}
}

View 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 */

View 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;
}

View 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 */

View 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---");
}

View File

@@ -8,6 +8,8 @@
#include "CSW.hpp"
#include "../../FileHolder.hpp"
#include <cassert>
using namespace Storage::Tape;

View File

@@ -10,7 +10,6 @@
#define CSW_hpp
#include "../Tape.hpp"
#include "../../FileHolder.hpp"
#include <string>
#include <vector>

View File

@@ -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.
*/
class BinaryTapePlayer: public TapePlayer {
class BinaryTapePlayer : public TapePlayer {
public:
BinaryTapePlayer(int input_clock_rate);
void set_motor_control(bool enabled);
@@ -145,7 +145,7 @@ class BinaryTapePlayer: public TapePlayer {
};
void set_delegate(Delegate *delegate);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
protected:
Delegate *delegate_ = nullptr;