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

Compare commits

...

344 Commits

Author SHA1 Message Date
Thomas Harte
ceb62ac7f9 Reenable the hardened runtime for macOS. 2021-07-03 13:41:32 -04:00
Thomas Harte
bda0756620 Merge pull request #959 from TomHarte/WriteCrash
Corrects buffer placement of decoded sectors.
2021-07-03 13:41:00 -04:00
Thomas Harte
6b47fb38c6 Corrects buffer placement of decoded sectors. 2021-07-03 13:36:01 -04:00
Thomas Harte
38bf8a06a7 Merge pull request #958 from TomHarte/EnterpriseFloatingBus
Makes a guess re: the Enterprise floating bus
2021-07-03 13:26:19 -04:00
Thomas Harte
196651d9aa Consolidates TODO. 2021-07-03 13:08:53 -04:00
Thomas Harte
6b46212a4e Deal with dangling TODO. 2021-07-03 13:07:41 -04:00
Thomas Harte
2a6fff2008 Takes a stab at what might happen if you read from Nick. 2021-07-03 13:06:07 -04:00
Thomas Harte
c5944efe50 Adds various method definitions. 2021-07-03 12:56:56 -04:00
Thomas Harte
f384370b18 Switch what's left of Enterprise logging to actual LOGs. 2021-07-03 12:50:46 -04:00
Thomas Harte
0c09275a9f Merge pull request #957 from TomHarte/EnterpriseTimingWindow
Correct various Enterprise timing discrepancies.
2021-07-03 12:47:29 -04:00
Thomas Harte
278671cdb9 Correct Nick interrupt prediction. 2021-07-03 00:05:13 -04:00
Thomas Harte
964d2d4fa4 Be consistent in expression of logic. 2021-07-03 00:00:00 -04:00
Thomas Harte
f371221dba Add a quick test of tone generator 1. 2021-07-02 23:57:11 -04:00
Thomas Harte
27b0579ec6 Avoid stack-error test case.
Also test that the interrupt is generated on the downward stroke.
2021-07-02 23:55:43 -04:00
Thomas Harte
283092cfbc With a unit test in aid, corrects some lingering TimedInterruptSource issues. 2021-07-02 23:41:19 -04:00
Thomas Harte
614953a222 Allows the low-pass filter to react to high-pass effects. 2021-07-02 22:36:35 -04:00
Thomas Harte
4fffb3cf19 Allow that final Z80 cycle to start anywhere in the first three of Nick's window of six. 2021-07-02 22:29:35 -04:00
Thomas Harte
850aa2b23a Merge pull request #956 from TomHarte/EnterpriseComposite
Adds Enterprise composite video option.
2021-07-02 22:22:09 -04:00
Thomas Harte
d715e5fd1d Expose composite/RGB option in Qt. 2021-07-02 21:51:48 -04:00
Thomas Harte
7826a26c7b Adds Enterprise composite video option.
While enabling more pixels on the left for RGB mode.
2021-07-02 21:42:09 -04:00
Thomas Harte
dc0a82cf9a Merge pull request #955 from TomHarte/FAT12
Adds a FAT12 parser.
2021-07-02 21:33:54 -04:00
Thomas Harte
2e60c81bd6 Enter :dir as a complete command. 2021-07-02 21:15:48 -04:00
Thomas Harte
763b9ba0ec Ensure the splash screen is skipped for self-booting disks. 2021-07-02 21:11:54 -04:00
Thomas Harte
bae8bb0c00 Gives the FAT parser responsibility for right trims. 2021-07-02 19:50:27 -04:00
Thomas Harte
bcf483fb7e Adds some basic loading command assistance. 2021-07-02 19:42:43 -04:00
Thomas Harte
a5b7d819a7 Correct FAT parser. 2021-07-02 19:28:13 -04:00
Thomas Harte
fe07a0b1d8 Starts to add a FAT[12] parser. 2021-07-02 18:56:43 -04:00
Thomas Harte
d9231e5d4a Merge pull request #954 from TomHarte/stddefRedux
The FIRFilter interface depends upon size_t.
2021-07-02 17:26:37 -04:00
Thomas Harte
b7aa1a1c84 The FIRFilter interface depends upon size_t. 2021-07-02 17:21:53 -04:00
Thomas Harte
32e144115d Add missing article, plus other minor corrections. 2021-07-02 11:03:14 -04:00
Thomas Harte
177cc96f49 Merge pull request #953 from TomHarte/stddef
Add missing stddef header where size_t is used.
2021-07-01 23:29:56 -04:00
Thomas Harte
51d98ef9ab Add missing stddef header where size_t is used. 2021-07-01 23:15:32 -04:00
Thomas Harte
2327c48cc4 Merge pull request #952 from TomHarte/EnterpriseTyper
Add typer support for the Enterprise.
2021-07-01 22:59:04 -04:00
Thomas Harte
742d44a532 Switch to an activity-based typing trigger; add a target loading command. 2021-07-01 22:53:23 -04:00
Thomas Harte
52b96db2b9 Correct syntax, mapping and inter-key timing. 2021-07-01 21:18:15 -04:00
Thomas Harte
0b9de78c38 Add typer support for the Enterprise. 2021-07-01 21:05:03 -04:00
Thomas Harte
2c28cb8c57 Merge pull request #951 from TomHarte/EnterpriseMention
Add Enterprise screenshots
2021-06-30 22:24:43 -04:00
Thomas Harte
483fe82e9d Add a second image, to even things out. 2021-06-30 22:23:26 -04:00
Thomas Harte
29492d6138 Add an Enterprise screenshot. 2021-06-30 22:18:29 -04:00
Thomas Harte
19310e32c4 Adds the Enterprise 64/128 as a bullet-pointed item.
No relevant screenshots yet.
2021-06-30 08:06:20 -04:00
Thomas Harte
c04a395499 Merge pull request #950 from TomHarte/Enterprise
Adds emulation of the Enterprise
2021-06-29 21:37:38 -04:00
Thomas Harte
1c424833a9 Correct EXDOS ROM name. 2021-06-29 21:04:53 -04:00
Thomas Harte
a46ff5590d Adds Enterprise new machine dialogue for Qt. 2021-06-29 21:04:17 -04:00
Thomas Harte
ab059b63fd Add Enterprise to Qt project file. 2021-06-29 20:36:28 -04:00
Thomas Harte
3d8fc9952d Remove dead TODO, correct for overflow position. 2021-06-29 15:44:02 -04:00
Thomas Harte
8ce8fbd977 Provide correct input when one of the tone generators is the interrupt source. 2021-06-29 15:41:08 -04:00
Thomas Harte
7f08218b28 The Nick interrupt input also seems to be a live poll, not a retrieval of the mask.
This corrects the two pieces of software I knew not to be working.
2021-06-28 22:10:11 -04:00
Thomas Harte
2c139ad931 Adds some notes to self.
I think I'm starting to find enough information to handle tapes.
2021-06-28 22:03:06 -04:00
Thomas Harte
1119779c8b Ensure EXDOS card is completely disabled if no FDC is present. 2021-06-28 21:47:53 -04:00
Thomas Harte
5351ac560f Ensure the motor goes off for unselected drives. 2021-06-28 21:40:12 -04:00
Thomas Harte
49f0ab0f15 Add note to self.
Although I still think there may be some issue lurking.
2021-06-28 21:31:55 -04:00
Thomas Harte
a5c57e777e VRES appears to work negatively in attribute mode too. 2021-06-28 21:24:13 -04:00
Thomas Harte
3c59042388 Fixes initial state for 1kHz. 2021-06-28 21:08:41 -04:00
Thomas Harte
919e211bc4 Reduces number of interrupt-related sequence points. 2021-06-28 19:30:12 -04:00
Thomas Harte
daa0737ce4 Ensure addresses tick upwards even during sync/burst; correct 2/4/8bpp character sizing. 2021-06-28 19:00:51 -04:00
Thomas Harte
36805cb120 Correct tone channel interrupts, remove dead warning. 2021-06-27 23:21:00 -04:00
Thomas Harte
7de69e9874 Makes an attempt to round out the timed interrupts. 2021-06-27 23:09:11 -04:00
Thomas Harte
b93575bbcc Spots that b0 and b2 of 0xb4 are 'dividers', not enables. 2021-06-27 22:33:20 -04:00
Thomas Harte
116e0f0105 Interupts 1kHz and 50Hz interrupts, while edging towards tone generator interrupts. 2021-06-27 22:08:38 -04:00
Thomas Harte
e4a650aaff Implements the 1Hz interrupt. 2021-06-27 21:47:21 -04:00
Thomas Harte
b5312b9ba0 get_interrupt_line can be const. 2021-06-27 21:37:11 -04:00
Thomas Harte
6afee7bb9b Captures appropriate fields.
No action yet.
2021-06-27 21:36:55 -04:00
Thomas Harte
5729e6e13a Corrects potential JustInTimeActor overflow. 2021-06-27 21:36:41 -04:00
Thomas Harte
2f53b105bb The Enterprise is now an Activity::Source; also sketches out the owner of Dave's timed interrupt logic. 2021-06-27 21:02:04 -04:00
Thomas Harte
b698056f78 Correct divisor. 2021-06-27 17:39:13 -04:00
Thomas Harte
95c906f03d Takes a serious shot at back_map. 2021-06-27 17:36:25 -04:00
Thomas Harte
be19fa9dde This mapping needs to know where it will occur. 2021-06-27 17:30:09 -04:00
Thomas Harte
81e9ba5608 This is correct from the Enterprise's side of things, I think.
I just need to complete the missing part of JustInTimeActor. After I do some empirical testing of this.
2021-06-27 17:24:21 -04:00
Thomas Harte
f2d7b9f6a9 Apply a crop, allow time until Z80 slot to be a future-based query. 2021-06-27 17:13:07 -04:00
Thomas Harte
1ea034310a Edge up very close to video waits.
I just need to implement back conversions that include marginal phase over in the JustInTimeActor.
2021-06-27 16:28:01 -04:00
Thomas Harte
bdcab447f9 Add a further accessor. 2021-06-27 16:27:26 -04:00
Thomas Harte
10bf6744aa Correct typo. 2021-06-27 16:26:55 -04:00
Thomas Harte
895d98e266 Implements out-of-video-area pauses. 2021-06-27 16:11:22 -04:00
Thomas Harte
903e343895 Attempts to complete Dave's audio duties. 2021-06-27 14:06:49 -04:00
Thomas Harte
f8b7c59616 Corrects tone frequency. 2021-06-26 23:51:43 -04:00
Thomas Harte
fcd267a3f9 Starts implementing noise. 2021-06-26 23:48:53 -04:00
Thomas Harte
f8bb66d2a0 Attempts an essentially-complete implementation of tone channels. 2021-06-26 23:39:59 -04:00
Thomas Harte
90782d3c27 Corrects for IntType != int. 2021-06-26 23:39:37 -04:00
Thomas Harte
f2336d2efc I think reloads occur after overflow, not before. 2021-06-26 23:16:00 -04:00
Thomas Harte
c2d093fa3c Respect user volume input.
Basic tones are now present. Neato!
2021-06-24 22:27:02 -04:00
Thomas Harte
1a97cc8a91 Start making some effort towards audio generation. 2021-06-24 22:21:01 -04:00
Thomas Harte
c34a548fa0 Ensure character pixel reads can't go out of bounds. 2021-06-24 22:19:50 -04:00
Thomas Harte
d1b89392a2 Improve exposiiton. 2021-06-24 22:18:31 -04:00
Thomas Harte
ed734754e5 Adds a through route for IMG files. 2021-06-24 21:04:21 -04:00
Thomas Harte
520c3c9218 Corrects colour deserialisation.
Long story short: the documentation I'm reading inexplicably lists the bits in reverse order. Luckily, a lot of the other documentation doesn't.
2021-06-24 20:59:04 -04:00
Thomas Harte
9230cf1726 Corrects bug when left_ or right_margin_ = 0. 2021-06-24 20:28:50 -04:00
Thomas Harte
6e616972a5 Better binds margin tests to window movements; simplifies line parameter addressing. 2021-06-24 18:55:15 -04:00
Thomas Harte
f98888824d Switches to an overt active/inactive state machine. 2021-06-24 18:34:21 -04:00
Thomas Harte
6c8b23e708 Alters 4bpp mapping; adds character mode 4bpp and 8bpp. 2021-06-23 19:35:47 -04:00
Thomas Harte
2c2bb3765f Withdraws the EPDOS option.
At least for now; it's something to worry about later.
2021-06-23 19:32:34 -04:00
Thomas Harte
0d165740ea Honours memory size request. 2021-06-22 21:48:55 -04:00
Thomas Harte
88f0f2b623 Adds to the macOS UI and wires through all Enterprise options. 2021-06-22 21:39:07 -04:00
Thomas Harte
0afa143375 Add missing include. 2021-06-22 21:31:46 -04:00
Thomas Harte
8319aca351 Correct syntax errors. 2021-06-22 20:50:03 -04:00
Thomas Harte
a66734883a Starts sketching out Dave. 2021-06-22 19:33:41 -04:00
Thomas Harte
d2ab0dd839 Adds a quick way to get the compiler to pick an integral type. 2021-06-22 19:33:29 -04:00
Thomas Harte
2574407afb Relocates MinIntTypeValue to Numeric. 2021-06-22 19:33:02 -04:00
Thomas Harte
83a54fd6d2 Use the FAT12 boot sector to determine geometry. 2021-06-22 06:54:17 -04:00
Thomas Harte
e062780968 Extends back to 128kb and stops halting on unrecognised ports. 2021-06-22 06:15:42 -04:00
Thomas Harte
3acd0be1f7 Copy and paste 2bpp character support. 2021-06-21 23:27:13 -04:00
Thomas Harte
69c0734975 WD1770: switch motor on even if spin-up is disabled. 2021-06-21 23:26:55 -04:00
Thomas Harte
c1678d7be7 Corrects exposition and transmission of drive selection.
What a klutz I've been.
2021-06-21 22:56:25 -04:00
Thomas Harte
117f9a9794 Adds notes on intended meaning of status register. 2021-06-21 07:31:58 -04:00
Thomas Harte
b49cc407c6 Adds some guesses as to the EXDos expansion.
... with plenty left to do.
2021-06-20 22:30:27 -04:00
Thomas Harte
954386f1cc Creates a shell for the disk-drive add-on card. 2021-06-20 20:50:23 -04:00
Thomas Harte
d7ff6bd04d Adds necessary declarations to install a DOS ROM. 2021-06-20 20:30:54 -04:00
Thomas Harte
6025516f9f Ensure addresses increment even when there's no target for pixels. 2021-06-20 14:31:02 -04:00
Thomas Harte
d8b9cdf7a2 Correct multiplier. 2021-06-20 14:25:37 -04:00
Thomas Harte
09dbff39f2 Also map keypad to F keys.
This is a pragmatic and arguably Apple-centric decision. But also it's fairly arbitrary, as the Enterprise doesn't have a number pad.
2021-06-20 14:25:28 -04:00
Thomas Harte
2fe15a6168 Switch to idealised Nick clock rate. 2021-06-20 14:21:56 -04:00
Thomas Harte
07dc26f8fa Adds TODO to resolve screen jumping. 2021-06-19 23:41:29 -04:00
Thomas Harte
a08d65b1ff Adds IMG -> Enterprise connection.
Albeit still without an Enterprise static analyser.
2021-06-19 23:16:33 -04:00
Thomas Harte
199621db08 Observes that the actual guess here is MS-DOS-style. 2021-06-19 23:11:51 -04:00
Thomas Harte
0e1e8c7faa Attempts to support the panoply of EXOS and BASIC versions. 2021-06-19 22:59:09 -04:00
Thomas Harte
42a98e1676 Fix composition with empty nodes. 2021-06-19 22:13:17 -04:00
Thomas Harte
23e26e0333 Attempts to complete handling of VRES. 2021-06-19 22:00:19 -04:00
Thomas Harte
fadb04f3f3 Attempts to implement LSBALT and MSBALT. 2021-06-19 21:57:26 -04:00
Thomas Harte
4968ccf46d Corrects attributed mode. 2021-06-19 13:08:14 -04:00
Thomas Harte
1dcac304d3 Implements the ALTIND bits and attempts ATTR mode. 2021-06-19 13:04:18 -04:00
Thomas Harte
1651efe4fc Ensures all keys are initially unpressed. 2021-06-19 13:03:31 -04:00
Thomas Harte
8f24aed43e Slightly reduces logging. 2021-06-18 23:17:44 -04:00
Thomas Harte
a381374e31 Drops back down to 64kb. 2021-06-18 23:14:44 -04:00
Thomas Harte
9411c37d23 Fleshes out the keyboard map. 2021-06-18 23:14:35 -04:00
Thomas Harte
6af6f21868 Attempts to implement interrupt latches and clears. 2021-06-18 22:59:41 -04:00
Thomas Harte
9a0022cfcb Removes temporary work. 2021-06-18 18:44:07 -04:00
Thomas Harte
266310d9c2 Fixes automatic flushing for non-1/1-clocked actors. 2021-06-18 18:43:08 -04:00
Thomas Harte
fbf1adef05 Introduces unit test and thereby seemingly fixes get_next_sequence_point.
There's still improper output in the actual machine though, so maybe something else is afoot?
2021-06-18 17:44:17 -04:00
Thomas Harte
311ddfb05a Add note to self for tomorrow. 2021-06-17 22:34:52 -04:00
Thomas Harte
2fd8a8aa66 Begins addition of interrupt feedback from Nick.
Also fixes clock rate. Though clearly get_next_sequence_point isn't quite right yet.
2021-06-17 22:30:24 -04:00
Thomas Harte
0c3e9dca28 Adds some basic keyboard inputs.
I think the next thing required is interrupts though.
2021-06-17 20:47:56 -04:00
Thomas Harte
c331d15429 Makes space to allow for 64kb EXOS ROMs.
I think some of the later ROMs have a more thorough memory test, which might provide better detail on whatever's going on here.
2021-06-16 22:25:00 -04:00
Thomas Harte
4414e96710 Adds enough text mode for now.
Discovered: a memory fault is being reported at startup.
2021-06-16 21:42:20 -04:00
Thomas Harte
7161783a4f Adds lpixel output. 2021-06-16 21:16:26 -04:00
Thomas Harte
cbac48da86 Attempts full run at pixel mode. 2021-06-16 20:43:22 -04:00
Thomas Harte
d9142d5427 Adjusts declared scale, accurately to communicate pixel clock. 2021-06-15 22:39:44 -04:00
Thomas Harte
e5e988b28f Adds an incorrect assumed-pixel-mode serialiser.
This actually shows something a bit like the Enterprise boot logo.
2021-06-15 22:31:50 -04:00
Thomas Harte
e94e051c87 Adds an allocator for pixels. 2021-06-15 22:03:41 -04:00
Thomas Harte
5fc91effb5 Corrects top border. 2021-06-15 21:48:11 -04:00
Thomas Harte
6c9dacbe89 Stabilises display, albeit that top border mode now appears to be off. 2021-06-15 21:31:07 -04:00
Thomas Harte
6a7eb832cc Introduces almost-stable block-level frame generation. 2021-06-15 20:55:58 -04:00
Thomas Harte
60cf8486bb Makes a genuine attempt at real line counting.
No output yet though.
2021-06-15 18:57:14 -04:00
Thomas Harte
90b8163d54 Add exposition. 2021-06-15 17:44:39 -04:00
Thomas Harte
a1e4389f63 Give Nick some RAM to inspect and just enough sense to know when it should reload its line parameter table. 2021-06-15 17:43:13 -04:00
Thomas Harte
440b11708b Adds an unused CRT. 2021-06-14 23:11:48 -04:00
Thomas Harte
f90dce5c54 Take a guess at Nick timing. 2021-06-14 22:56:26 -04:00
Thomas Harte
606c7709cf Shims in enough to get the Z80 to run perpetually.
Notably I don't actually currently know how the interrupt registers work, but getting some sort of display running might be the first order of business.
2021-06-14 22:28:31 -04:00
Thomas Harte
1d1e6d1820 Adds a shell of a Nick. 2021-06-14 22:19:25 -04:00
Thomas Harte
3eb4dd74a2 Exposes memory control.
Machine now runs as far as trying to interact with Nick.
2021-06-14 21:45:12 -04:00
Thomas Harte
853914480c Revised guess; there's a jump to C02E almost immediately. 2021-06-14 21:40:19 -04:00
Thomas Harte
fe04410681 Merge branch 'master' into Enterprise 2021-06-14 21:30:49 -04:00
Thomas Harte
1f686c4e6b Add missing AppleIIOptionsPanel class. 2021-06-14 21:30:30 -04:00
Thomas Harte
2a2ac1227b Makes first attempt at giving the Z80 something to do. 2021-06-14 21:29:56 -04:00
Thomas Harte
b5340c8f74 Correct syntax. 2021-06-14 21:17:35 -04:00
Thomas Harte
196c4dcdd9 Adds an appropriate ROM request. 2021-06-14 21:17:09 -04:00
Thomas Harte
c5a86f0ef7 Add Enterprise parts of the static analyser. 2021-06-14 21:11:06 -04:00
Thomas Harte
88f2a2940b Add Enterprise source paths. 2021-06-14 21:07:35 -04:00
Thomas Harte
26b019a4d4 Removes assumption that all machines produce audio. 2021-06-14 21:02:55 -04:00
Thomas Harte
5f7b3ae313 Adds bare minimum to get a non-functional machine. 2021-06-14 21:02:40 -04:00
Thomas Harte
61c127ed2e Adds Enterprise as a File -> New... machine.
And, similarly, exposes it for the route used by SDL.
2021-06-14 20:55:39 -04:00
Thomas Harte
333981e2a7 Merge branch 'master' into Enterprise 2021-06-13 22:22:44 -04:00
Thomas Harte
423fbc9ac7 Merge pull request #949 from TomHarte/QtSnapshotDragAndDrop
Adds drag-and-drop snapshot support for Qt.
2021-06-13 21:48:42 -04:00
Thomas Harte
1c1719e561 Adds drag-and-drop snapshot support for Qt. 2021-06-13 21:41:20 -04:00
Thomas Harte
56c30e1651 Merge pull request #948 from TomHarte/QtAspectRatio
Ensures Apple II square pixels work correctly under OpenGL
2021-06-13 21:23:28 -04:00
Thomas Harte
1ea4130035 Avoid OpenGL restretching bug. 2021-06-13 19:46:47 -04:00
Thomas Harte
57713d63fa Avoids regression of selected tab upon app restart. 2021-06-13 19:38:56 -04:00
Thomas Harte
d18a537509 Fiddles with the preprocessor to make kiosk mode match other OSes even on macOS. 2021-06-13 19:28:05 -04:00
Thomas Harte
8e0a6df03b Merge branch 'master' into Enterprise 2021-06-10 21:41:57 -04:00
Thomas Harte
95a52a9f62 Merge pull request #947 from TomHarte/AppleIISquarePixels
Adds optional square pixel output for the Apple II
2021-06-08 18:04:08 -04:00
Thomas Harte
ae2993625c Add missing header. 2021-06-08 17:54:30 -04:00
Thomas Harte
0982141442 Corrects many minor errors. 2021-06-08 17:52:39 -04:00
Thomas Harte
85fab2abc4 Takes a swing at adding a square pixels toggle for Qt. 2021-06-08 17:37:46 -04:00
Thomas Harte
de3b37799c Switches to a static_cast. No need for reflection here. 2021-06-08 17:37:28 -04:00
Thomas Harte
70851f3b2d Resolve misplacement. 2021-06-07 21:43:26 -04:00
Thomas Harte
462bbf2e40 Exposes square pixels option on macOS. 2021-06-07 21:21:45 -04:00
Thomas Harte
778b9ef683 Ensures set_square_pixels is exposed, works around OpenGL aspect ratio bug. 2021-06-07 20:41:02 -04:00
Thomas Harte
96e7eb1bed Adds a use-square-pixels option for the Apple II. 2021-06-07 20:16:01 -04:00
Thomas Harte
05671f3553 Merge pull request #946 from TomHarte/OptionalOricColourROM
Introduces a new grammar for ROM requests.
2021-06-06 22:47:46 -04:00
Thomas Harte
6e4832f999 Ensures Oric honours absence of the colour ROM. 2021-06-06 22:43:53 -04:00
Thomas Harte
54e3332673 Ensure optionals appear at the end of any ROM request list. 2021-06-06 22:36:26 -04:00
Thomas Harte
6c559d7556 Fix lead-in text. 2021-06-06 22:02:11 -04:00
Thomas Harte
9165a85484 Correct wstring conversion. 2021-06-06 21:58:38 -04:00
Thomas Harte
98ada2588a Resolve name confusion. 2021-06-06 21:51:51 -04:00
Thomas Harte
43f686c22d Correct return type and map insertion. 2021-06-06 21:44:37 -04:00
Thomas Harte
4a2673d757 Make a prima facie attempt to adapt the Qt build. 2021-06-06 20:47:25 -04:00
Thomas Harte
f27e331462 Updates autotests to new RomFetcher world. 2021-06-06 20:34:55 -04:00
Thomas Harte
dd64aef910 Improves request construction and improves descriptions. 2021-06-06 20:25:26 -04:00
Thomas Harte
95971f39f1 Reintroduces full messaging to macOS. 2021-06-06 20:02:13 -04:00
Thomas Harte
83beb3c0e6 Introduces slightly-less manual ROM::Request::visit. 2021-06-06 18:28:02 -04:00
Thomas Harte
76335e5cf2 Factors out and slightly generalises textual descriptions of ROM::Descriptions. 2021-06-06 18:15:00 -04:00
Thomas Harte
4494320238 Corrects the macOS Swift side of things. 2021-06-06 14:56:43 -04:00
Thomas Harte
5acd97c860 Puts enough in place for a GUI-led installation process.
... and provides a lot of the Objective-C wiring necessary to expose that to Swift.
2021-06-06 14:24:38 -04:00
Thomas Harte
b0f551c307 Ensures only _missing_ ROMs are reported. 2021-06-05 21:09:35 -04:00
Thomas Harte
b6b3d845a3 Correct Apple IIe and Enhanced IIe startup. 2021-06-04 22:48:08 -04:00
Thomas Harte
505d84f336 Corrects Amstrad 664 and 6128 ROM collection. 2021-06-04 22:43:26 -04:00
Thomas Harte
1d5144b912 Correct no-interrupt signal. 2021-06-04 22:38:07 -04:00
Thomas Harte
deff91e460 Correct Electron name mapping. 2021-06-04 22:25:11 -04:00
Thomas Harte
afd8dc0915 Nudge just far enough to be able to launch again under macOS. 2021-06-04 22:24:31 -04:00
Thomas Harte
fbee74e1fe Avoids storing or printing a CRC if none is known. 2021-06-04 22:03:08 -04:00
Thomas Harte
ccd82591aa Reinstates SDL error message; adds expansion of ~. 2021-06-04 21:53:56 -04:00
Thomas Harte
64931e476d Completes transcription of ROM details with the Oric and MSX. 2021-06-04 19:50:49 -04:00
Thomas Harte
604a715a49 Transcribes the Spectrum, Electron, Master System and Vic-20 ROMs. 2021-06-04 19:45:47 -04:00
Thomas Harte
24757ef20c Transcribes the Macintosh, Atari ST, ColecoVision and ZX80/81 ROMs. 2021-06-04 19:24:57 -04:00
Thomas Harte
e36cc9e777 Transcribes the Apple II ROM descriptions. 2021-06-04 19:19:55 -04:00
Thomas Harte
2e999889bd Attempts to implement tree construction. 2021-06-04 19:03:07 -04:00
Thomas Harte
f4db4c3a73 Implements ROM::Request::validate.
It now also validates ROM sizes, so can no longer take a const Map.
2021-06-04 18:54:50 -04:00
Thomas Harte
d923fe72c0 Resolves various ROM selection warnings. 2021-06-03 22:46:47 -04:00
Thomas Harte
f05cdd5e34 With large swathes of implementation missing, compiles. 2021-06-03 22:39:18 -04:00
Thomas Harte
f9954619d4 Add missing header file. 2021-06-03 22:28:30 -04:00
Thomas Harte
0aa8c3c40d For SDL at least, advances to failed linking.
... and with error reporting currently AWOL.
2021-06-03 22:22:56 -04:00
Thomas Harte
a30eeaab6a Starts to introduce a new grammar for ROM requests.
They can be optional, and chained together in AND or OR combinations. A central catalogue knows the definitions of all ROMs.
2021-06-03 21:55:59 -04:00
Thomas Harte
3858e79579 Merge pull request #944 from TomHarte/SDLErrorReporting
Improve SDL failed-ROM reporting.
2021-05-30 19:50:53 -04:00
Thomas Harte
b4a5fa33b0 Improve SDL failed-ROM reporting.
Specifically to include all paths tried, and not use the plural for 'crc32' when only one is present.
2021-05-30 19:40:29 -04:00
Thomas Harte
2a6e9c5e8a Add readme for Enterprise ROM names. 2021-05-30 19:28:26 -04:00
Thomas Harte
488c2aed51 Merge pull request #939 from TomHarte/DragAndDropState
Accept insertion of state snapshots into existing windows
2021-05-16 20:47:36 -04:00
Thomas Harte
5483f979dc Merge branch 'master' into DragAndDropState 2021-05-16 20:42:44 -04:00
Thomas Harte
ea11f3826a Merge pull request #941 from TomHarte/LargeDSK
Adds support for Macintosh SCSI drive images.
2021-05-13 19:17:18 -04:00
Thomas Harte
ceae81a332 Add missing header. 2021-05-13 19:11:19 -04:00
Thomas Harte
50ea56e908 Adds support for Macintosh SCSI device images.
This is now in addition to the single-partition images previously supported.
2021-05-13 19:06:00 -04:00
Thomas Harte
bfb2f79cff That's two learning curves. 2021-05-10 21:33:40 -04:00
Thomas Harte
8268e8ee4c Ensures music survives a machine switch. 2021-05-08 20:46:17 -04:00
Thomas Harte
cb31e22f59 Merge branch 'master' into DragAndDropState 2021-05-08 20:41:44 -04:00
Thomas Harte
6752f4fd73 Merge pull request #940 from TomHarte/TighterTapeStop
Tightens automatic tape control timing.
2021-05-08 18:21:14 -04:00
Thomas Harte
22c31e4f55 Tightens automatic tape control timing. 2021-05-08 17:34:59 -04:00
Thomas Harte
c2ff64c1e0 Removes dangling OpenGL reference, attempts to ensure audio handover upon a machine change. 2021-05-08 14:42:43 -04:00
Thomas Harte
4db792591a macOS: ensure activity and options panels change upon a drag-and-drop state. 2021-05-08 14:35:57 -04:00
Thomas Harte
1290a8e32b SDL: Ensures joysticks, mouse, LEDs, etc, all update to a dragged state snapshot. 2021-05-08 13:30:07 -04:00
Thomas Harte
8ae38991b0 Factor out machine wiring. 2021-05-08 13:15:18 -04:00
Thomas Harte
6d40549c0c Merge branch 'master' into DragAndDropState 2021-05-07 21:56:36 -04:00
Thomas Harte
93d5c9a3c7 Tighten wording further. 2021-05-07 18:55:15 -04:00
Thomas Harte
9af6c0b37a Improves comment. 2021-05-06 12:57:32 -04:00
Thomas Harte
7e3528c692 Shunt the tech/URL stuff below the headline feature list. 2021-05-06 09:44:40 -04:00
Thomas Harte
41f2fc51be Clarify second sentence.
As per discussion at https://www.retrogameboards.com/t/clock-signal-a-multi-platform-emulator-that-focuses-on-a-better-user-experience/2375 — the previous could be read as "no emulator | or per-emulated-machine learning curve". But there is an emulator.
2021-05-06 09:43:19 -04:00
Thomas Harte
11228dc265 Merge pull request #937 from TomHarte/XKeySyms
Eliminate magic constants in Qt/X11 keyboard code.
2021-05-05 22:21:31 -04:00
Thomas Harte
ef50967793 Limit X11 linkage to Linux. 2021-05-05 22:17:24 -04:00
Thomas Harte
5f6c08b7e0 Avoid partial struct instantiation. 2021-05-05 22:00:50 -04:00
Thomas Harte
6cb23ec5be Tidy up and comment. 2021-05-05 21:58:54 -04:00
Thomas Harte
1bae70bcf8 Correct capitalisation. 2021-05-05 21:49:01 -04:00
Thomas Harte
9820591ba4 Corrects enum references. 2021-05-05 21:46:34 -04:00
Thomas Harte
77071b3c69 Adds KeySym -> key lookup. 2021-05-05 21:41:59 -04:00
Thomas Harte
335e839b31 Wrangles a single working call to XKeysymToKeycode. 2021-05-05 21:35:08 -04:00
Thomas Harte
6fe947b8b9 Fix class name, add constructor. 2021-05-05 19:17:23 -04:00
Thomas Harte
22b29e77a7 Add keyboard.cpp/h to the Qt project. 2021-05-05 19:06:25 -04:00
Thomas Harte
4858cfce6b Starts to factor out the keyboard mapper.
The more easily to clarify as to #includes, etc, and to allow for a relevant constructor.
2021-05-05 18:56:10 -04:00
Thomas Harte
8da3e91f5e Merge branch 'master' into XKeySyms 2021-05-03 22:23:55 -04:00
Thomas Harte
012235bfeb Merge pull request #936 from TomHarte/Style
Correct minor style errors.
2021-05-03 22:23:27 -04:00
Thomas Harte
052e284c33 Add overt fallthrough. 2021-05-03 22:17:43 -04:00
Thomas Harte
32e3dd71b1 Be overt in empty std::string construction. 2021-05-03 22:17:32 -04:00
Thomas Harte
95f4272919 Make sure size_t is visible. 2021-05-03 22:17:25 -04:00
Thomas Harte
00679b6135 t may be unused, per the if constexpr. 2021-05-03 22:17:19 -04:00
Thomas Harte
2c18bb4508 Make it overt that this can't return without a value. 2021-05-03 22:17:12 -04:00
Thomas Harte
0cf1c9040a Add missing fallthrough declaration. 2021-05-03 22:17:06 -04:00
Thomas Harte
9196341482 Retrenches: it seems nativeVirtualKey does what I want.
Hooray!
2021-05-03 21:45:53 -04:00
Thomas Harte
685140a4c2 Correct Qt -> QT. 2021-05-03 21:18:14 -04:00
Thomas Harte
1465b0ee4d Shunt X11 code to bottom of file, to avoid #include interference. 2021-05-03 21:15:20 -04:00
Thomas Harte
0bf6b765d3 Further namespace/name corrections. 2021-05-03 21:11:47 -04:00
Thomas Harte
4774676e2a Correct keypad symbols, push X11 into a namespace. 2021-05-03 21:09:01 -04:00
Thomas Harte
9c29655da2 Add x11extras as per use of <QX11Info>. 2021-05-03 20:43:22 -04:00
Thomas Harte
c8ab18f2b6 Add overt fallthrough. 2021-05-03 20:38:50 -04:00
Thomas Harte
8ebce466db Be overt in empty std::string construction. 2021-05-03 20:35:23 -04:00
Thomas Harte
1b39b17125 Make sure size_t is visible. 2021-05-03 20:33:25 -04:00
Thomas Harte
5a46853075 t may be unused, per the if constexpr. 2021-05-03 20:32:16 -04:00
Thomas Harte
48ad4d4c4c Make it overt that this can't return without a value. 2021-05-03 20:31:39 -04:00
Thomas Harte
056a036712 Add missing fallthrough declaration. 2021-05-03 20:31:13 -04:00
Thomas Harte
70eaa79108 Makes an attempt to use X11 KeySyms.
Rather than hard-coding a mapping.
2021-05-03 18:51:58 -04:00
Thomas Harte
20c814a4dd Factors out boilerplate around full-device sector images. 2021-05-01 21:10:46 -04:00
Thomas Harte
6a052e1900 Starts working on SDL drag-and-drop support for snapshots. 2021-04-30 22:56:13 -04:00
Thomas Harte
cecdf8584a Ensures proper propagation of will_change_owner. 2021-04-30 22:51:26 -04:00
Thomas Harte
4758bc8615 Attempts to support insertion of states into existing windows. 2021-04-30 21:37:41 -04:00
Thomas Harte
c906dc3c0a Merge pull request #935 from TomHarte/OricJoystick
Adds Altai-style joystick support for the Oric.
2021-04-29 20:15:42 -04:00
Thomas Harte
d1dcb41b6f Adds Altai-style joystick support. 2021-04-29 18:29:29 -04:00
Thomas Harte
96ac86a757 Merge pull request #934 from TomHarte/OricTapes
Relaxes Oric .tap signature check.
2021-04-29 18:14:36 -04:00
Thomas Harte
4919786825 Relaxes Oric .tap signature check. 2021-04-29 18:00:02 -04:00
Thomas Harte
24b4185714 Merge pull request #933 from TomHarte/SpectrumJoystick
Adds ZX Spectrum joystick support.
2021-04-28 21:08:36 -04:00
Thomas Harte
ad10d0037a Inverts the Game Controller Framework value of the y axis. 2021-04-28 20:31:35 -04:00
Thomas Harte
b6554c8255 Adds joystick support. 2021-04-28 20:19:01 -04:00
Thomas Harte
01dc83d0d6 Merge pull request #932 from MaddTheSane/xcodemaintenance
Xcode maintenance.
2021-04-27 19:53:51 -04:00
C.W. Betts
2fd08789ab Xcode maintenance. 2021-04-27 12:50:26 -06:00
Thomas Harte
bc9e529995 Merge pull request #931 from TomHarte/FieldName
This field is counted in half-cycles.
2021-04-26 21:33:38 -04:00
Thomas Harte
708c24cc57 This field is counted in half-cycles. 2021-04-26 21:20:32 -04:00
Thomas Harte
7fb3048257 Update AllDisk and AllTape. 2021-04-26 21:04:25 -04:00
Thomas Harte
9319f0525a Merge pull request #930 from TomHarte/SZX
Adds SZX support.
2021-04-26 20:57:06 -04:00
Thomas Harte
b7a62e0121 Adds SZX support.
Tweaking exposed Spectrum state object as relevant.
2021-04-26 20:47:28 -04:00
Thomas Harte
bd5dd9b9a3 Merge pull request #929 from TomHarte/SpectrumSnapshots
Adds loading of state snapshots for the ZX Spectrum
2021-04-26 17:44:02 -04:00
Thomas Harte
3348167c46 Ensures AY registers are conveyed. 2021-04-26 17:39:11 -04:00
Thomas Harte
700c505974 Ensures the ZX Spectrum properly reports its display type. 2021-04-25 21:16:22 -04:00
Thomas Harte
d403036d86 Reduce bounce at Spectrum startup. 2021-04-25 20:56:57 -04:00
Thomas Harte
5e08d7db39 Carries through paging state; avoids file rereads. 2021-04-25 20:46:49 -04:00
Thomas Harte
c34cb310a8 Switches to more straightforward handler for .z80-style compression. 2021-04-25 18:07:36 -04:00
Thomas Harte
8d86aa69bc Adds an assert to check handling of compressed data. 2021-04-25 18:02:31 -04:00
Thomas Harte
cc41ccc5f1 Adds RAM deserialisation. 2021-04-25 17:55:52 -04:00
Thomas Harte
e6252fe0ed Sneaks up towards loading RAM. 2021-04-25 17:34:43 -04:00
Thomas Harte
03577de675 Adds an empty vessel for .z80 support. 2021-04-25 16:54:34 -04:00
Thomas Harte
205518ba75 Switch to more efficient copy. 2021-04-25 16:51:07 -04:00
Thomas Harte
2510064218 Completes state object.
Subject to not yet dealing with last_fetches_ and last_contended_access_ correctly. Thought required.
2021-04-25 14:20:40 -04:00
Thomas Harte
0ef2806970 Adds just enough to ensure that border state gets through. 2021-04-25 14:16:35 -04:00
Thomas Harte
d80f03e369 Corrects longstanding deviation from naming convention. 2021-04-25 14:11:36 -04:00
Thomas Harte
fd271d920b Adds capture and forwarding of border colour. 2021-04-25 14:00:12 -04:00
Thomas Harte
2bbf8bc9fa Ensures 16/48kb snapshots are properly copied into place. 2021-04-25 13:27:11 -04:00
Thomas Harte
9b65d56ed0 Resolves potential flaw in POPping here. 2021-04-25 13:26:53 -04:00
Thomas Harte
a5098a60ec Attempts to get in-SNA software to start. 2021-04-25 13:18:26 -04:00
Thomas Harte
0ebd900e40 Baby steps: apply Z80 state.
As far as it currently is. Since SNA is leaving the PC at the default of 0x0000, this currently has no visible effect.
2021-04-25 13:03:24 -04:00
Thomas Harte
7aeb17ac92 Corrects HeaderDoc/etc directive. 2021-04-25 13:01:23 -04:00
Thomas Harte
cc78bfb229 Forwards most of the Z80 state. 2021-04-25 13:00:43 -04:00
Thomas Harte
485c2a866c Without yet a struct for Spectrum states, at least checks general wiring. 2021-04-24 23:38:00 -04:00
Thomas Harte
5b419ca5bf Add State folder to Scons and Qt projects. 2021-04-24 23:25:08 -04:00
Thomas Harte
14ae579fca Add further note to future self. 2021-04-24 23:19:41 -04:00
Thomas Harte
1c2ea0d7fe unique_ptr makes more sense here. 2021-04-24 23:19:30 -04:00
Thomas Harte
e7a9ae18a1 Introduce further default state. 2021-04-24 23:18:00 -04:00
Thomas Harte
d61f478a39 Basic sketch for state snapshots: an extra field on Target.
I think it doesn't make sense for states to own a target as that complicates the concept of Media. Plus they're distinct because it makes sense to have only one per Target. Let's see how this pans out.
2021-04-24 23:17:47 -04:00
Thomas Harte
9cc747b3e2 Resolves potential source of errors: specifying incorrect table size.
(Having made exactly this mistake with the ZX Spectrum)
2021-04-24 12:10:28 -04:00
Thomas Harte
2f223f7db2 Spectrum emulation is no longer +2a/+3 specific. 2021-04-23 22:55:54 -04:00
Thomas Harte
17f11a3be3 Merge pull request #928 from TomHarte/ContentionTests
Add timing tests, fix +3 discrepancy.
2021-04-23 22:54:34 -04:00
Thomas Harte
37dcf61130 Add timing tests, fix +3 discrepancy. 2021-04-23 22:29:57 -04:00
Thomas Harte
856ebfacca Merge pull request #927 from TomHarte/SimplifiedTiming
Moves horizontal sync on the 48kb.
2021-04-21 19:50:40 -04:00
Thomas Harte
9731fdd33b Moves horizontal sync on the 48kb. 2021-04-21 19:46:44 -04:00
Thomas Harte
5ea605ccf7 Merge pull request #926 from TomHarte/SimplifiedTiming
Attempts more cleanly to express ZX Spectrum timing.
2021-04-21 19:46:23 -04:00
Thomas Harte
d0c789ff9a Locks declarative form of contention closer to regular expressions. 2021-04-21 19:37:36 -04:00
Thomas Harte
9baa861742 Simplifies timing calculation expression. 2021-04-21 19:18:07 -04:00
Thomas Harte
30a1a53c97 Merge pull request #925 from TomHarte/ZXROMSpeed
Corrects timing error in Spectrum 48kb and 128kb ROM accesses.
2021-04-21 18:54:16 -04:00
Thomas Harte
bdb1b7e77c Reinstate the +2 as the default Spectrum. 2021-04-21 18:49:39 -04:00
Thomas Harte
9293bcbc88 Exclude the ROM from contention on 48kb and 128kb models. 2021-04-21 18:49:18 -04:00
Thomas Harte
c481f475e7 Merge pull request #923 from TomHarte/STStartup
Resolves failure of ST to startup
2021-04-20 22:43:55 -04:00
Thomas Harte
ef01471e17 Ensures the DMA controller remains clocked. 2021-04-20 22:34:13 -04:00
Thomas Harte
73c8157197 Retain 6850 time tracking at all times. 2021-04-20 22:26:43 -04:00
Thomas Harte
af1dc2d3b2 Switches to correct non-value sentinel. 2021-04-20 21:56:58 -04:00
Thomas Harte
8f6b3feee1 Merge pull request #921 from TomHarte/Plus2aDefault
Switches default machine back to +2a.
2021-04-19 22:15:48 -04:00
Thomas Harte
a20f5528b7 Switches default machine back to +2a. 2021-04-19 22:04:49 -04:00
Thomas Harte
f48876d80e Merge pull request #920 from TomHarte/AppleIIVirtual
Disambiguates `reset_all_keys`.
2021-04-19 22:03:01 -04:00
Thomas Harte
db52f13c32 Disambiguates reset_all_keys. 2021-04-19 21:49:06 -04:00
Thomas Harte
2590769d3f Merge pull request #919 from TomHarte/XcodeProjectTweaks
Increases warnings, cleans up a touch.
2021-04-19 21:33:04 -04:00
Thomas Harte
5667dcac36 Increases warnings, cleans up a touch. 2021-04-19 21:28:12 -04:00
Thomas Harte
bec71ead39 Merge pull request #918 from TomHarte/macOS13
Reintroduces macOS 10.13 support.
2021-04-19 21:12:57 -04:00
Thomas Harte
e4d9022d37 Returns deployment target to 10.13. 2021-04-19 20:57:56 -04:00
Thomas Harte
572be48f38 Attempts to add an early exit for non-Metal Macs.
This will be necessary only prior to 10.14.
2021-04-19 20:55:25 -04:00
Thomas Harte
6f4ccebfa1 Merge pull request #917 from TomHarte/InterruptAddress
Put the program counter on the bus during interrupt acknowledge.
2021-04-19 20:08:22 -04:00
Thomas Harte
77fcf52d27 Purely style: remove some redundant nullptrs. 2021-04-19 18:53:00 -04:00
Thomas Harte
79c2bc1fd7 Put the program counter on the bus during interrupt acknowledge. 2021-04-19 18:43:50 -04:00
Thomas Harte
76370d9418 Merge pull request #916 from TomHarte/OffByOne
Corrects off-by-one timing errors in the ZX Spectrum.
2021-04-18 20:25:13 -04:00
Thomas Harte
7bac18bd65 Address bus load time is not + 1/2. 2021-04-18 18:41:24 -04:00
Thomas Harte
704737144a Corrects all interrupt timing for sign and off-by-one errors. 2021-04-18 18:40:44 -04:00
Thomas Harte
2a9c73a1d3 Merge pull request #915 from TomHarte/SpectrumSDLOptions
Adds display of Spectrum command-line options.
2021-04-18 12:08:02 -04:00
Thomas Harte
e87e851401 Add a redundant but idiomatic initial value. 2021-04-18 11:56:22 -04:00
Thomas Harte
80d4846a27 Respond with 0xff during an interrupt acknowledge. 2021-04-18 11:56:00 -04:00
Thomas Harte
9fd53c9c91 Adds the ZX Spectrum to ::AllMachines. 2021-04-17 23:06:37 -04:00
156 changed files with 7796 additions and 1788 deletions

View File

@@ -19,6 +19,7 @@ enum class Machine {
AtariST,
ColecoVision,
Electron,
Enterprise,
Macintosh,
MasterSystem,
MSX,

View File

@@ -0,0 +1,81 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/06/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/FAT.hpp"
#include <algorithm>
namespace {
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
return std::equal(
lhs.begin(), lhs.end(),
rhs.begin(), rhs.end(),
[] (char l, char r) {
return tolower(l) == tolower(r);
});
}
}
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Otherwise, assume a return will happen.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Enterprise::Target;
auto *const target = new Target;
target->media = media;
// Always require a BASIC.
target->basic_version = Target::BASICVersion::Any;
// Inspect any supplied disks.
if(!media.disks.empty()) {
// DOS will be needed.
target->dos = Target::DOS::EXDOS;
// Grab the volume information, which includes the root directory.
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
if(volume) {
// If there's an EXDOS.INI then this disk should be able to boot itself.
// If not but if there's only one .COM or .BAS, automatically load that.
// Failing that, issue a :DIR and give the user a clue as to how to load.
const Storage::Disk::FAT::File *selected_file = nullptr;
bool has_exdos_ini = false;
bool did_pick_file = false;
for(const auto &file: (*volume).root_directory) {
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
has_exdos_ini = true;
break;
}
if(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas")) {
did_pick_file = !selected_file;
selected_file = &file;
}
}
if(!has_exdos_ini) {
if(did_pick_file) {
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"";
} else {
target->loading_command = ":dir\n";
}
}
}
}
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/06/2021.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Enterprise {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */

View File

@@ -0,0 +1,53 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Enterprise_Target_h
#define Analyser_Static_Enterprise_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
ReflectableEnum(DOS, EXDOS, None);
Model model = Model::Enterprise128;
EXOSVersion exos_version = EXOSVersion::Any;
BASICVersion basic_version = BASICVersion::None;
DOS dos = DOS::None;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Enterprise) {
if(needs_declare()) {
AnnounceEnum(Model);
AnnounceEnum(EXOSVersion);
AnnounceEnum(BASICVersion);
AnnounceEnum(DOS);
DeclareField(model);
DeclareField(exos_version);
DeclareField(basic_version);
DeclareField(dos);
}
}
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

View File

@@ -23,6 +23,7 @@
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Enterprise/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
@@ -43,9 +44,9 @@
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
@@ -55,8 +56,14 @@
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/DSK.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/SZX.hpp"
#include "../../Storage/State/Z80.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@@ -73,15 +80,23 @@
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
namespace {
std::string get_extension(const std::string &name) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::string::size_type final_dot = name.find_last_of(".");
if(final_dot == std::string::npos) return name;
std::string extension = name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
@@ -131,8 +146,9 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
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, 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.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, 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
Format( "hfe",
@@ -142,6 +158,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
@@ -199,14 +216,35 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
#undef TryInsert
// Otherwise:
//
// 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);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
@@ -220,6 +258,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Enterprise);
Append(Macintosh);
Append(MSX);
Append(Oric);

View File

@@ -15,6 +15,7 @@
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Reflection/Struct.hpp"
#include <memory>
#include <string>
@@ -23,8 +24,10 @@
namespace Analyser {
namespace Static {
struct State;
/*!
A list of disks, tapes and cartridges.
A list of disks, tapes and cartridges, and possibly a state snapshot.
*/
struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
@@ -48,13 +51,16 @@ struct Media {
};
/*!
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() {}
// This field is entirely optional.
std::unique_ptr<Reflection::Struct> state;
Machine machine;
Media media;
float confidence = 0.0f;

View File

@@ -213,7 +213,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
*/
forceinline Cycles divide_cycles(const Cycles &divisor) {
const HalfCycles half_divisor = HalfCycles(divisor);

View File

@@ -103,9 +103,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
}
if constexpr (has_sequence_points<T>::value) {
time_until_event_ -= rhs;
time_until_event_ -= rhs * multiplier;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_;
time_overrun_ = time_until_event_ / divider;
flush();
update_sequence_point();
return true;
@@ -145,13 +145,21 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the amount of time since the object was last flushed, in the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
/// converted to the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
if constexpr (divider == 1) {
return time_since_update_ + offset;
}
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
}
/// Flushes all accumulated time.
///
/// This does not affect this actor's record of when the next sequence point will occur.
@@ -185,7 +193,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
/// supports sequence points; @c LocalTimeScale() otherwise.
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
return time_until_event_;
return time_until_event_ / divider;
}
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
@@ -196,10 +204,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
return rhs >= time_until_event_;
}
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
/// after @c duration, if that delay were to occur in @c offset units of time from now.
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
// A 1:1 mapping is easy.
if constexpr (multiplier == 1 && divider == 1) {
return duration;
}
// Work out when this query is placed, and the time to which it relates
const auto base = time_since_update_ + offset * divider;
const auto target = base + duration * divider;
// Figure out the number of whole input steps that is required to get
// past target, and subtract the number of whole input steps necessary
// to get to base.
const auto steps_to_base = base.as_integral() / multiplier;
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
return LocalTimeScale(steps_to_target - steps_to_base);
}
/// Updates this template's record of the next sequence point.
void update_sequence_point() {
if constexpr (has_sequence_points<T>::value) {
time_until_event_ = object_.get_next_sequence_point();
// Keep a fast path where no conversions will be applied; if conversions are
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.get_next_sequence_point();
} else {
const auto time = object_.get_next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
time_until_event_ = time * divider;
}
}
assert(time_until_event_ > LocalTimeScale(0));
}
}

View File

@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
goto test_type1_type;
begin_type1_spin_up:
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) {
set_motor_on(true);
goto test_type1_type;
}
SPIN_UP();
test_type1_type:
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) {
if(has_motor_on_line()) set_motor_on(true);
goto test_type2_delay;
}
if(has_motor_on_line()) goto begin_type2_spin_up;
goto begin_type2_load_head;

View File

@@ -129,8 +129,8 @@ ClockingHint::Preference ACIA::preferred_clocking() const {
// because it's unclear when the interrupt might come.
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
// No clocking required then.
return ClockingHint::Preference::None;
// Real-time clocking not required then.
return ClockingHint::Preference::JustInTime;
}
bool ACIA::get_interrupt_line() const {

View File

@@ -208,7 +208,7 @@ void MFP68901::run_for(HalfCycles time) {
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
return HalfCycles::max();
}
// MARK: - Timers

View File

@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_next_sequence_point() {
if(next_line_interrupt_row == -1) {
return generate_interrupts_ ?
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
HalfCycles(-1);
HalfCycles::max();
}
// Figure out the number of internal cycles until the next line interrupt, which is the amount

View File

@@ -12,6 +12,8 @@
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Reflection/Struct.hpp"
namespace GI {
namespace AY38910 {
@@ -162,6 +164,8 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
friend struct State;
};
/*!
@@ -192,6 +196,29 @@ struct Utility {
};
struct State: public Reflection::StructImpl<State> {
uint8_t registers[16]{};
uint8_t selected_register = 0;
// TODO: all audio-production thread state.
State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(selected_register);
}
}
template <typename AY> void apply(AY &target) {
// Establish emulator-thread state
for(uint8_t c = 0; c < 16; c++) {
target.select_register(c);
target.set_register_value(registers[c]);
}
target.select_register(selected_register);
}
};
}
}

View File

@@ -11,11 +11,11 @@
using namespace Concurrency;
AsyncTaskQueue::AsyncTaskQueue()
#ifndef __APPLE__
#ifndef USE_GCD
: should_destruct_(false)
#endif
{
#ifdef __APPLE__
#ifdef USE_GCD
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_ = std::make_unique<std::thread>([this]() {
@@ -44,7 +44,7 @@ AsyncTaskQueue::AsyncTaskQueue()
}
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
#ifdef USE_GCD
flush();
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
@@ -57,7 +57,7 @@ AsyncTaskQueue::~AsyncTaskQueue() {
}
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_async(serial_dispatch_queue_, ^{function();});
#else
std::lock_guard lock(queue_mutex_);
@@ -67,7 +67,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
}
void AsyncTaskQueue::flush() {
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_sync(serial_dispatch_queue_, ^{});
#else
auto flush_mutex = std::make_shared<std::mutex>();

View File

@@ -16,8 +16,9 @@
#include <memory>
#include <thread>
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
#include <dispatch/dispatch.h>
#define USE_GCD
#endif
namespace Concurrency {
@@ -47,7 +48,7 @@ class AsyncTaskQueue {
void flush();
private:
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_queue_t serial_dispatch_queue_;
#else
std::unique_ptr<std::thread> thread_;

View File

@@ -9,6 +9,7 @@
#ifndef Joystick_hpp
#define Joystick_hpp
#include <cstddef>
#include <vector>
namespace Inputs {

View File

@@ -8,6 +8,8 @@
#include "Keyboard.hpp"
#include <cstddef>
using namespace Inputs;
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {

View File

@@ -9,7 +9,7 @@
#ifndef CachingExecutor_hpp
#define CachingExecutor_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <array>
#include <cstdint>

View File

@@ -9,7 +9,7 @@
#ifndef Disassembler_hpp
#define Disassembler_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <list>
#include <map>

View File

@@ -787,45 +787,43 @@ template <bool has_fdc> class ConcreteMachine:
ay_.ay().set_port_handler(&key_state_);
// construct the list of necessary ROMs
const std::string machine_name = "AmstradCPC";
std::vector<ROMMachine::ROM> required_roms = {
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
};
std::string model_number;
uint32_t crcs[2];
bool has_amsdos = false;
ROM::Name firmware, basic;
switch(target.model) {
default:
model_number = "6128";
has_128k_ = true;
crcs[0] = 0x0219bb74;
crcs[1] = 0xca6af63d;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
model_number = "464";
has_128k_ = false;
crcs[0] = 0x815752df;
crcs[1] = 0x7d9a3bac;
firmware = ROM::Name::CPC464Firmware;
basic = ROM::Name::CPC464BASIC;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
crcs[0] = 0x3f5a6dc4;
crcs[1] = 0x32fee492;
firmware = ROM::Name::CPC664Firmware;
basic = ROM::Name::CPC664BASIC;
has_amsdos = true;
break;
default:
firmware = ROM::Name::CPC6128Firmware;
basic = ROM::Name::CPC6128BASIC;
has_amsdos = true;
break;
}
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
// fetch and verify the ROMs
const auto roms = rom_fetcher(required_roms);
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) throw ROMMachine::Error::MissingROMs;
roms_[index] = std::move(*data);
roms_[index].resize(16384);
ROM::Request request = ROM::Request(firmware) && ROM::Request(basic);
if(has_amsdos) {
request = request && ROM::Request(ROM::Name::AMSDOS);
}
// Fetch and verify the ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
if(has_amsdos) {
roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second;
}
roms_[ROMType::OS] = roms.find(firmware)->second;
roms_[ROMType::BASIC] = roms.find(basic)->second;
// Establish default memory map
upper_rom_is_paged_ = true;
upper_rom_ = ROMType::BASIC;

View File

@@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@@ -16,6 +16,7 @@ void Keyboard::perform_command(const Command &command) {
switch(command.type) {
case Command::Type::Reset:
modifiers_ = 0xffff;
[[fallthrough]];
case Command::Type::Flush: {
std::lock_guard lock_guard(keys_mutex_);
pending_events_.clear();

View File

@@ -48,7 +48,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::JoystickMachine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public Configurable::Device,
public Activity::Source,
public Apple::II::Card::Delegate {
@@ -66,7 +65,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
using Processor = CPU::MOS6502::Processor<
(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502,
ConcreteMachine,
false>;
Processor m6502_;
VideoBusHandler video_bus_handler_;
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
int cycles_into_current_line_ = 0;
@@ -91,16 +94,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
uint8_t get_keyboard_input() {
if(string_serialiser_) {
return string_serialiser_->head() | 0x80;
} else {
return keyboard_input_;
}
}
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
@@ -258,16 +251,99 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK: - Keyboard and typing.
// MARK - Joysticks.
struct Keyboard: public Inputs::Keyboard {
Keyboard(Processor *m6502) : m6502_(m6502) {}
void reset_all_keys() final {
open_apple_is_pressed = closed_apple_is_pressed = key_is_down = false;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
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::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_->set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input = uint8_t(value | 0x80);
key_is_down = true;
} else {
if((keyboard_input & 0x7f) == value) {
key_is_down = false;
}
}
return true;
}
uint8_t get_keyboard_input() {
if(string_serialiser) {
return string_serialiser->head() | 0x80;
} else {
return keyboard_input;
}
}
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed = false;
bool closed_apple_is_pressed = false;
uint8_t keyboard_input = 0x00;
bool key_is_down = false;
std::unique_ptr<Utility::StringSerialiser> string_serialiser;
private:
Processor *const m6502_;
};
Keyboard keyboard_;
// MARK: - Joysticks.
JoystickPair joysticks_;
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
@@ -276,7 +352,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_toggle_(audio_queue_),
speaker_(audio_toggle_),
language_card_(*this),
auxiliary_switches_(*this) {
auxiliary_switches_(*this),
keyboard_(&m6502_) {
// The system's master clock rate.
constexpr float master_clock = 14318180.0;
@@ -300,49 +377,55 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
const std::string machine_name = "AppleII";
std::vector<ROMMachine::ROM> rom_descriptions;
size_t rom_size = 12*1024;
ROM::Name character, system;
switch(target.model) {
default:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIOriginal;
break;
case Target::Model::IIplus:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIPlus;
break;
case Target::Model::IIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
character = ROM::Name::AppleIIeCharacter;
system = ROM::Name::AppleIIe;
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
character = ROM::Name::AppleIIEnhancedECharacter;
system = ROM::Name::AppleIIEnhancedE;
break;
}
const auto roms = rom_fetcher(rom_descriptions);
// Try to install a Disk II card now, before checking the ROM list,
// to make sure that Disk II dependencies have been communicated.
if(target.disk_controller != Target::DiskController::None) {
ROM::Request request = ROM::Request(character) && ROM::Request(system);
// Add the necessary Disk II requests if appropriate.
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
if(has_disk_controller) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
request = request && DiskIICard::rom_request(is_sixteen_sector);
}
// Now, check and move the ROMs.
if(!roms[0] || !roms[1]) {
// Request, validate and install ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(*roms[1]);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - off_t(rom_size));
if(has_disk_controller) {
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
}
video_.set_character_rom(*roms[0]);
rom_ = std::move(roms.find(system)->second);
// The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary.
if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) {
if(rom_.size() > 16128) {
rom_.erase(rom_.begin(), rom_.end() - off_t(16128));
}
}
video_.set_character_rom(roms.find(character)->second);
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
@@ -435,18 +518,18 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
default: break;
case 0xc000:
*value = get_keyboard_input();
*value = keyboard_.get_keyboard_input();
break;
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
*value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f);
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(
joysticks_.button(0) ||
(is_iie() && open_apple_is_pressed_)
(is_iie() && keyboard_.open_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -454,7 +537,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
*value &= 0x7f;
if(
joysticks_.button(1) ||
(is_iie() && closed_apple_is_pressed_)
(is_iie() && keyboard_.closed_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -476,7 +559,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.state().bank2); break;
case 0xc012: IIeSwitchRead(language_card_.state().read); break;
case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break;
@@ -559,15 +642,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
break;
case 0xc010:
keyboard_input_ &= 0x7f;
if(string_serialiser_) {
if(!string_serialiser_->advance())
string_serialiser_.reset();
keyboard_.keyboard_input &= 0x7f;
if(keyboard_.string_serialiser) {
if(!keyboard_.string_serialiser->advance())
keyboard_.string_serialiser.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie() && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
*value = (keyboard_.key_is_down ? 0x80 : 0x00) | (keyboard_.keyboard_input & 0x7f);
}
break;
@@ -683,81 +766,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
bool prefers_logical_input() final {
return true;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
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::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed_ = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed_ = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_.set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input_ = uint8_t(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
return true;
}
Inputs::Keyboard &get_keyboard() final {
return *this;
return keyboard_;
}
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
keyboard_.string_serialiser = std::make_unique<Utility::StringSerialiser>(string, true);
}
bool can_type(char c) const final {
@@ -769,12 +787,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->use_square_pixels = video_.get_use_square_pixels();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
video_.set_use_square_pixels(options->use_square_pixels);
}
// MARK: MediaTarget

View File

@@ -30,8 +30,11 @@ class Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
bool use_square_pixels = false;
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
if(needs_declare()) {
DeclareField(use_square_pixels);
declare_display_option();
limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1);
}

View File

@@ -10,27 +10,34 @@
using namespace Apple::II;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Request DiskIICard::rom_request(bool is_16_sector) {
if(is_16_sector) {
roms = rom_fetcher({
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
});
return ROM::Request(ROM::Name::DiskIIBoot16Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
} else {
roms = rom_fetcher({
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
});
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
return ROM::Request(ROM::Name::DiskIIBoot13Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
}
if(!roms[0] || !roms[1]) {
}
DiskIICard::DiskIICard(ROM::Map &map, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Map::iterator state_machine, boot;
if(is_16_sector) {
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot16Sector);
} else {
// TODO: see above re: 13-sector state machine.
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot13Sector);
}
if(state_machine == map.end() || boot == map.end()) {
throw ROMMachine::Error::MissingROMs;
}
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
boot_ = std::move(boot->second);
diskii_.set_state_machine(state_machine->second);
set_select_constraints(None);
diskii_.set_clocking_hint_observer(this);
}

View File

@@ -25,7 +25,8 @@ namespace II {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
static ROM::Request rom_request(bool is_16_sector);
DiskIICard(ROM::Map &, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;

View File

@@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
is_iie_(is_iie) {
// Show only the centre 75% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
set_use_square_pixels(use_square_pixels_);
// TODO: there seems to be some sort of bug whereby switching modes can cause
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
@@ -26,6 +25,41 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// crt_.set_immediate_default_phase(0.5f);
}
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
use_square_pixels_ = use_square_pixels;
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
#else
if(use_square_pixels) {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.09f, 0.75f, 0.77f));
} else {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
}
#endif
if(use_square_pixels) {
// From what I can make out, many contemporary Apple II monitors were
// calibrated slightly to stretch the Apple II's display slightly wider
// than it should be per the NTSC standards, for approximately square
// pixels. This reproduces that.
// 243 lines and 52µs are visible.
// i.e. to be square, 1 pixel should be: (1/243 * 52) * (3/4) = 156/972 = 39/243 µs
// On an Apple II each pixel is actually 1/7µs.
// Therefore the adjusted aspect ratio should be (4/3) * (39/243)/(1/7) = (4/3) * 273/243 = 1092/729 = 343/243 ~= 1.412
crt_.set_aspect_ratio(343.0f / 243.0f);
} else {
// Standard NTSC aspect ratio.
crt_.set_aspect_ratio(4.0f / 3.0f);
}
}
bool VideoBase::get_use_square_pixels() {
return use_square_pixels_;
}
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}

View File

@@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches<Cycles> {
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Sets whether the current CRT should be recalibrated away from normative NTSC
/// to produce square pixels in 40-column text mode.
void set_use_square_pixels(bool);
bool get_use_square_pixels();
protected:
Outputs::CRT::CRT crt_;
bool use_square_pixels_ = false;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
@@ -393,7 +399,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
// Supply the real phase value if this is an Apple build.
// TODO: eliminate UGLY HACK.
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
constexpr int phase = 224;
#else
constexpr int phase = 0;

View File

@@ -228,36 +228,6 @@ template <typename TimeUnit> class VideoSwitches {
return external_.annunciator_3;
}
enum class CharacterROM {
/// The ROM that shipped with both the Apple II and the II+.
II,
/// The ROM that shipped with the original IIe.
IIe,
/// The ROM that shipped with the Enhanced IIe.
EnhancedIIe,
/// The ROM that shipped with the IIgs.
IIgs
};
/// @returns A file-level description of @c rom.
static ROMMachine::ROM rom_description(CharacterROM rom) {
const std::string machine_name = "AppleII";
switch(rom) {
case CharacterROM::II:
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
case CharacterROM::IIe:
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
default: // To appease GCC.
case CharacterROM::EnhancedIIe:
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
case CharacterROM::IIgs:
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
}
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;

View File

@@ -189,31 +189,23 @@ class ConcreteMachine:
speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider));
using Target = Analyser::Static::AppleIIgs::Target;
std::vector<ROMMachine::ROM> rom_descriptions;
const std::string machine_name = "AppleIIgs";
ROM::Name system;
switch(target.model) {
case Target::Model::ROM00:
/* TODO */
case Target::Model::ROM01:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0);
break;
case Target::Model::ROM03:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29);
break;
case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break;
case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break;
default: system = ROM::Name::AppleIIgsROM03; break;
}
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter;
constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03;
// TODO: pick a different ADB ROM for earlier machine revisions?
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0] || !roms[1] || !roms[2]) {
ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = *roms[0];
video_->set_character_rom(*roms[1]);
adb_glu_->set_microcontroller_rom(*roms[2]);
rom_ = roms.find(system)->second;
video_->set_character_rom(roms.find(characters)->second);
adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second);
// Run only the currently-interesting self test.
// rom_[0x36402] = 2;

View File

@@ -271,7 +271,7 @@ class Keyboard {
/// 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,
/// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
/// but not afterwards.
int response_ = 0;

View File

@@ -96,25 +96,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
using Model = Analyser::Static::Macintosh::Target::Model;
const std::string machine_name = "Macintosh";
uint32_t ram_size, rom_size;
std::vector<ROMMachine::ROM> rom_descriptions;
ROM::Name rom_name;
switch(model) {
default:
case Model::Mac128k:
ram_size = 128*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
rom_name = ROM::Name::Macintosh128k;
break;
case Model::Mac512k:
ram_size = 512*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
rom_name = ROM::Name::Macintosh512k;
break;
case Model::Mac512ke:
case Model::MacPlus: {
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);
rom_name = ROM::Name::MacintoshPlus;
} break;
}
ram_mask_ = ram_size - 1;
@@ -123,12 +122,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
// Grab a copy of the ROM and convert it into big-endian data.
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
roms[0]->resize(rom_size);
Memory::PackBigEndian16(*roms[0], rom_);
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
// Randomise memory contents.
Memory::Fuzz(ram_);

View File

@@ -35,7 +35,7 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
// and set a visible area to work around the OpenGL issue if required.
// TODO: eliminate UGLY HACK.
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
#else
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));

View File

@@ -74,15 +74,13 @@ class ConcreteMachine:
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
Memory::Fuzz(ram_);
std::vector<ROMMachine::ROM> rom_descriptions = {
{"AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64}
// {"AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43}
};
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(*roms[0], rom_);
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
// Set up basic memory map.
memory_map_[0] = BusDevice::MostlyRAM;

View File

@@ -252,7 +252,7 @@ void DMAController::set_component_prefers_clocking(ClockingHint::Source *, Clock
}
ClockingHint::Preference DMAController::preferred_clocking() const {
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::RealTime;
}
void DMAController::set_activity_observer(Activity::Observer *observer) {

View File

@@ -127,15 +127,13 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
const auto roms = rom_fetcher(
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
if(!roms[0]) {
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
bios_ = *roms[0];
bios_.resize(8192);
bios_ = roms.find(rom_name)->second;
if(!target.media.cartridges.empty()) {
const auto &segment = target.media.cartridges.front()->get_segments().front();

View File

@@ -41,7 +41,8 @@ namespace C1540 {
*/
class Machine final: public MachineBase {
public:
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
static ROM::Request rom_request(Personality personality);
Machine(Personality personality, const ROM::Map &roms);
/*!
Sets the serial bus to which this drive should attach itself.

View File

@@ -16,7 +16,15 @@
using namespace Commodore::C1540;
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
ROM::Request Machine::rom_request(Personality personality) {
switch(personality) {
default:
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
}
}
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
Storage::Disk::Controller(1000000),
m6502_(*this),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
@@ -39,28 +47,22 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
emplace_drive(1000000, 300, 2);
set_drive(1);
std::string device_name;
uint32_t crc = 0;
ROM::Name rom_name;
switch(personality) {
case Personality::C1540:
device_name = "1540";
crc = 0x718d42b1;
break;
case Personality::C1541:
device_name = "1541";
crc = 0xfb760019;
break;
default:
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
}
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
if(!roms[0]) {
auto rom = roms.find(rom_name);
if(rom == roms.end()) {
throw ROMMachine::Error::MissingROMs;
}
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
}
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
MachineBase(personality, rom_fetcher) {}
Machine::Machine(Personality personality, const ROM::Map &roms) :
MachineBase(personality, roms) {}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);

View File

@@ -127,7 +127,7 @@ class MachineBase:
public Storage::Disk::Controller {
public:
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
MachineBase(Personality personality, const ROM::Map &roms);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);

View File

@@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -316,53 +316,48 @@ class ConcreteMachine:
// Install a joystick.
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
const std::string machine_name = "Vic20";
std::vector<ROMMachine::ROM> rom_names = {
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
};
ROM::Request request(ROM::Name::Vic20BASIC);
ROM::Name kernel, character;
switch(target.region) {
default:
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishPALKernel;
break;
case Analyser::Static::Commodore::Target::Region::American:
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishNTSCKernel;
break;
case Analyser::Static::Commodore::Target::Region::Danish:
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
character = ROM::Name::Vic20DanishCharacters;
kernel = ROM::Name::Vic20DanishKernel;
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
character = ROM::Name::Vic20JapaneseCharacters;
kernel = ROM::Name::Vic20JapaneseKernel;
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
character = ROM::Name::Vic20SwedishCharacters;
kernel = ROM::Name::Vic20SwedishKernel;
break;
}
const auto roms = rom_fetcher(rom_names);
if(target.has_c1540) {
request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540);
}
request = request && ROM::Request(character) && ROM::Request(kernel);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
basic_rom_ = std::move(*roms[0]);
character_rom_ = std::move(*roms[1]);
kernel_rom_ = std::move(*roms[2]);
// Characters ROMs should be 4kb.
character_rom_.resize(4096);
// Kernel ROMs and the BASIC ROM should be 8kb.
kernel_rom_.resize(8192);
basic_rom_ = std::move(roms.find(ROM::Name::Vic20BASIC)->second);
character_rom_ = std::move(roms.find(character)->second);
kernel_rom_ = std::move(roms.find(kernel)->second);
if(target.has_c1540) {
// construct the 1540
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, rom_fetcher);
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
// attach it to the serial bus
c1540_->set_serial_bus(serial_bus_);

View File

@@ -69,37 +69,25 @@ template <bool has_scsi_bus> class ConcreteMachine:
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
speaker_.set_high_frequency_cutoff(6000);
const std::string machine_name = "Electron";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
};
const size_t pres_adfs_rom_position = required_roms.size();
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
if(target.has_pres_adfs) {
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
}
const size_t acorn_adfs_rom_position = required_roms.size();
if(target.has_acorn_adfs) {
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
request = request && ::ROM::Request(::ROM::Name::AcornADFS);
}
const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) {
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS);
}
const size_t ap6_rom_position = required_roms.size();
if(target.has_ap6_rom) {
required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc);
request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6);
}
const auto roms = rom_fetcher(required_roms);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
set_rom(ROM::BASIC, *roms[0], false);
set_rom(ROM::OS, *roms[1], false);
set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false);
set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false);
/*
ROM slot mapping applied:
@@ -115,19 +103,18 @@ template <bool has_scsi_bus> class ConcreteMachine:
plus3_ = std::make_unique<Plus3>();
if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, true);
}
if(target.has_pres_adfs) {
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true);
set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true);
}
if(target.has_acorn_adfs) {
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true);
}
}
if(target.has_ap6_rom) {
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true);
}
if(target.has_sideways_ram) {

View File

@@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@@ -0,0 +1,326 @@
//
// Dave.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Dave.hpp"
using namespace Enterprise::Dave;
// MARK: - Audio generator
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Audio::write(uint16_t address, uint8_t value) {
address &= 0xf;
audio_queue_.defer([address, value, this] {
switch(address) {
case 0: case 2: case 4:
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
break;
case 1: case 3: case 5:
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
channels_[address >> 1].distortion = Channel::Distortion((value >> 4)&3);
channels_[address >> 1].high_pass = value & 0x40;
channels_[address >> 1].ring_modulate = value & 0x80;
break;
case 6:
noise_.frequency = Noise::Frequency(value&3);
noise_.polynomial = Noise::Polynomial((value >> 2)&3);
noise_.swap_polynomial = value & 0x10;
noise_.low_pass = value & 0x20;
noise_.high_pass = value & 0x40;
noise_.ring_modulate = value & 0x80;
break;
case 7:
channels_[0].sync = value & 0x01;
channels_[1].sync = value & 0x02;
channels_[2].sync = value & 0x04;
use_direct_output_[0] = value & 0x08;
use_direct_output_[1] = value & 0x10;
// Interrupt bits are handled separately.
break;
case 8: case 9: case 10:
channels_[address - 8].amplitude[0] = value & 0x3f;
break;
case 12: case 13: case 14:
channels_[address - 12].amplitude[1] = value & 0x3f;
break;
case 11: noise_.amplitude[0] = value & 0x3f; break;
case 15: noise_.amplitude[1] = value & 0x3f; break;
}
});
}
void Audio::set_sample_volume_range(int16_t range) {
audio_queue_.defer([range, this] {
volume_ = range / (63*4);
});
}
void Audio::update_channel(int c) {
if(channels_[c].sync) {
channels_[c].count = channels_[c].reload;
channels_[c].output <<= 1;
return;
}
auto output = channels_[c].output & 1;
channels_[c].output <<= 1;
if(!channels_[c].count) {
channels_[c].count = channels_[c].reload;
if(channels_[c].distortion == Channel::Distortion::None)
output ^= 1;
else
output = poly_state_[int(channels_[c].distortion)];
if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) {
output = 0;
}
if(channels_[c].ring_modulate) {
output = ~(output ^ channels_[(c+2)%3].output) & 1;
}
} else {
--channels_[c].count;
}
channels_[c].output |= output;
}
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
for(size_t c = 0; c < number_of_samples; c++) {
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
if(noise_.swap_polynomial) {
poly_state_[int(Channel::Distortion::SevenBit)] = poly_state_[int(Channel::Distortion::None)];
}
// Update tone channels.
update_channel(0);
update_channel(1);
update_channel(2);
// Update noise channel.
// Step 1: decide whether there is a tick to apply.
bool noise_tick = false;
if(noise_.frequency == Noise::Frequency::DivideByFour) {
if(!noise_.count) {
noise_tick = true;
noise_.count = 3;
} else {
--noise_.count;
}
} else {
noise_tick = (channels_[int(noise_.frequency) - 1].output&3) == 2;
}
// Step 2: tick if necessary.
int noise_output = noise_.output & 1;
noise_.output <<= 1;
if(noise_tick) {
switch(noise_.polynomial) {
case Noise::Polynomial::SeventeenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly17_.next());
break;
case Noise::Polynomial::FifteenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly15_.next());
break;
case Noise::Polynomial::ElevenBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly11_.next());
break;
case Noise::Polynomial::NineBit:
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly9_.next());
break;
}
noise_output = poly_state_[int(Channel::Distortion::None)];
}
noise_.output |= noise_output;
// Low pass: sample channel 2 on downward transitions of the prima facie output.
if(noise_.low_pass && (noise_.output & 3) == 2) {
noise_.output = (noise_.output & ~1) | (channels_[2].output & 1);
}
// Apply noise high-pass.
if(noise_.high_pass && (channels_[0].output & 3) == 2) {
noise_.output &= ~1;
}
// Update noise ring modulation, if any.
if(noise_.ring_modulate) {
noise_.final_output = !((noise_.output ^ channels_[1].output) & 1);
} else {
noise_.final_output = noise_.output & 1;
}
// I'm unclear on the details of the time division multiplexing so,
// for now, just sum the outputs.
target[(c << 1) + 0] =
volume_ *
(use_direct_output_[0] ?
channels_[0].amplitude[0]
: (
channels_[0].amplitude[0] * (channels_[0].output & 1) +
channels_[1].amplitude[0] * (channels_[1].output & 1) +
channels_[2].amplitude[0] * (channels_[2].output & 1) +
noise_.amplitude[0] * noise_.final_output
));
target[(c << 1) + 1] =
volume_ *
(use_direct_output_[1] ?
channels_[0].amplitude[1]
: (
channels_[0].amplitude[1] * (channels_[0].output & 1) +
channels_[1].amplitude[1] * (channels_[1].output & 1) +
channels_[2].amplitude[1] * (channels_[2].output & 1) +
noise_.amplitude[1] * noise_.final_output
));
}
}
// MARK: - Interrupt source
uint8_t TimedInterruptSource::get_new_interrupts() {
const uint8_t result = interrupts_;
interrupts_ = 0;
return result;
}
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
address &= 15;
switch(address) {
default: break;
case 0: case 2:
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
break;
case 1: case 3:
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
break;
case 7: {
channels_[0].sync = value & 0x01;
channels_[1].sync = value & 0x02;
const InterruptRate rate = InterruptRate((value >> 5) & 3);
if(rate != rate_) {
rate_ = rate;
if(rate_ >= InterruptRate::ToneGenerator0) {
programmable_level_ = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)].level;
} else {
programmable_offset_ = programmble_reload(rate_);
}
}
} break;
}
}
void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement) {
if(channels_[c].sync) {
channels_[c].value = channels_[c].reload;
} else {
if(decrement <= channels_[c].value) {
channels_[c].value -= decrement;
} else {
// The decrement is greater than the current value, therefore
// there'll be at least one flip.
//
// After decreasing the decrement by the current value + 1,
// it'll be clear how many decrements are left after reload.
//
// Dividing that by the number of decrements necessary for a
// flip will provide the total number of flips.
const int decrements_after_flip = decrement - (channels_[c].value + 1);
const int num_flips = 1 + decrements_after_flip / (channels_[c].reload + 1);
// If this is a linked channel, set the interrupt mask if a transition
// from high to low is amongst the included flips.
if(is_linked && num_flips + channels_[c].level >= 2) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
}
channels_[c].level ^= (num_flips & 1);
// Apply the modulo number of decrements to the reload value to
// figure out where things stand now.
channels_[c].value = channels_[c].reload - decrements_after_flip % (channels_[c].reload + 1);
}
}
}
void TimedInterruptSource::run_for(Cycles cycles) {
// Update the 1Hz interrupt.
one_hz_offset_ -= cycles;
if(one_hz_offset_ <= Cycles(0)) {
interrupts_ |= uint8_t(Interrupt::OneHz);
one_hz_offset_ += clock_rate;
}
// Update the two tone channels.
update_channel(0, rate_ == InterruptRate::ToneGenerator0, cycles.as<int>());
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
// Update the programmable-frequency interrupt.
if(rate_ < InterruptRate::ToneGenerator0) {
programmable_offset_ -= cycles.as<int>();
if(programmable_offset_ <= 0) {
if(programmable_level_) {
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
}
programmable_level_ ^= true;
programmable_offset_ = programmble_reload(rate_);
}
}
}
Cycles TimedInterruptSource::get_next_sequence_point() const {
int result = one_hz_offset_.as<int>();
switch(rate_) {
case InterruptRate::OnekHz:
case InterruptRate::FiftyHz:
result = std::min(result, programmable_offset_ + (!programmable_level_) * programmble_reload(rate_));
break;
case InterruptRate::ToneGenerator0:
case InterruptRate::ToneGenerator1: {
const auto &channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)];
const int cycles_until_interrupt = channel.value + 1 + (!channel.level) * (channel.reload + 1);
result = std::min(result, cycles_until_interrupt);
} break;
}
return Cycles(result);
}
uint8_t TimedInterruptSource::get_divider_state() {
bool programmable_flag = false;
switch(rate_) {
case InterruptRate::OnekHz:
case InterruptRate::FiftyHz:
programmable_flag = programmable_level_;
break;
case InterruptRate::ToneGenerator0:
programmable_flag = channels_[0].level;
break;
case InterruptRate::ToneGenerator1:
programmable_flag = channels_[1].level;
break;
}
// one_hz_offset_ counts downwards, so when it crosses the halfway mark
// it enters the high part of its wave.
return
(one_hz_offset_ < half_clock_rate ? 0x4 : 0x0) |
(programmable_flag ? 0x1 : 0x0);
}

View File

@@ -0,0 +1,188 @@
//
// Dave.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Dave_hpp
#define Dave_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Numeric/LFSR.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace Enterprise {
namespace Dave {
enum class Interrupt: uint8_t {
VariableFrequency = 0x02,
OneHz = 0x08,
Nick = 0x20,
};
/*!
Models the audio-production subset of Dave's behaviour.
*/
class Audio: public Outputs::Speaker::SampleSource {
public:
Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue);
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value);
// MARK: - SampleSource.
void set_sample_volume_range(int16_t range);
static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound.
void get_samples(std::size_t number_of_samples, int16_t *target);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
// Tone channels.
struct Channel {
// User-set values.
uint16_t reload = 0;
bool high_pass = false;
bool ring_modulate = false;
enum class Distortion {
None = 0,
FourBit = 1,
FiveBit = 2,
SevenBit = 3,
} distortion = Distortion::None;
uint8_t amplitude[2]{};
bool sync = false;
// Current state.
uint16_t count = 0;
int output = 0;
} channels_[3];
void update_channel(int);
// Noise channel.
struct Noise {
// User-set values.
uint8_t amplitude[2]{};
enum class Frequency {
DivideByFour,
ToneChannel0,
ToneChannel1,
ToneChannel2,
} frequency = Frequency::DivideByFour;
enum class Polynomial {
SeventeenBit,
FifteenBit,
ElevenBit,
NineBit
} polynomial = Polynomial::SeventeenBit;
bool swap_polynomial = false;
bool low_pass = false;
bool high_pass = false;
bool ring_modulate = false;
// Current state.
int count = 0;
int output = 0;
bool final_output = false;
} noise_;
void update_noise();
bool use_direct_output_[2]{};
// Global volume, per SampleSource obligations.
int16_t volume_ = 0;
// Polynomials that are always running.
Numeric::LFSRv<0xc> poly4_;
Numeric::LFSRv<0x14> poly5_;
Numeric::LFSRv<0x60> poly7_;
// The selectable, noise-related polynomial.
Numeric::LFSRv<0x110> poly9_;
Numeric::LFSRv<0x500> poly11_;
Numeric::LFSRv<0x6000> poly15_;
Numeric::LFSRv<0x12000> poly17_;
// Current state of the active polynomials.
uint8_t poly_state_[4];
};
/*!
Provides Dave's timed interrupts — those that are provided at 1 kHz,
50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
1 Hz interrupt.
*/
class TimedInterruptSource {
public:
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
/// already identified this write as being to an audio register.
void write(uint16_t address, uint8_t value);
/// Returns a bitmask of interrupts that have become active since
/// the last time this method was called; flags are as defined in
/// @c Enterprise::Dave::Interrupt
uint8_t get_new_interrupts();
/// Returns the current high or low states of the inputs that trigger
/// the interrupts modelled here, as a bit mask compatible with that
/// exposed by Dave as the register at 0xb4.
uint8_t get_divider_state();
/// Advances the interrupt source.
void run_for(Cycles);
/// @returns The amount of time from now until the earliest that
/// @c get_new_interrupts() _might_ have new interrupts to report.
Cycles get_next_sequence_point() const;
private:
static constexpr Cycles clock_rate{250000};
static constexpr Cycles half_clock_rate{125000};
// Interrupts that have fired since get_new_interrupts()
// was last called.
uint8_t interrupts_ = 0;
// A counter for the 1Hz interrupt.
Cycles one_hz_offset_ = clock_rate;
// A counter specific to the 1kHz and 50Hz timers, if in use.
enum class InterruptRate {
OnekHz,
FiftyHz,
ToneGenerator0,
ToneGenerator1,
} rate_ = InterruptRate::OnekHz;
int programmable_offset_ = programmble_reload(InterruptRate::OnekHz);
bool programmable_level_ = false;
// A local duplicate of the counting state of the first two audio
// channels, maintained in case either of those is used as an
// interrupt source.
struct Channel {
int value = 100, reload = 100;
bool sync = false;
bool level = false;
} channels_[2];
void update_channel(int c, bool is_linked, int decrement);
static constexpr int programmble_reload(InterruptRate rate) {
switch(rate) {
default: return 0;
case InterruptRate::OnekHz: return 125;
case InterruptRate::FiftyHz: return 2500;
}
}
};
}
}
#endif /* Dave_hpp */

View File

@@ -0,0 +1,78 @@
//
// EXDos.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "EXDos.hpp"
// TODO: disk_did_change_ should be on the drive. Some drives report it.
using namespace Enterprise;
EXDos::EXDos() : WD1770(P1770) {
emplace_drives(4, 8000000, 300, 2);
set_control_register(0x00);
}
void EXDos::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
get_drive(drive).set_disk(disk);
disk_did_change_ = true;
}
// Documentation for the control register:
//
// Write:
// b7 in use (???)
// b6 disk change reset
// b5 0 = double density, 1 = single density
// b4 side 1 select
// b3, b3, b1, b0 select drive 3, 2, 1, 0
//
// Read:
// b7 data request from WD1770
// b6 disk change
// b5, b4, b3, b2: not used
// b1 interrupt request from WD1770
// b0 drive ready
void EXDos::set_control_register(uint8_t control) {
if(control & 0x40) disk_did_change_ = false;
set_is_double_density(!(control & 0x20));
// Set side.
const int head = (control >> 4) & 1;
for(size_t c = 0; c < 4; c++) {
get_drive(c).set_head(head);
}
// Select drive, ensuring handover of the motor-on state.
const bool motor_state = get_drive().get_motor_on();
for_all_drives([] (Storage::Disk::Drive &drive, size_t) {
drive.set_motor_on(false);
});
set_drive(control & 0xf);
get_drive().set_motor_on(motor_state);
}
uint8_t EXDos::get_control_register() {
const uint8_t status =
(get_data_request_line() ? 0x80 : 0x00) |
(disk_did_change_ ? 0x40 : 0x00) |
(get_interrupt_request_line() ? 0x02 : 0x00) |
(get_drive().get_is_ready() ? 0x01 : 0x00);
return status;
}
void EXDos::set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void EXDos::set_activity_observer(Activity::Observer *observer) {
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}

View File

@@ -0,0 +1,36 @@
//
// EXDos.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef EXDos_hpp
#define EXDos_hpp
#include "../../Components/1770/1770.hpp"
#include "../../Activity/Observer.hpp"
namespace Enterprise {
class EXDos final : public WD::WD1770 {
public:
EXDos();
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
void set_control_register(uint8_t control);
uint8_t get_control_register();
void set_activity_observer(Activity::Observer *observer);
private:
bool disk_did_change_ = false;
void set_motor_on(bool on) override;
};
}
#endif /* EXDos_hpp */

View File

@@ -0,0 +1,740 @@
//
// Enterprise.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Enterprise.hpp"
#include "Dave.hpp"
#include "EXDos.hpp"
#include "Keyboard.hpp"
#include "Nick.hpp"
#include "../MachineTypes.hpp"
#include "../Utility/Typer.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/Z80/Z80.hpp"
#define LOG_PREFIX "[Enterprise] "
#include "../../Outputs/Log.hpp"
namespace Enterprise {
/*
Notes to self on timing:
Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the
first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access
if one has been requested.
The Z80 has a separate, asynchronous 4Mhz clock. That's that.
The documentation is also very forward in emphasising that Nick generates phaselocked
(i.e. in-phase) PAL video.
So: 57*16 = 912 cycles/line.
A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles.
I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than
reducing it to 283.
Therefore 912 cycles occurs in 284/283.7516 * 64 µs.
So one line = 181760000 / 2837516 µs = 45440000 / 709379 µs
=> one cycle = 45440000 / 709379*912 = 45440000 / 646953648 = 2840000 / 40434603 µs
=> clock rate of 40434603 / 2840000 Mhz
And, therefore, the ratio to a 4Mhz Z80 clock is:
40434603 / (2840000 * 4)
= 40434603 / 11360000
i.e. roughly 3.55 Nick cycles per Z80 cycle.
If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80
clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be
guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further
postulating that the clock stoppage takes place so as to align the final cycle of a relevant
access over the available window.
*/
template <bool has_disk_controller> class ConcreteMachine:
public Activity::Source,
public Configurable::Device,
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Utility::TypeRecipient<CharacterMapper> {
private:
constexpr uint8_t min_ram_slot(const Analyser::Static::Enterprise::Target &target) {
size_t ram_size = 128*1024;
switch(target.model) {
case Analyser::Static::Enterprise::Target::Model::Enterprise64: ram_size = 64*1024; break;
case Analyser::Static::Enterprise::Target::Model::Enterprise128: ram_size = 128*1024; break;
case Analyser::Static::Enterprise::Target::Model::Enterprise256: ram_size = 256*1024; break;
}
return uint8_t(0x100 - ram_size / 0x4000);
}
public:
ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
min_ram_slot_(min_ram_slot(target)),
z80_(*this),
nick_(ram_.end() - 65536),
dave_audio_(audio_queue_),
speaker_(dave_audio_) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
set_clock_rate(4'000'000);
ROM::Request request;
using Target = Analyser::Static::Enterprise::Target;
// Pick one or more EXOS ROMs.
switch(target.exos_version) {
case Target::EXOSVersion::v10: request = request && ROM::Request(ROM::Name::EnterpriseEXOS10); break;
case Target::EXOSVersion::v20: request = request && ROM::Request(ROM::Name::EnterpriseEXOS20); break;
case Target::EXOSVersion::v21: request = request && ROM::Request(ROM::Name::EnterpriseEXOS21); break;
case Target::EXOSVersion::v23: request = request && ROM::Request(ROM::Name::EnterpriseEXOS23); break;
case Target::EXOSVersion::Any:
request =
request && (
ROM::Request(ROM::Name::EnterpriseEXOS10) || ROM::Request(ROM::Name::EnterpriseEXOS20) ||
ROM::Request(ROM::Name::EnterpriseEXOS21) || ROM::Request(ROM::Name::EnterpriseEXOS23)
);
break;
default: break;
}
// Similarly pick one or more BASIC ROMs.
switch(target.basic_version) {
case Target::BASICVersion::v10:
request = request && (
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2))
);
break;
case Target::BASICVersion::v11:
request = request && (
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
ROM::Request(ROM::Name::EnterpriseBASIC11Suffixed)
);
case Target::BASICVersion::v21:
request = request && ROM::Request(ROM::Name::EnterpriseBASIC21);
break;
case Target::BASICVersion::Any:
request =
request && (
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2)) ||
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
ROM::Request(ROM::Name::EnterpriseBASIC21)
);
break;
default: break;
}
// Possibly add in a DOS.
switch(target.dos) {
case Target::DOS::EXDOS: request = request && ROM::Request(ROM::Name::EnterpriseEXDOS); break;
default: break;
}
// Get and validate ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
// Extract the appropriate EXOS ROM.
exos_.fill(0xff);
for(const auto rom_name: { ROM::Name::EnterpriseEXOS10, ROM::Name::EnterpriseEXOS20, ROM::Name::EnterpriseEXOS21, ROM::Name::EnterpriseEXOS23 }) {
const auto exos = roms.find(rom_name);
if(exos != roms.end()) {
memcpy(exos_.data(), exos->second.data(), std::min(exos_.size(), exos->second.size()));
break;
}
}
// Extract the appropriate BASIC ROM[s] (if any).
basic_.fill(0xff);
bool has_basic = false;
for(const auto rom_name: { ROM::Name::EnterpriseBASIC10, ROM::Name::EnterpriseBASIC11, ROM::Name::EnterpriseBASIC11Suffixed, ROM::Name::EnterpriseBASIC21 }) {
const auto basic = roms.find(rom_name);
if(basic != roms.end()) {
memcpy(basic_.data(), basic->second.data(), std::min(basic_.size(), basic->second.size()));
has_basic = true;
break;
}
}
if(!has_basic) {
const auto basic1 = roms.find(ROM::Name::EnterpriseBASIC10Part1);
const auto basic2 = roms.find(ROM::Name::EnterpriseBASIC10Part2);
if(basic1 != roms.end() && basic2 != roms.end()) {
memcpy(&basic_[0x0000], basic1->second.data(), std::min(size_t(8192), basic1->second.size()));
memcpy(&basic_[0x2000], basic2->second.data(), std::min(size_t(8192), basic2->second.size()));
}
}
// Extract the appropriate DOS ROMs.
epdos_rom_.fill(0xff);
const auto epdos = roms.find(ROM::Name::EnterpriseEPDOS);
if(epdos != roms.end()) {
memcpy(epdos_rom_.data(), epdos->second.data(), std::min(epdos_rom_.size(), epdos->second.size()));
}
exdos_rom_.fill(0xff);
const auto exdos = roms.find(ROM::Name::EnterpriseEXDOS);
if(exdos != roms.end()) {
memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size()));
}
// Seed key state.
clear_all_keys();
// Take a reasonable guess at the initial memory configuration:
// put EXOS into the first bank since this is a Z80 and therefore
// starts from address 0; the third instruction in EXOS is a jump
// to $c02e so it's reasonable to assume EXOS is in the highest bank
// too, and it appears to act correctly if it's the first 16kb that's
// in the highest bank. From there I guess: all banks are initialised
// to 0.
page<0>(0x00);
page<1>(0x00);
page<2>(0x00);
page<3>(0x00);
// Set up audio.
speaker_.set_input_rate(250000.0f); // TODO: a bigger number, and respect the programmable divider.
// Pass on any media.
insert_media(target.media);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
// Ensure the splash screen is automatically skipped if any media has been provided.
if(!target.media.empty()) {
should_skip_splash_screen_ = !target.media.empty();
typer_delay_ = 2;
}
}
~ConcreteMachine() {
audio_queue_.flush();
}
// MARK: - Z80::BusHandler.
forceinline void advance_nick(HalfCycles duration) {
if(nick_ += duration) {
const auto nick = nick_.last_valid();
const bool nick_interrupt_line = nick->get_interrupt_line();
if(nick_interrupt_line && !previous_nick_interrupt_line_) {
set_interrupts(uint8_t(Dave::Interrupt::Nick), nick_.last_sequence_point_overrun());
}
previous_nick_interrupt_line_ = nick_interrupt_line;
}
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
// Calculate an access penalty, if applicable.
//
// Rule applied here, which is slightly inferred:
//
// Non-video reads and writes are delayed by exactly a cycle or not delayed at all,
// depending on the programmer's configuration of Dave.
//
// Video reads and writes, and Nick port accesses, are delayed so that the last
// clock cycle of the machine cycle falls wholly inside the designated Z80 access
// window, per Nick.
//
// The switch statement below just attempts to implement that logic.
//
HalfCycles penalty;
switch(cycle.operation) {
default: break;
// For non-video pauses, insert during the initial part of the bus cycle.
case CPU::Z80::PartialMachineCycle::ReadStart:
case CPU::Z80::PartialMachineCycle::WriteStart:
if(!is_video_[address >> 14] && wait_mode_ == WaitMode::OnAllAccesses) {
penalty = HalfCycles(2);
}
break;
case CPU::Z80::PartialMachineCycle::ReadOpcodeStart:
if(!is_video_[address >> 14] && wait_mode_ != WaitMode::None) {
penalty = HalfCycles(2);
} else {
// Query Nick for the amount of delay that would occur with one cycle left
// in this read opcode.
const auto delay_time = nick_.time_since_flush(HalfCycles(2));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
}
break;
// Video pauses: insert right at the end of the bus cycle.
case CPU::Z80::PartialMachineCycle::Write:
// Ensure all video that should have been collected prior to
// this write has been.
if(is_video_[address >> 14]) {
nick_.flush();
}
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(is_video_[address >> 14]) {
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
// cycles from now (i.e. with one cycle left to run).
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
}
break;
case CPU::Z80::PartialMachineCycle::Input:
case CPU::Z80::PartialMachineCycle::Output: {
if((address & 0xf0) == 0x80) {
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
// cycles from now (i.e. with one cycle left to run).
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
penalty = nick_.back_map(delay, delay_time);
}
}
}
const HalfCycles full_length = cycle.length + penalty;
time_since_audio_update_ += full_length;
advance_nick(full_length);
if(dave_timer_ += full_length) {
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
}
// The WD/etc runs at a nominal 8Mhz.
if constexpr (has_disk_controller) {
exdos_.run_for(Cycles(full_length.as_integral()));
}
switch(cycle.operation) {
default: break;
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xff) {
default:
LOG("Unhandled input from " << PADHEX(2) << (address & 0xff));
*cycle.value = 0xff;
break;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
if constexpr (has_disk_controller) {
*cycle.value = exdos_.read(address);
} else {
*cycle.value = 0xff;
}
break;
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
if constexpr (has_disk_controller) {
*cycle.value = exdos_.get_control_register();
} else {
*cycle.value = 0xff;
}
break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: case 0x8b:
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
*cycle.value = nick_->read();
break;
case 0xb0: *cycle.value = pages_[0]; break;
case 0xb1: *cycle.value = pages_[1]; break;
case 0xb2: *cycle.value = pages_[2]; break;
case 0xb3: *cycle.value = pages_[3]; break;
case 0xb4:
*cycle.value =
(nick_->get_interrupt_line() ? 0x00 : 0x10) |
dave_timer_->get_divider_state() |
interrupt_state_;
break;
case 0xb5:
if(active_key_line_ < key_lines_.size()) {
*cycle.value = key_lines_[active_key_line_];
} else {
*cycle.value = 0xff;
}
break;
case 0xb6: {
// TODO: selected keyboard row, 0 to 9, should return one bit of joystick
// input. That being the case:
//
// b0: joystick input
// b1, b2: unused (in theory read from control port, but not used by any hardware)
// b3: 0 = printer ready; 1 = not ready
// b4: serial, data in
// b5: serial, status in
// b6: tape input volume level, 0 = high, 1 = low
// b7: tape data input
*cycle.value = 0xff;
} break;
}
break;
case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xff) {
default:
LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff));
break;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
if constexpr(has_disk_controller) {
exdos_.write(address, *cycle.value);
}
break;
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
if constexpr(has_disk_controller) {
exdos_.set_control_register(*cycle.value);
}
break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: case 0x8b:
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
nick_->write(address, *cycle.value);
break;
case 0xb0: page<0>(*cycle.value); break;
case 0xb1: page<1>(*cycle.value); break;
case 0xb2: page<2>(*cycle.value); break;
case 0xb3: page<3>(*cycle.value); break;
case 0xa0: case 0xa1: case 0xa2: case 0xa3:
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
case 0xac: case 0xad: case 0xae: case 0xaf:
update_audio();
dave_audio_.write(address, *cycle.value);
dave_timer_->write(address, *cycle.value);
break;
case 0xb4:
interrupt_mask_ = *cycle.value & 0x55;
interrupt_state_ &= ~*cycle.value;
update_interrupts();
break;
case 0xb5:
// Logic here: the ROM scans the keyboard by checking ascending
// lines. It also seems to provide a line of 0 when using port B5
// for non-keyboard uses.
//
// So: use the rollover from line 9 back to line 0 as a trigger to
// spot that a scan of the keyboard just finished. Which makes it
// time to enqueue the next keypress.
//
// Re: should_skip_splash_screen_ and typer_delay_, assume that a
// single keypress is necessary to get past the Enterprise splash
// screen, then a pause in keypressing while BASIC or whatever
// starts up, then presses can resume.
if(active_key_line_ == 9 && !(*cycle.value & 0xf) && (should_skip_splash_screen_ || typer_)) {
if(should_skip_splash_screen_) {
set_key_state(uint16_t(Key::Space), typer_delay_);
if(typer_delay_) {
--typer_delay_;
} else {
typer_delay_ = 60;
should_skip_splash_screen_ = false;
}
} else {
if(!typer_delay_) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_ = nullptr;
}
} else {
--typer_delay_;
}
}
}
active_key_line_ = *cycle.value & 0xf;
// TODO:
//
// b4: strobe output for printer
// b5: tape sound control (?)
// b6: tape motor control 1, 1 = on
// b7: tape motor control 2, 1 = on
break;
case 0xb6:
// Just 8 bits of printer data.
LOG("TODO: printer output " << PADHEX(2) << *cycle.value);
break;
case 0xb7:
// b0 = serial data out
// b1 = serial status out
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
break;
case 0xbf:
// TODO: onboard RAM, Dave 8/12Mhz select.
switch((*cycle.value >> 2)&3) {
default: wait_mode_ = WaitMode::None; break;
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
case 1: wait_mode_ = WaitMode::OnM1; break;
}
break;
}
break;
case CPU::Z80::PartialMachineCycle::Read:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(read_pointers_[address >> 14]) {
*cycle.value = read_pointers_[address >> 14][address];
} else {
*cycle.value = 0xff;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(write_pointers_[address >> 14]) {
write_pointers_[address >> 14][address] = *cycle.value;
}
break;
}
return penalty;
}
void flush() {
nick_.flush();
update_audio();
audio_queue_.perform();
}
private:
// MARK: - Memory layout
std::array<uint8_t, 256 * 1024> ram_{};
std::array<uint8_t, 64 * 1024> exos_;
std::array<uint8_t, 16 * 1024> basic_;
std::array<uint8_t, 16 * 1024> exdos_rom_;
std::array<uint8_t, 32 * 1024> epdos_rom_;
const uint8_t min_ram_slot_;
const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};
template <size_t slot> void page(uint8_t offset) {
pages_[slot] = offset;
#define Map(location, source) \
if(offset >= location && offset < location + source.size() / 0x4000) { \
page<slot>(&source[(offset - location) * 0x4000], nullptr); \
is_video_[slot] = false; \
return; \
}
Map(0, exos_);
Map(16, basic_);
Map(32, exdos_rom_);
Map(48, epdos_rom_);
#undef Map
// Of whatever size of RAM I've declared above, use only the final portion.
// This correlated with Nick always having been handed the final 64kb and,
// at least while the RAM is the first thing declared above, does a little
// to benefit data locality. Albeit not in a useful sense.
if(offset >= min_ram_slot_) {
const auto ram_floor = 4194304 - ram_.size();
const size_t address = offset * 0x4000 - ram_floor;
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
page<slot>(&ram_[address], &ram_[address]);
return;
}
page<slot>(nullptr, nullptr);
}
template <size_t slot> void page(const uint8_t *read, uint8_t *write) {
read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
}
// MARK: - Memory Timing
// The wait mode affects all memory accesses _outside of the video area_.
enum class WaitMode {
None,
OnM1,
OnAllAccesses
} wait_mode_ = WaitMode::None;
bool is_video_[4]{};
// MARK: - ScanProducer
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
nick_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
return nick_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
nick_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return nick_.last_valid()->get_display_type();
}
// MARK: - AudioProducer
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
// MARK: - TimedMachine
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
}
// MARK: - KeyboardMachine
Enterprise::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
uint8_t active_key_line_ = 0;
std::array<uint8_t, 10> key_lines_;
void set_key_state(uint16_t key, bool is_pressed) final {
if(is_pressed) {
key_lines_[key >> 8] &= ~uint8_t(key);
} else {
key_lines_[key >> 8] |= uint8_t(key);
}
}
void clear_all_keys() final {
key_lines_.fill(0xff);
}
// MARK: - Utility::TypeRecipient
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
if(z80_.get_is_resetting()) {
should_skip_splash_screen_ = true;
typer_delay_ = 1;
} else {
should_skip_splash_screen_ = false;
typer_delay_ = 0;
}
}
bool can_type(char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
bool should_skip_splash_screen_ = false;
int typer_delay_ = 30;
// MARK: - MediaTarget
bool insert_media(const Analyser::Static::Media &media) final {
if constexpr (has_disk_controller) {
if(!media.disks.empty()) {
exdos_.set_disk(media.disks.front(), 0);
}
}
return true;
}
// MARK: - Interrupts
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
void set_interrupts(uint8_t mask, HalfCycles offset = HalfCycles(0)) {
interrupt_state_ |= uint8_t(mask);
update_interrupts(offset);
}
void update_interrupts(HalfCycles offset = HalfCycles(0)) {
z80_.set_interrupt_line((interrupt_state_ >> 1) & interrupt_mask_, offset);
}
// MARK: - Chips.
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000> nick_;
bool previous_nick_interrupt_line_ = false;
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave::Audio dave_audio_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
// The following two should both use the same divider.
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
inline void update_audio() {
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
// as per its own register.
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
}
// MARK: - EXDos card.
EXDos exdos_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
if constexpr (has_disk_controller) {
exdos_.set_activity_observer(observer);
}
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
}
};
}
using namespace Enterprise;
Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Enterprise::Target;
const Target *const enterprise_target = dynamic_cast<const Target *>(target);
if(enterprise_target->dos == Target::DOS::None)
return new Enterprise::ConcreteMachine<false>(*enterprise_target, rom_fetcher);
else
return new Enterprise::ConcreteMachine<true>(*enterprise_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -0,0 +1,47 @@
//
// Enterprise.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Enterprise_hpp
#define Enterprise_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include "../ROMMachine.hpp"
namespace Enterprise {
/*!
@abstract Represents an Elan Enterprise.
@discussion An instance of Enterprise::Machine represents the current state of an
Elan Enterprise.
*/
class Machine {
public:
virtual ~Machine();
static Machine *Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
/// Defines the runtime options available for an Enterprise.
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour) {
if(needs_declare()) {
declare_display_option();
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, Configurable::Display::CompositeMonochrome, -1);
}
}
};
};
};
#endif /* Enterprise_hpp */

View File

@@ -0,0 +1,151 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Enterprise;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
switch(key) {
default: break;
BIND(Backslash, Backslash);
BIND(CapsLock, Lock);
BIND(Tab, Tab);
BIND(Escape, Escape);
BIND(Hyphen, Hyphen);
BIND(Equals, Caret);
BIND(Backspace, Erase);
BIND(Delete, Delete);
BIND(Semicolon, Semicolon);
BIND(Quote, Colon);
BIND(OpenSquareBracket, OpenSquareBracket);
BIND(CloseSquareBracket, CloseSquareBracket);
BIND(End, Stop);
BIND(Insert, Insert);
BIND(BackTick, At);
BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); BIND(k5, k5);
BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); BIND(k0, k0);
BIND(F1, F1); BIND(F2, F2); BIND(F3, F3); BIND(F4, F4);
BIND(F5, F5); BIND(F6, F6); BIND(F7, F7); BIND(F8, F8);
BIND(Keypad1, F1); BIND(Keypad2, F2); BIND(Keypad3, F3); BIND(Keypad4, F4);
BIND(Keypad5, F5); BIND(Keypad6, F6); BIND(Keypad7, F7); BIND(Keypad8, F8);
BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T);
BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P);
BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G);
BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L);
BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V);
BIND(B, B); BIND(N, N); BIND(M, M);
BIND(FullStop, FullStop);
BIND(Comma, Comma);
BIND(ForwardSlash, ForwardSlash);
BIND(Space, Space); BIND(Enter, Enter);
BIND(LeftShift, LeftShift);
BIND(RightShift, RightShift);
BIND(LeftOption, Alt);
BIND(RightOption, Alt);
BIND(LeftControl, Control);
BIND(RightControl, Control);
BIND(Left, Left);
BIND(Right, Right);
BIND(Up, Up);
BIND(Down, Down);
}
#undef BIND
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}
const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#define KEYS(x) {uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define SHIFT(x) {uint16_t(Key::LeftShift), uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define _ {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ _, /* SOH */ _,
/* STX */ _, /* ETX */ _,
/* EOT */ _, /* ENQ */ _,
/* ACK */ _, /* BEL */ _,
/* BS */ KEYS(Key::Erase), /* HT */ KEYS(Key::Tab),
/* LF */ KEYS(Key::Enter), /* VT */ _,
/* FF */ _, /* CR */ KEYS(Key::Enter),
/* SO */ _, /* SI */ _,
/* DLE */ _, /* DC1 */ _,
/* DC2 */ _, /* DC3 */ _,
/* DC4 */ _, /* NAK */ _,
/* SYN */ _, /* ETB */ _,
/* CAN */ _, /* EM */ _,
/* SUB */ _, /* ESC */ KEYS(Key::Escape),
/* FS */ _, /* GS */ _,
/* RS */ _, /* US */ _,
/* space */ KEYS(Key::Space), /* ! */ SHIFT(Key::k1),
/* " */ SHIFT(Key::k2), /* # */ _,
/* $ */ SHIFT(Key::k4), /* % */ SHIFT(Key::k5),
/* & */ SHIFT(Key::k6), /* ' */ SHIFT(Key::k7),
/* ( */ SHIFT(Key::k8), /* ) */ SHIFT(Key::k9),
/* * */ SHIFT(Key::Colon), /* + */ SHIFT(Key::Semicolon),
/* , */ KEYS(Key::Comma), /* - */ KEYS(Key::Hyphen),
/* . */ KEYS(Key::FullStop), /* / */ KEYS(Key::ForwardSlash),
/* 0 */ KEYS(Key::k0), /* 1 */ KEYS(Key::k1),
/* 2 */ KEYS(Key::k2), /* 3 */ KEYS(Key::k3),
/* 4 */ KEYS(Key::k4), /* 5 */ KEYS(Key::k5),
/* 6 */ KEYS(Key::k6), /* 7 */ KEYS(Key::k7),
/* 8 */ KEYS(Key::k8), /* 9 */ KEYS(Key::k9),
/* : */ KEYS(Key::Colon), /* ; */ KEYS(Key::Semicolon),
/* < */ SHIFT(Key::Comma), /* = */ SHIFT(Key::Hyphen),
/* > */ SHIFT(Key::FullStop), /* ? */ SHIFT(Key::ForwardSlash),
/* @ */ KEYS(Key::At), /* A */ SHIFT(Key::A),
/* B */ SHIFT(Key::B), /* C */ SHIFT(Key::C),
/* D */ SHIFT(Key::D), /* E */ SHIFT(Key::E),
/* F */ SHIFT(Key::F), /* G */ SHIFT(Key::G),
/* H */ SHIFT(Key::H), /* I */ SHIFT(Key::I),
/* J */ SHIFT(Key::J), /* K */ SHIFT(Key::K),
/* L */ SHIFT(Key::L), /* M */ SHIFT(Key::M),
/* N */ SHIFT(Key::N), /* O */ SHIFT(Key::O),
/* P */ SHIFT(Key::P), /* Q */ SHIFT(Key::Q),
/* R */ SHIFT(Key::R), /* S */ SHIFT(Key::S),
/* T */ SHIFT(Key::T), /* U */ SHIFT(Key::U),
/* V */ SHIFT(Key::V), /* W */ SHIFT(Key::W),
/* X */ SHIFT(Key::X), /* Y */ SHIFT(Key::Y),
/* Z */ SHIFT(Key::Z), /* [ */ KEYS(Key::OpenSquareBracket),
/* \ */ KEYS(Key::Backslash), /* ] */ KEYS(Key::CloseSquareBracket),
/* ^ */ SHIFT(Key::Caret), /* _ */ SHIFT(Key::k0),
/* ` */ SHIFT(Key::At), /* a */ KEYS(Key::A),
/* b */ KEYS(Key::B), /* c */ KEYS(Key::C),
/* d */ KEYS(Key::D), /* e */ KEYS(Key::E),
/* f */ KEYS(Key::F), /* g */ KEYS(Key::G),
/* h */ KEYS(Key::H), /* i */ KEYS(Key::I),
/* j */ KEYS(Key::J), /* k */ KEYS(Key::K),
/* l */ KEYS(Key::L), /* m */ KEYS(Key::M),
/* n */ KEYS(Key::N), /* o */ KEYS(Key::O),
/* p */ KEYS(Key::P), /* q */ KEYS(Key::Q),
/* r */ KEYS(Key::R), /* s */ KEYS(Key::S),
/* t */ KEYS(Key::T), /* u */ KEYS(Key::U),
/* v */ KEYS(Key::V), /* w */ KEYS(Key::W),
/* x */ KEYS(Key::X), /* y */ KEYS(Key::Y),
/* z */ KEYS(Key::Z), /* { */ SHIFT(Key::OpenSquareBracket),
/* | */ SHIFT(Key::Backslash), /* } */ SHIFT(Key::CloseSquareBracket),
/* ~ */ SHIFT(Key::Caret)
};
#undef _
#undef SHIFT
#undef KEYS
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -0,0 +1,66 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Machines_Enterprise_Keyboard_hpp
#define Machines_Enterprise_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace Enterprise {
#define KeyCode(line, mask) (line << 8) | mask
enum class Key: uint16_t {
N = 0x0000 | 0x01, Backslash = 0x0000 | 0x02, B = 0x0000 | 0x04, C = 0x0000 | 0x08,
V = 0x0000 | 0x10, X = 0x0000 | 0x20, Z = 0x0000 | 0x40, LeftShift = 0x0000 | 0x80,
H = 0x0100 | 0x01, Lock = 0x0100 | 0x02, G = 0x0100 | 0x04, D = 0x0100 | 0x08,
F = 0x0100 | 0x10, S = 0x0100 | 0x20, A = 0x0100 | 0x40, Control = 0x0100 | 0x80,
U = 0x0200 | 0x01, Q = 0x0200 | 0x02, Y = 0x0200 | 0x04, R = 0x0200 | 0x08,
T = 0x0200 | 0x10, E = 0x0200 | 0x20, W = 0x0200 | 0x40, Tab = 0x0200 | 0x80,
k7 = 0x0300 | 0x01, k1 = 0x0300 | 0x02, k6 = 0x0300 | 0x04, k4 = 0x0300 | 0x08,
k5 = 0x0300 | 0x10, k3 = 0x0300 | 0x20, k2 = 0x0300 | 0x40, Escape = 0x0300 | 0x80,
F4 = 0x0400 | 0x01, F8 = 0x0400 | 0x02, F3 = 0x0400 | 0x04, F6 = 0x0400 | 0x08,
F5 = 0x0400 | 0x10, F7 = 0x0400 | 0x20, F2 = 0x0400 | 0x40, F1 = 0x0400 | 0x80,
k8 = 0x0500 | 0x01, k9 = 0x0500 | 0x04, Hyphen = 0x0500 | 0x08,
k0 = 0x0500 | 0x10, Caret = 0x0500 | 0x20, Erase = 0x0500 | 0x40,
J = 0x0600 | 0x01, K = 0x0600 | 0x04, Semicolon = 0x0600 | 0x08,
L = 0x0600 | 0x10, Colon = 0x0600 | 0x20, CloseSquareBracket = 0x0600 | 0x40,
Stop = 0x0700 | 0x01, Down = 0x0700 | 0x02, Right = 0x0700 | 0x04, Up = 0x0700 | 0x08,
Hold = 0x0700 | 0x10, Left = 0x0700 | 0x20, Enter = 0x0700 | 0x40, Alt = 0x0700 | 0x80,
M = 0x0800 | 0x01, Delete = 0x0800 | 0x02, Comma = 0x0800 | 0x04,
ForwardSlash = 0x0800 | 0x08,
FullStop = 0x0800 | 0x10,
RightShift = 0x0800 | 0x20, Space = 0x0800 | 0x40, Insert = 0x0800 | 0x80,
I = 0x0900 | 0x01, O = 0x0900 | 0x04, At = 0x0900 | 0x08,
P = 0x0900 | 0x10,
OpenSquareBracket = 0x0900 | 0x20
};
#undef KeyCode
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
struct CharacterMapper: public ::Utility::CharacterMapper {
const uint16_t *sequence_for_character(char character) const override;
};
}
#endif /* Keyboard_hpp */

View File

@@ -0,0 +1,629 @@
//
// Nick.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Nick.hpp"
#include <cstdio>
namespace {
uint16_t mapped_colour(uint8_t source) {
// On the Enterprise, red and green are 3-bit quantities; blue is a 2-bit quantity.
int red = ((source&0x01) << 2) | ((source&0x08) >> 2) | ((source&0x40) >> 6);
int green = ((source&0x02) << 1) | ((source&0x10) >> 3) | ((source&0x80) >> 7);
int blue = ((source&0x04) >> 1) | ((source&0x20) >> 5);
assert(red <= 7);
assert(green <= 7);
assert(blue <= 3);
red = (red << 1) + (red >> 3);
green = (green << 1) + (green >> 3);
blue = (blue << 2) + blue;
assert(red <= 15);
assert(green <= 15);
assert(blue <= 15);
// Duplicate bits where necessary to map to a full 4-bit range per channel.
const uint8_t parts[2] = {
uint8_t(
red
),
uint8_t(
(green << 4) + blue
)
};
return *reinterpret_cast<const uint16_t *>(parts);
}
}
using namespace Enterprise;
Nick::Nick(const uint8_t *ram) :
crt_(57*16, 16, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
ram_(ram) {
// Just use RGB for now.
set_display_type(Outputs::Display::DisplayType::RGB);
// Crop to the centre 90% of the display.
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
}
void Nick::write(uint16_t address, uint8_t value) {
switch(address & 3) {
case 0:
// Ignored: everything to do with external colour.
for(int c = 0; c < 8; c++) {
palette_[c + 8] = mapped_colour(uint8_t(((value & 0x1f) << 3) + c));
}
break;
case 1:
if(output_type_ == OutputType::Border) {
set_output_type(OutputType::Border, true);
}
border_colour_ = mapped_colour(value);
break;
case 2:
line_parameter_base_ = uint16_t((line_parameter_base_ & 0xf000) | (value << 4));
break;
case 3:
line_parameter_base_ = uint16_t((line_parameter_base_ & 0x0ff0) | (value << 12));
// Still a mystery to me: the exact meaning of the top two bits here. For now
// just treat a 0 -> 1 transition of the MSB as a forced frame restart.
if((value^line_parameter_control_) & value & 0x80) {
// For now: just force this to be the final line of this mode block.
// I'm unclear whether I should also reset the horizontal counter
// (i.e. completely abandon current video phase).
lines_remaining_ = 0xff;
should_reload_line_parameters_ = true;
}
line_parameter_control_ = value & 0xc0;
break;
}
}
uint8_t Nick::read() {
return last_read_;
}
Cycles Nick::get_time_until_z80_slot(Cycles after_period) const {
// Place Z80 accesses in the first six cycles in each sixteen-cycle window.
// That models video accesses as being the final ten. Which has the net effect
// of responding to the line parameter table interrupt flag as soon as it's
// loaded.
// Assumed below: the Z80 can start its final cycle anywhere in the first three
// of the permitted six.
const int offset = (horizontal_counter_ + after_period.as<int>()) & 15;
if(offset < 3) {
return 0;
} else {
return 16 - offset;
}
}
void Nick::run_for(Cycles duration) {
constexpr int line_length = 912;
#define add_window(x) \
line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \
line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \
window += x; \
if(window != 57 && window == left_margin_) is_sync_or_pixels_ = true; \
if(window != 57 && window == right_margin_) is_sync_or_pixels_ = false;
int clocks_remaining = duration.as<int>();
while(clocks_remaining) {
// Determine how many cycles are left this line.
const int clocks_this_line = std::min(clocks_remaining, line_length - horizontal_counter_);
// Convert that into a [start/current] and end window.
int window = horizontal_counter_ >> 4;
const int end_window = (horizontal_counter_ + clocks_this_line) >> 4;
// Advance the line counters.
clocks_remaining -= clocks_this_line;
horizontal_counter_ = (horizontal_counter_ + clocks_this_line) % line_length;
// Do nothing if a window boundary isn't crossed.
if(window == end_window) continue;
// HSYNC is signalled for four windows at the start of the line.
// I currently believe this happens regardless of Vsync mode.
if(!window) {
set_output_type(OutputType::Sync);
// There's no increment to get to 0, it happens when the horizontal_counter_
// is reset. So test for active bit effect manually.
if(!left_margin_) is_sync_or_pixels_ = true;
if(!right_margin_) is_sync_or_pixels_ = false;
}
// Default to noting read.
last_read_ = 0xff;
while(window < 4 && window < end_window) {
if(should_reload_line_parameters_) {
switch(window) {
// First slot: line count, mode and interrupt flag.
case 0:
// Byte 0: lines remaining.
lines_remaining_ = ram_[line_parameter_pointer_];
// Byte 1: current interrupt output plus graphics modes...
last_read_ = ram_[line_parameter_pointer_ + 1];
// Set the new interrupt line output.
interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80;
// Determine the mode and depth, and hence the column size.
mode_ = Mode((ram_[line_parameter_pointer_ + 1] >> 1)&7);
bpp_ = 1 << ((ram_[line_parameter_pointer_ + 1] >> 5)&3);
switch(mode_) {
default:
case Mode::Pixel:
column_size_ = 16 / bpp_;
line_data_per_column_increments_[0] = 2;
line_data_per_column_increments_[1] = 0;
break;
case Mode::LPixel:
case Mode::CH64:
case Mode::CH128:
case Mode::CH256:
column_size_ = 8 / bpp_;
line_data_per_column_increments_[0] = 1;
line_data_per_column_increments_[1] = 0;
break;
case Mode::Attr:
column_size_ = 8;
line_data_per_column_increments_[0] = 1;
line_data_per_column_increments_[1] = 1;
break;
}
vres_ = ram_[line_parameter_pointer_ + 1] & 0x10;
reload_line_parameter_pointer_ = ram_[line_parameter_pointer_ + 1] & 0x01;
break;
// Second slot: margins and ALT/IND bits.
case 1:
// Determine the margins.
left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f;
right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f;
last_read_ = ram_[line_parameter_pointer_ + 3];
// Set up the alternative palettes,
switch(mode_) {
default:
break;
// NB: LSBALT/MSBALT and ALTIND0/ALTIND1 appear to have opposite effects on palette selection.
case Mode::Pixel:
case Mode::LPixel: {
const uint8_t flags = ram_[line_parameter_pointer_ + 2];
// Use MSBALT and LSBALT to pick the alt_ind_palettes.
//
// LSBALT = b6 of params[2], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
// MSBALT = b7 of params[2], if set => character codes with bit 7 set should use palette indices 2 and 3.
two_colour_mask_ = 0xff &~ (((flags&0x80) >> 7) | ((flags&0x40) << 1));
alt_ind_palettes[0] = palette_;
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x80) ? 2 : 0);
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x40) ? 4 : 0);
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x40) ? 4 : 0);
} break;
case Mode::CH64:
case Mode::CH128:
case Mode::CH256: {
const uint8_t flags = ram_[line_parameter_pointer_ + 3];
// Use ALTIND0 and ALTIND1 to pick the alt_ind_palettes.
//
// ALTIND1 = b6 of params[3], if set => character codes with bit 7 set should use palette indices 2 and 3.
// ALTIND0 = b7 of params[3], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
alt_ind_palettes[0] = palette_;
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x40) ? 2 : 0);
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x80) ? 4 : 0);
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x80) ? 4 : 0);
} break;
}
break;
// Third slot: Line data pointer 1.
case 2:
start_line_data_pointer_[0] = ram_[line_parameter_pointer_ + 4];
start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8;
line_data_pointer_[0] = start_line_data_pointer_[0];
last_read_ = ram_[line_parameter_pointer_ + 5];
break;
// Fourth slot: Line data pointer 2.
case 3:
start_line_data_pointer_[1] = ram_[line_parameter_pointer_ + 6];
start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8;
line_data_pointer_[1] = start_line_data_pointer_[1];
last_read_ = ram_[line_parameter_pointer_ + 7];
break;
}
}
++output_duration_;
add_window(1);
}
if(window == 4) {
if(mode_ == Mode::Vsync) {
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
} else {
set_output_type(OutputType::Blank);
}
}
// Deal with vsync mode out here.
if(mode_ == Mode::Vsync) {
if(window >= 4) {
while(window < end_window) {
// Skip straight to the next event.
int next_event = end_window;
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
output_duration_ += next_event - window;
add_window(next_event - window);
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
}
}
} else {
// If present then the colour burst is output for the period from
// the start of window 6 to the end of window 10.
//
// The first 8 palette entries also need to be fetched here.
while(window < first_pixel_window_ && window < end_window) {
if(window == 6) {
set_output_type(OutputType::ColourBurst);
}
if(should_reload_line_parameters_ && window < 8) {
const int base = (window - 4) << 1;
assert(base < 7);
palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]);
palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]);
last_read_ = ram_[line_parameter_pointer_ + base + 9];
}
++output_duration_;
add_window(1);
}
if(window >= first_pixel_window_) {
if(window == first_pixel_window_) {
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
}
while(window < end_window) {
int next_event = end_window;
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
if(is_sync_or_pixels_) {
#define DispatchBpp(func) \
switch(bpp_) { \
default: \
case 1: func(1)(pixel_pointer_, output_duration); break; \
case 2: func(2)(pixel_pointer_, output_duration); break; \
case 4: func(4)(pixel_pointer_, output_duration); break; \
case 8: func(8)(pixel_pointer_, output_duration); break; \
}
#define pixel(x) output_pixel<x, false>
#define lpixel(x) output_pixel<x, true>
#define ch256(x) output_character<x, 8>
#define ch128(x) output_character<x, 7>
#define ch64(x) output_character<x, 6>
#define attr(x) output_attributed<x>
int columns_remaining = next_event - window;
while(columns_remaining) {
if(!pixel_pointer_) {
if(output_duration_) {
set_output_type(OutputType::Pixels, true);
}
pixel_pointer_ = allocated_pointer_ = reinterpret_cast<uint16_t *>(crt_.begin_data(allocation_size));
}
if(allocated_pointer_) {
const int output_duration = std::min(columns_remaining, int(allocated_pointer_ + allocation_size - pixel_pointer_) / column_size_);
switch(mode_) {
default:
case Mode::Pixel: DispatchBpp(pixel); break;
case Mode::LPixel: DispatchBpp(lpixel); break;
case Mode::CH256: DispatchBpp(ch256); break;
case Mode::CH128: DispatchBpp(ch128); break;
case Mode::CH64: DispatchBpp(ch64); break;
case Mode::Attr: DispatchBpp(attr); break;
}
pixel_pointer_ += output_duration * column_size_;
output_duration_ += output_duration;
if(pixel_pointer_ - allocated_pointer_ == allocation_size) {
set_output_type(OutputType::Pixels, true);
}
columns_remaining -= output_duration;
add_window(output_duration);
} else {
output_duration_ += columns_remaining;
add_window(columns_remaining);
columns_remaining = 0;
}
}
#undef attr
#undef ch64
#undef ch128
#undef ch256
#undef pixel
#undef lpixel
#undef DispatchBpp
} else {
output_duration_ += next_event - window;
add_window(next_event - window);
}
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
}
}
}
// Check for end of line.
if(!horizontal_counter_) {
assert(window == 57);
++lines_remaining_;
if(!lines_remaining_) {
should_reload_line_parameters_ = true;
// Check for end-of-frame.
if(reload_line_parameter_pointer_) {
line_parameter_pointer_ = line_parameter_base_;
} else {
line_parameter_pointer_ += 16;
}
} else {
should_reload_line_parameters_ = false;
}
// Deal with VRES and other address reloading, dependant upon mode.
switch(mode_) {
default: break;
case Mode::CH64:
case Mode::CH128:
case Mode::CH256:
line_data_pointer_[0] = start_line_data_pointer_[0];
++line_data_pointer_[1];
break;
case Mode::Pixel:
case Mode::LPixel:
case Mode::Attr:
// Reload the pixel or attribute address if VRES is clear.
if(!vres_) {
line_data_pointer_[0] = start_line_data_pointer_[0];
}
break;
}
}
}
#undef add_window
}
void Nick::set_output_type(OutputType type, bool force_flush) {
if(type == output_type_ && !force_flush) {
return;
}
if(output_duration_) {
switch(output_type_) {
case OutputType::Border: {
uint16_t *const colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = border_colour_;
crt_.output_level(output_duration_*16);
} break;
case OutputType::Pixels: {
crt_.output_data(output_duration_*16, size_t(output_duration_*column_size_));
pixel_pointer_ = nullptr;
allocated_pointer_ = nullptr;
} break;
case OutputType::Sync: crt_.output_sync(output_duration_*16); break;
case OutputType::Blank: crt_.output_blank(output_duration_*16); break;
case OutputType::ColourBurst: crt_.output_colour_burst(output_duration_*16, 0); break;
}
}
output_duration_ = 0;
output_type_ = type;
}
// MARK: - Sequence points.
Cycles Nick::get_next_sequence_point() const {
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
// interrupt line may change. That is, after the
// second byte of the mode line has been read.
// Any mode line may cause a change in the interrupt output, so as a first blush
// just always report the time until the end of the mode line.
if(lines_remaining_ || horizontal_counter_ >= load_point) {
return Cycles(load_point + (912 - horizontal_counter_) + (0xff - lines_remaining_) * 912);
} else {
return Cycles(load_point - horizontal_counter_);
}
}
// MARK: - CRT passthroughs.
void Nick::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Nick::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status();
}
void Nick::set_display_type(Outputs::Display::DisplayType display_type) {
first_pixel_window_ = display_type == Outputs::Display::DisplayType::RGB ? 8 : 10;
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType Nick::get_display_type() const {
return crt_.get_display_type();
}
// MARK: - Specific pixel outputters.
#define output1bpp(x) \
target[0] = palette[(x & 0x80) >> 7]; \
target[1] = palette[(x & 0x40) >> 6]; \
target[2] = palette[(x & 0x20) >> 5]; \
target[3] = palette[(x & 0x10) >> 4]; \
target[4] = palette[(x & 0x08) >> 3]; \
target[5] = palette[(x & 0x04) >> 2]; \
target[6] = palette[(x & 0x02) >> 1]; \
target[7] = palette[(x & 0x01) >> 0]; \
target += 8
#define output2bpp(x) \
target[0] = palette_[((x & 0x80) >> 7) | ((x & 0x08) >> 2)]; \
target[1] = palette_[((x & 0x40) >> 6) | ((x & 0x04) >> 1)]; \
target[2] = palette_[((x & 0x20) >> 5) | ((x & 0x02) >> 0)]; \
target[3] = palette_[((x & 0x10) >> 4) | ((x & 0x01) << 1)]; \
target += 4
#define output4bpp(x) \
target[0] = palette_[((x & 0x02) << 2) | ((x & 0x20) >> 3) | ((x & 0x08) >> 2) | ((x & 0x80) >> 7)]; \
target[1] = palette_[((x & 0x01) << 3) | ((x & 0x10) >> 2) | ((x & 0x04) >> 1) | ((x & 0x40) >> 6)]; \
target += 2
#define output8bpp(x) \
target[0] = mapped_colour(x); \
++target
template <int bpp, bool is_lpixel> void Nick::output_pixel(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
int index = 0;
for(int c = 0; c < columns; c++) {
uint8_t pixels[2] = {
ram_[(line_data_pointer_[0] + index) & 0xffff],
ram_[(line_data_pointer_[0] + index + 1) & 0xffff]
};
index += is_lpixel ? 1 : 2;
last_read_ = pixels[1];
switch(bpp) {
default:
case 1: {
const uint16_t *palette = alt_ind_palettes[((pixels[0] >> 6) & 0x02) | (pixels[0]&1)];
pixels[0] &= two_colour_mask_;
output1bpp(pixels[0]);
if constexpr (!is_lpixel) {
palette = alt_ind_palettes[((pixels[1] >> 6) & 0x02) | (pixels[1]&1)];
pixels[1] &= two_colour_mask_;
output1bpp(pixels[1]);
}
} break;
case 2:
output2bpp(pixels[0]);
if constexpr (!is_lpixel) {
output2bpp(pixels[1]);
}
break;
case 4:
output4bpp(pixels[0]);
if constexpr (!is_lpixel) {
output4bpp(pixels[1]);
}
break;
case 8:
output8bpp(pixels[0]);
if constexpr (!is_lpixel) {
output8bpp(pixels[1]);
}
break;
}
}
}
template <int bpp, int index_bits> void Nick::output_character(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
for(int c = 0; c < columns; c++) {
const uint8_t character = ram_[(line_data_pointer_[0] + c) & 0xffff];
const uint8_t pixels = ram_[(
(line_data_pointer_[1] << index_bits) +
(character & ((1 << index_bits) - 1))
) & 0xffff];
last_read_ = pixels;
switch(bpp) {
default:
assert(false);
break;
case 1: {
// This applies ALTIND0 and ALTIND1.
const uint16_t *palette = alt_ind_palettes[character >> 6];
output1bpp(pixels);
} break;
case 2: output2bpp(pixels); break;
case 4: output4bpp(pixels); break;
case 8: output8bpp(pixels); break;
}
}
}
template <int bpp> void Nick::output_attributed(uint16_t *target, int columns) const {
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
for(int c = 0; c < columns; c++) {
const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff];
const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff];
last_read_ = pixels;
const uint16_t palette[2] = {
palette_[attributes >> 4], palette_[attributes & 0x0f]
};
output1bpp(pixels);
}
}
#undef output1bpp
#undef output2bpp
#undef output4bpp
#undef output8bpp

View File

@@ -0,0 +1,129 @@
//
// Nick.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Nick_hpp
#define Nick_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/CRT/CRT.hpp"
namespace Enterprise {
class Nick {
public:
Nick(const uint8_t *ram);
/// Writes to a Nick register; only the low two bits are decoded.
void write(uint16_t address, uint8_t value);
/// Reads from the Nick range. Nobody seems to be completely clear what
/// this should return; I've set it up to return the last fetched video or mode
/// line byte during periods when those things are being fetched, 0xff at all
/// other times. Including during refresh, since I don't know what addresses
/// are generated then.
///
/// This likely isn't accurate, but is the most accurate guess I could make.
uint8_t read();
void run_for(Cycles);
Cycles get_time_until_z80_slot(Cycles after_period) const;
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// @returns The amount of time until the next potential change in interrupt output.
Cycles get_next_sequence_point() const;
/*!
@returns The current state of the interrupt line — @c true for active;
@c false for inactive.
*/
inline bool get_interrupt_line() const {
return interrupt_line_;
}
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
private:
Outputs::CRT::CRT crt_;
const uint8_t *const ram_;
// CPU-provided state.
uint8_t line_parameter_control_ = 0xc0;
uint16_t line_parameter_base_ = 0x0000;
uint16_t border_colour_ = 0;
// Ephemerals, related to current video position.
int horizontal_counter_ = 0;
uint16_t line_parameter_pointer_ = 0x0000;
bool should_reload_line_parameters_ = true;
uint16_t line_data_pointer_[2];
uint16_t start_line_data_pointer_[2];
mutable uint8_t last_read_ = 0xff;
// Current mode line parameters.
uint8_t lines_remaining_ = 0x00;
uint8_t two_colour_mask_ = 0xff;
int left_margin_ = 0, right_margin_ = 0;
const uint16_t *alt_ind_palettes[4];
enum class Mode {
Vsync,
Pixel,
Attr,
CH256,
CH128,
CH64,
Unused,
LPixel,
} mode_ = Mode::Vsync;
bool is_sync_or_pixels_ = false;
int bpp_ = 0;
int column_size_ = 0;
bool interrupt_line_ = true;
int line_data_per_column_increments_[2] = {0, 0};
bool vres_ = false;
bool reload_line_parameter_pointer_ = false;
// An accumulator for border output regions.
int border_duration_ = 0;
// The destination for new pixels.
static constexpr int allocation_size = 336;
static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16");
uint16_t *pixel_pointer_ = nullptr, *allocated_pointer_ = nullptr;
// Output transitions.
enum class OutputType {
Sync, Blank, Pixels, Border, ColourBurst
};
void set_output_type(OutputType, bool force_flush = false);
int output_duration_ = 0;
OutputType output_type_ = OutputType::Sync;
// Current palette.
uint16_t palette_[16]{};
// The first column with pixels on it; will be either 8 or 10 depending
// on whether the colour burst is meaningful to the current display type.
int first_pixel_window_ = 10;
// Specific outputters.
template <int bpp, bool is_lpixel> void output_pixel(uint16_t *target, int columns) const;
template <int bpp, int index_bits> void output_character(uint16_t *target, int columns) const;
template <int bpp> void output_attributed(uint16_t *target, int columns) const;
};
}
#endif /* Nick_hpp */

View File

@@ -169,19 +169,22 @@ class ConcreteMachine:
// Install the proper TV standard and select an ideal BIOS name.
const std::string machine_name = "MSX";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3}
};
ROM::Request bios_request = ROM::Request(ROM::Name::MSXGenericBIOS);
// std::vector<ROMMachine::ROM> required_roms = {
// {machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u}
// };
bool is_ntsc = true;
uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */
uint8_t date_format = 1; /* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */
uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */
ROM::Name regional_bios_name;
// TODO: CRCs below are incomplete, at best.
switch(target.region) {
default:
case Target::Region::Japan:
required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390);
regional_bios_name = ROM::Name::MSXJapaneseBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
@@ -189,7 +192,7 @@ class ConcreteMachine:
date_format = 0;
break;
case Target::Region::USA:
required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0);
regional_bios_name = ROM::Name::MSXAmericanBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
@@ -197,7 +200,7 @@ class ConcreteMachine:
date_format = 1;
break;
case Target::Region::Europe:
required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0);
regional_bios_name = ROM::Name::MSXEuropeanBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::PAL);
is_ntsc = false;
@@ -205,27 +208,30 @@ class ConcreteMachine:
date_format = 2;
break;
}
bios_request = bios_request || ROM::Request(regional_bios_name);
// Fetch the necessary ROMs; try the region-specific ROM first,
// but failing that fall back on patching the main one.
size_t disk_index = 0;
ROM::Request request;
if(target.has_disk_drive) {
disk_index = required_roms.size();
required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df);
request = ROM::Request(ROM::Name::MSXDOS) && bios_request;
} else {
request = bios_request;
}
const auto roms = rom_fetcher(required_roms);
if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) {
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
// Figure out which BIOS to use, either a specific one or the generic
// one appropriately patched.
if(roms[1]) {
memory_slots_[0].source = std::move(*roms[1]);
const auto regional_bios = roms.find(regional_bios_name);
if(regional_bios != roms.end()) {
memory_slots_[0].source = std::move(regional_bios->second);
memory_slots_[0].source.resize(32768);
} else {
memory_slots_[0].source = std::move(*roms[0]);
memory_slots_[0].source = std::move(roms.find(ROM::Name::MSXGenericBIOS)->second);
memory_slots_[0].source.resize(32768);
memory_slots_[0].source[0x2b] = uint8_t(
@@ -252,7 +258,7 @@ class ConcreteMachine:
// Add a disk cartridge if any disks were supplied.
if(target.has_disk_drive) {
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
memory_slots_[2].source = std::move(*roms[disk_index]);
memory_slots_[2].source = std::move(roms.find(ROM::Name::MSXDOS)->second);
memory_slots_[2].source.resize(16384);
map(2, 0, 0x4000, 0x2000);

View File

@@ -20,6 +20,7 @@
#include "MediaTarget.hpp"
#include "MouseMachine.hpp"
#include "ScanProducer.hpp"
#include "StateProducer.hpp"
#include "TimedMachine.hpp"
#endif /* MachineTypes_h */

View File

@@ -141,23 +141,20 @@ class ConcreteMachine:
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const auto roms = rom_fetcher(
{ {"MasterSystem",
is_japanese ? "the Japanese Master System BIOS" : "the European/US Master System BIOS",
is_japanese ? "japanese-bios.sms" : "bios.sms",
8*1024,
{ is_japanese ? 0x48d44a13u : 0x0072ed54u }
} }
);
if(!roms[0]) {
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
roms[0]->resize(8*1024);
memcpy(&bios_, roms[0]->data(), roms[0]->size());
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();

View File

@@ -127,5 +127,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -40,6 +40,45 @@
#include <memory>
#include <vector>
namespace {
/*!
Provides an Altai-style joystick.
*/
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
#define APPLY(b) if(is_active) state_ &= ~b; else state_ |= b;
switch(digital_input.type) {
default: return;
case Input::Right: APPLY(0x02); break;
case Input::Left: APPLY(0x01); break;
case Input::Down: APPLY(0x08); break;
case Input::Up: APPLY(0x10); break;
case Input::Fire: APPLY(0x20); break;
}
#undef APPLY
}
uint8_t get_state() {
return state_;
}
private:
uint8_t state_ = 0xff;
};
}
namespace Oric {
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
@@ -140,7 +179,12 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
public:
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard)
{
// Attach a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
/*!
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
@@ -165,6 +209,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
} else {
update_ay();
ay8910_.set_data_input(value);
porta_output_ = value;
}
}
@@ -176,7 +221,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
uint8_t column = ay8910_.get_port_output(false) ^ 0xff;
return keyboard_.query_column(column) ? 0x08 : 0x00;
} else {
return ay8910_.get_data_output();
uint8_t result = ay8910_.get_data_output();
if(porta_output_ & 0x40) result &= static_cast<Joystick *>(joysticks_[0].get())->get_state();
if(porta_output_ & 0x80) result &= static_cast<Joystick *>(joysticks_[1].get())->get_state();
return result;
}
}
@@ -193,12 +241,17 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
audio_queue_.perform();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
void update_ay() {
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
}
bool ay_bdir_ = false;
bool ay_bc1_ = false;
uint8_t porta_output_ = 0xff;
HalfCycles cycles_since_ay_update_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -206,12 +259,15 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
Speaker &speaker_;
TapePlayer &tape_player_;
Keyboard &keyboard_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS6502Esque::Type processor_type> class ConcreteMachine:
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MediaTarget,
public MachineTypes::MappedKeyboardMachine,
public Configurable::Device,
@@ -248,73 +304,64 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
ram_[c] |= 0x40;
}
const std::string machine_name = "Oric";
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
::ROM::Request request = ::ROM::Request(::ROM::Name::OricColourROM, true);
::ROM::Name basic;
switch(target.rom) {
case Analyser::Static::Oric::Target::ROM::BASIC10:
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
break;
case Analyser::Static::Oric::Target::ROM::BASIC11:
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
break;
case Analyser::Static::Oric::Target::ROM::Pravetz:
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
break;
case Analyser::Static::Oric::Target::ROM::BASIC10: basic = ::ROM::Name::OricBASIC10; break;
default:
case Analyser::Static::Oric::Target::ROM::BASIC11: basic = ::ROM::Name::OricBASIC11; break;
case Analyser::Static::Oric::Target::ROM::Pravetz: basic = ::ROM::Name::OricPravetzBASIC; break;
}
size_t diskii_state_machine_index = 0;
request = request && ::ROM::Request(basic);
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
rom_names.emplace_back(machine_name, "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
request = request && ::ROM::Request(::ROM::Name::OricByteDrive500);
break;
case DiskInterface::Jasmin:
rom_names.emplace_back(machine_name, "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
request = request && ::ROM::Request(::ROM::Name::OricJasmin);
break;
case DiskInterface::Microdisc:
rom_names.emplace_back(machine_name, "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
request = request && ::ROM::Request(::ROM::Name::OricMicrodisc);
break;
case DiskInterface::Pravetz:
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
// These ROM details are coupled with those in the DiskIICard.
diskii_state_machine_index = rom_names.size();
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
request = request && ::ROM::Request(::ROM::Name::Oric8DOSBoot) && ::ROM::Request(::ROM::Name::DiskIIStateMachine16Sector);
break;
}
const auto roms = rom_fetcher(rom_names);
for(std::size_t index = 0; index < roms.size(); ++index) {
if(!roms[index]) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
video_->set_colour_rom(*roms[0]);
rom_ = std::move(*roms[1]);
// The colour ROM is optional; an alternative composite encoding can be used if
// it is absent.
const auto colour_rom = roms.find(::ROM::Name::OricColourROM);
if(colour_rom != roms.end()) {
video_->set_colour_rom(colour_rom->second);
}
rom_ = std::move(roms.find(basic)->second);
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
disk_rom_ = std::move(roms.find(::ROM::Name::OricByteDrive500)->second);
break;
case DiskInterface::Jasmin:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(2048);
disk_rom_ = std::move(roms.find(::ROM::Name::OricJasmin)->second);
break;
case DiskInterface::Microdisc:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
disk_rom_ = std::move(roms.find(::ROM::Name::OricMicrodisc)->second);
break;
case DiskInterface::Pravetz: {
pravetz_rom_ = std::move(*roms[2]);
pravetz_rom_ = std::move(roms.find(::ROM::Name::Oric8DOSBoot)->second);
pravetz_rom_.resize(512);
diskii_->set_state_machine(*roms[diskii_state_machine_index]);
diskii_->set_state_machine(roms.find(::ROM::Name::DiskIIStateMachine16Sector)->second);
} break;
}
rom_.resize(16384);
paged_rom_ = rom_.data();
switch(target.disk_interface) {
@@ -731,8 +778,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
}
}
// MARK - typing
// MARK: - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK: - Joysticks
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return via_port_handler_.get_joysticks();
}
};
}

View File

@@ -54,7 +54,7 @@ void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
#ifdef SUPPLY_COMPOSITE
const auto data_type =
(display_type == Outputs::Display::DisplayType::RGB) ?
(!has_colour_rom_ || display_type == Outputs::Display::DisplayType::RGB) ?
Outputs::Display::InputDataType::Red1Green1Blue1 :
Outputs::Display::InputDataType::PhaseLinkedLuminance8;
#else
@@ -80,6 +80,7 @@ Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
has_colour_rom_ = true;
for(std::size_t c = 0; c < 8; c++) {
colour_forms_[c] = 0;

View File

@@ -37,6 +37,7 @@ class VideoOutput {
Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false;
bool has_colour_rom_ = false;
void update_crt_frequency();

View File

@@ -10,38 +10,16 @@
#define ROMMachine_hpp
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "Utility/ROMCatalogue.hpp"
namespace ROMMachine {
/*!
Describes a ROM image; this term is used in this emulator strictly in the sense of firmware —
system software that is an inherent part of a machine.
*/
struct ROM {
/// The machine with which this ROM is associated, in a form that is safe for using as
/// part of a file name.
std::string machine_name;
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
/// clause, etc, e.g. "the Electron MOS 1.0".
std::string descriptive_name;
/// An idiomatic file name for this ROM, e.g. "os10.rom".
std::string file_name;
/// The expected size of this ROM in bytes, e.g. 32768.
size_t size = 0;
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
/// to exclude ROMs where the user's intent is otherwise clear.
std::vector<uint32_t> crc32s;
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {}
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {}
};
/*!
Defines the signature for a function that must be supplied by the host environment in order to give machines
a route for fetching any system ROMs they might need.
@@ -50,7 +28,7 @@ struct ROM {
return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by
index, or else are @c nullptr.
*/
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher;
typedef std::function<ROM::Map(const ROM::Request &request)> ROMFetcher;
enum class Error {
MissingROMs

View File

@@ -275,13 +275,14 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
switch(machine_) {
case Machine::ZX80:
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
return table_lookup_sequence_for_character(zx80_key_sequences, character);
case Machine::ZX81:
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(zx81_key_sequences, character);
default:
case Machine::ZXSpectrum:
return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(spectrum_key_sequences, character);
}
}

View File

@@ -74,15 +74,13 @@ template<bool is_zx81> class ConcreteMachine:
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const auto roms =
use_zx81_rom ?
rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) :
rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} });
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
rom_ = std::move(*roms[0]);
rom_.resize(use_zx81_rom ? 8192 : 4096);
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(roms.find(rom_name)->second);
rom_mask_ = uint16_t(rom_.size() - 1);

View File

@@ -32,7 +32,7 @@ class Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickloadOption<Options> {
friend Configurable::QuickloadOption<Options>;
public:
bool automatic_tape_motor_control;
bool automatic_tape_motor_control = true;
Options(Configurable::OptionsType type):
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly),

View File

@@ -0,0 +1,53 @@
//
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_hpp
#define State_hpp
#include "../../../Reflection/Struct.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "Video.hpp"
#include "../../../Components/AY38910/AY38910.hpp"
namespace Sinclair {
namespace ZXSpectrum {
struct State: public Reflection::StructImpl<State> {
CPU::Z80::State z80;
Video::State video;
// In 16kb or 48kb mode, RAM will be 16kb or 48kb and represent
// memory in standard linear order. In 128kb mode, RAM will be
// 128kb with the first 16kb representing bank 0, the next bank 1, etc.
std::vector<uint8_t> ram;
// Meaningful for 128kb machines only.
uint8_t last_7ffd = 0;
GI::AY38910::State ay;
// Meaningful for the +2a and +3 only.
uint8_t last_1ffd = 0;
State() {
if(needs_declare()) {
DeclareField(z80);
DeclareField(video);
DeclareField(ram);
DeclareField(last_7ffd);
DeclareField(last_1ffd);
DeclareField(ay);
}
}
};
}
}
#endif /* State_h */

View File

@@ -12,12 +12,15 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Reflection/Struct.hpp"
#include <algorithm>
namespace Sinclair {
namespace ZXSpectrum {
namespace Video {
enum class VideoTiming {
enum class Timing {
FortyEightK,
OneTwoEightK,
Plus3,
@@ -47,11 +50,11 @@ enum class VideoTiming {
*/
template <VideoTiming timing> class Video {
template <Timing timing> class Video {
private:
struct Timings {
// Number of cycles per line. Will be 224 or 228.
int cycles_per_line;
int half_cycles_per_line;
// Number of lines comprising a whole frame. Will be 311 or 312.
int lines_per_frame;
@@ -63,128 +66,56 @@ template <VideoTiming timing> class Video {
// Number of cycles after first pixel fetch at which interrupt is first signalled.
int interrupt_time;
// Contention to apply, in half-cycles, as a function of number of half cycles since
// Contention to apply, in whole cycles, as a function of number of whole cycles since
// contention began.
int delays[16];
int delays[8];
constexpr Timings(int cycles_per_line, int lines_per_frame, int contention_leadin, int contention_duration, int interrupt_offset, const int *delays) noexcept :
half_cycles_per_line(cycles_per_line * 2),
lines_per_frame(lines_per_frame),
contention_leadin(contention_leadin * 2),
contention_duration(contention_duration * 2),
interrupt_time((cycles_per_line * lines_per_frame - interrupt_offset - contention_leadin) * 2),
delays{ delays[0] * 2, delays[1] * 2, delays[2] * 2, delays[3] * 2, delays[4] * 2, delays[5] * 2, delays[6] * 2, delays[7] * 2}
{}
};
static constexpr Timings get_timings() {
if constexpr (timing == VideoTiming::Plus3) {
// Amstrad gate array timings, classic statement:
//
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
// The first four bytes of video are fetched at 1436514368 cycles, in the order [pixels, attribute, pixels, attribute].
//
// For my purposes:
//
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
//
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
//
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
// of regular Z80 signalling.
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
// i.e. video fetching begins five cycles after the start of the
// contended memory pattern below; that should put a clear two
// cycles between a Z80 access and the first video fetch.
.contention_leadin = 5 * 2,
.contention_duration = 129 * 2,
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
.interrupt_time = (228*311 - 14360) * 2,
.delays = { // Should start at 14365
2, 1,
0, 0,
14, 13,
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
}
};
return result;
if constexpr (timing == Timing::Plus3) {
constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
return Timings(228, 311, 6, 129, 14361, delays);
}
if constexpr (timing == VideoTiming::OneTwoEightK) {
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
.contention_leadin = 4 * 2,
.contention_duration = 128 * 2,
.interrupt_time = (228*311 - 14357) * 2,
.delays = { // Should start at 14361.
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
2, 1,
0, 0,
0, 0,
}
};
return result;
if constexpr (timing == Timing::OneTwoEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(228, 311, 4, 128, 14361, delays);
}
if constexpr (timing == VideoTiming::FortyEightK) {
constexpr Timings result = {
.cycles_per_line = 224 * 2,
.lines_per_frame = 312,
.contention_leadin = 4 * 2,
.contention_duration = 128 * 2,
.interrupt_time = (224*312 - 14331) * 2,
.delays = { // Should start at 14335.
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
2, 1,
0, 0,
0, 0,
}
};
return result;
if constexpr (timing == Timing::FortyEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(224, 312, 4, 128, 14335, delays);
}
}
// TODO: how long is the interrupt line held for?
static constexpr int interrupt_duration = 48;
// Interrupt should be held for 32 cycles.
static constexpr int interrupt_duration = 64;
public:
void run_for(HalfCycles duration) {
constexpr auto timings = get_timings();
constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1;
constexpr int sync_line = (timings.interrupt_time / timings.half_cycles_per_line) + 1;
constexpr int sync_position = 166 * 2;
constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
constexpr int sync_length = 17 * 2;
constexpr int burst_position = sync_position + 40;
constexpr int burst_length = 17;
int cycles_remaining = duration.as<int>();
while(cycles_remaining) {
int line = time_into_frame_ / timings.cycles_per_line;
int offset = time_into_frame_ % timings.cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset);
int line = time_into_frame_ / timings.half_cycles_per_line;
int offset = time_into_frame_ % timings.half_cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.half_cycles_per_line - offset);
const int end_offset = offset + cycles_this_line;
if(!offset) {
@@ -292,7 +223,7 @@ template <VideoTiming timing> class Video {
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
if constexpr (timing >= VideoTiming::OneTwoEightK) {
if constexpr (timing >= Timing::OneTwoEightK) {
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
// The colour burst phase above is an empirical guess. I need to research further.
} else {
@@ -308,7 +239,7 @@ template <VideoTiming timing> class Video {
}
cycles_remaining -= cycles_this_line;
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame);
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.half_cycles_per_line * timings.lines_per_frame);
}
}
@@ -320,7 +251,7 @@ template <VideoTiming timing> class Video {
}
static constexpr int half_cycles_per_line() {
if constexpr (timing == VideoTiming::FortyEightK) {
if constexpr (timing == Timing::FortyEightK) {
// TODO: determine real figure here, if one exists.
// The source I'm looking at now suggests that the theoretical
// ideal of 224*2 ignores the real-life effects of separate
@@ -331,6 +262,39 @@ template <VideoTiming timing> class Video {
}
}
static constexpr HalfCycles frame_duration() {
const auto timings = get_timings();
return HalfCycles(timings.half_cycles_per_line * timings.lines_per_frame);
}
HalfCycles time_since_interrupt() {
const auto timings = get_timings();
if(time_into_frame_ >= timings.interrupt_time) {
return HalfCycles(time_into_frame_ - timings.interrupt_time);
} else {
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
}
}
void set_time_since_interrupt(const HalfCycles time) {
// Advance using run_for to ensure that all proper CRT interactions occurred.
const auto timings = get_timings();
const auto target = (time + timings.interrupt_time) % frame_duration();
const auto now = HalfCycles(time_into_frame_);
// Maybe this is easy?
if(target == now) return;
// Is the time within this frame?
if(time > now) {
run_for(target - time);
return;
}
// Then it's necessary to finish this frame and run into the next.
run_for(frame_duration() - now + time);
}
public:
Video() :
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
@@ -339,6 +303,11 @@ template <VideoTiming timing> class Video {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// Get the CRT roughly into phase.
//
// TODO: this is coupled to an assumption about the initial CRT. Fix.
const auto timings = get_timings();
crt_.output_blank(timings.lines_per_frame*timings.half_cycles_per_line - timings.interrupt_time);
}
void set_video_source(const uint8_t *source) {
@@ -362,7 +331,7 @@ template <VideoTiming timing> class Video {
}
// If not, it'll be in the next batch.
return timings.interrupt_time + timings.cycles_per_line * timings.lines_per_frame - time_into_frame_;
return timings.interrupt_time + timings.half_cycles_per_line * timings.lines_per_frame - time_into_frame_;
}
/*!
@@ -377,21 +346,22 @@ template <VideoTiming timing> class Video {
@returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention
needs to be applied in @c offset half-cycles from now.
*/
int access_delay(HalfCycles offset) const {
HalfCycles access_delay(HalfCycles offset) const {
constexpr auto timings = get_timings();
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.cycles_per_line * timings.lines_per_frame);
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.half_cycles_per_line * timings.lines_per_frame);
assert(!(delay_time&1));
// Check for a time within the no-contention window.
if(delay_time >= (191*timings.cycles_per_line + timings.contention_duration)) {
if(delay_time >= (191*timings.half_cycles_per_line + timings.contention_duration)) {
return 0;
}
const int time_into_line = delay_time % timings.cycles_per_line;
const int time_into_line = delay_time % timings.half_cycles_per_line;
if(time_into_line >= timings.contention_duration) {
return 0;
}
return timings.delays[time_into_line & 15];
return HalfCycles(timings.delays[(time_into_line >> 1) & 7]);
}
/*!
@@ -399,21 +369,21 @@ template <VideoTiming timing> class Video {
*/
uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff;
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
const int line = time_into_frame_ / timings.cycles_per_line;
const int line = time_into_frame_ / timings.half_cycles_per_line;
if(line >= 192) {
return out_of_bounds;
}
const int time_into_line = time_into_frame_ % timings.cycles_per_line;
const int time_into_line = time_into_frame_ % timings.half_cycles_per_line;
if(time_into_line >= 256 || (time_into_line&8)) {
return out_of_bounds;
}
// The +2a and +3 always return the low bit as set.
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
return value | 1;
}
return value;
@@ -425,7 +395,7 @@ template <VideoTiming timing> class Video {
bus is accessed when the gate array isn't currently reading.
*/
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
last_contended_access_ = value | 1;
}
}
@@ -434,6 +404,7 @@ template <VideoTiming timing> class Video {
Sets the current border colour.
*/
void set_border_colour(uint8_t colour) {
border_byte_ = colour;
border_colour_ = palette[colour];
}
@@ -452,11 +423,17 @@ template <VideoTiming timing> class Video {
crt_.set_display_type(type);
}
/*! Gets the display type. */
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
private:
int time_into_frame_ = 0;
Outputs::CRT::CRT crt_;
const uint8_t *memory_ = nullptr;
uint8_t border_colour_ = 0;
uint8_t border_byte_ = 0;
uint8_t *pixel_target_ = nullptr;
int attribute_address_ = 0;
@@ -469,6 +446,8 @@ template <VideoTiming timing> class Video {
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
uint8_t last_contended_access_ = 0xff;
friend struct State;
#define RGB(r, g, b) (r << 4) | (g << 2) | b
static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
@@ -479,6 +458,41 @@ template <VideoTiming timing> class Video {
#undef RGB
};
struct State: public Reflection::StructImpl<State> {
uint8_t border_colour = 0;
int half_cycles_since_interrupt = 0;
bool flash = 0;
int flash_counter = 0;
bool is_alternate_line = false;
State() {
if(needs_declare()) {
DeclareField(border_colour);
DeclareField(half_cycles_since_interrupt);
DeclareField(flash);
DeclareField(flash_counter);
DeclareField(is_alternate_line);
}
}
template <typename Video> State(const Video &source) : State() {
border_colour = source.border_byte_;
flash = source.flash_mask_;
flash_counter = source.flash_counter_;
is_alternate_line = source. is_alternate_line_;
half_cycles_since_interrupt = source.time_since_interrupt().template as<int>();
}
template <typename Video> void apply(Video &target) {
target.set_border_colour(border_colour);
target.flash_mask_ = flash ? 0xff : 0x00;
target.flash_counter_ = flash_counter;
target.is_alternate_line_ = is_alternate_line;
target.set_time_since_interrupt(HalfCycles(half_cycles_since_interrupt));
}
};
}
}
}

View File

@@ -8,9 +8,9 @@
#include "ZXSpectrum.hpp"
#include "State.hpp"
#include "Video.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../Keyboard/Keyboard.hpp"
#include "../../../Activity/Source.hpp"
#include "../../MachineTypes.hpp"
@@ -24,7 +24,9 @@
// just grab the CPC's version of an FDC.
#include "../../AmstradCPC/FDC.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../../../Outputs/Log.hpp"
#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
@@ -39,12 +41,76 @@
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "../Keyboard/Keyboard.hpp"
#include <array>
namespace {
/*!
Provides a simultaneous Kempston and Interface 2-style joystick.
*/
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
#define APPLY_KEMPSTON(b) if(is_active) kempston_ |= b; else kempston_ &= ~b;
#define APPLY_SINCLAIR(b) if(is_active) sinclair_ &= ~b; else sinclair_ |= b;
switch(digital_input.type) {
default: return;
case Input::Right:
APPLY_KEMPSTON(0x01);
APPLY_SINCLAIR(0x0208);
break;
case Input::Left:
APPLY_KEMPSTON(0x02);
APPLY_SINCLAIR(0x0110);
break;
case Input::Down:
APPLY_KEMPSTON(0x04);
APPLY_SINCLAIR(0x0404);
break;
case Input::Up:
APPLY_KEMPSTON(0x08);
APPLY_SINCLAIR(0x0802);
break;
case Input::Fire:
APPLY_KEMPSTON(0x10);
APPLY_SINCLAIR(0x1001);
break;
}
#undef APPLY_KEMPSTON
#undef APPLY_SINCLAIR
}
/// @returns The value that a Kempston joystick interface would report if this joystick
/// were plugged into it.
uint8_t get_kempston() {
return kempston_;
}
/// @returns The value that a Sinclair interface would report if this joystick
/// were plugged into it via @c port (which should be either 0 or 1, for ports 1 or 2).
uint8_t get_sinclair(int port) {
return uint8_t(sinclair_ >> (port * 8));
}
private:
uint8_t kempston_ = 0x00;
uint16_t sinclair_ = 0xffff;
};
}
namespace Sinclair {
namespace ZXSpectrum {
@@ -58,6 +124,7 @@ template<Model model> class ConcreteMachine:
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
@@ -79,37 +146,32 @@ template<Model model> class ConcreteMachine:
set_clock_rate(clock_rate());
speaker_.set_input_rate(float(clock_rate()) / 2.0f);
// With only the +2a and +3 currently supported, the +3 ROM is always
// the one required.
std::vector<ROMMachine::ROM> rom_names;
const std::string machine = "ZXSpectrum";
ROM::Name rom_name;
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
break;
case Model::OneTwoEightK:
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
break;
case Model::Plus2:
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
break;
case Model::FortyEightK: rom_name = ROM::Name::Spectrum48k; break;
case Model::OneTwoEightK: rom_name = ROM::Name::Spectrum128k; break;
case Model::Plus2: rom_name = ROM::Name::SpecrumPlus2; break;
case Model::Plus2a:
case Model::Plus3: {
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
} break;
case Model::Plus3: rom_name = ROM::Name::SpectrumPlus3; break;
// TODO: possibly accept the +3 ROM in multiple parts?
}
const auto roms = rom_fetcher(rom_names);
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
const auto request = ROM::Request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
const auto &rom = roms.find(rom_name)->second;
memcpy(rom_.data(), rom.data(), std::min(rom_.size(), rom.size()));
// Register for sleeping notifications.
tape_player_.set_clocking_hint_observer(this);
// Attach a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Set up initial memory map.
update_memory_map();
set_video_address();
@@ -124,6 +186,29 @@ template<Model model> class ConcreteMachine:
duration_to_press_enter_ = Cycles(5 * clock_rate());
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, true);
}
// Install state if supplied.
if(target.state) {
const auto state = static_cast<State *>(target.state.get());
state->z80.apply(z80_);
state->video.apply(*video_.last_valid());
state->ay.apply(ay_);
// If this is a 48k or 16k machine, remap source data from its original
// linear form to whatever the banks end up being; otherwise copy as is.
if(model <= Model::FortyEightK) {
const size_t num_banks = std::min(size_t(48*1024), state->ram.size()) >> 14;
for(size_t c = 0; c < num_banks; c++) {
memcpy(&write_pointers_[c + 1][(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000);
}
} else {
memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size()));
port1ffd_ = state->last_1ffd;
port7ffd_ = state->last_7ffd;
update_memory_map();
}
}
}
~ConcreteMachine() {
@@ -202,6 +287,10 @@ template<Model model> class ConcreteMachine:
video_->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const override {
return video_->get_display_type();
}
// MARK: - BusHandler.
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
@@ -218,12 +307,23 @@ template<Model model> class ConcreteMachine:
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
advance(cycle.length + delay);
return delay;
}
} else {
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Input:
case CPU::Z80::PartialMachineCycle::Output:
case CPU::Z80::PartialMachineCycle::Read:
case CPU::Z80::PartialMachineCycle::Write:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Interrupt:
// For these, carry on into the actual handler, below.
break;
// For anything else that isn't listed below, just advance
// time and conclude here.
default:
advance(cycle.length);
return HalfCycles(0);
@@ -234,7 +334,7 @@ template<Model model> class ConcreteMachine:
// always occurs if it is in the $4000$8000 range regardless of current
// memory mapping.
HalfCycles delay;
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
HalfCycles time = video_.time_since_flush();
if((address & 0xc000) == 0x4000) {
for(int c = 0; c < ((address & 1) ? 4 : 2); c++) {
@@ -250,7 +350,7 @@ template<Model model> class ConcreteMachine:
advance(cycle.length + delay);
return delay;
}
} break;
case PartialMachineCycle::ReadOpcodeStart:
case PartialMachineCycle::ReadStart:
@@ -258,12 +358,12 @@ template<Model model> class ConcreteMachine:
// These all start by loading the address bus, then set MREQ
// half a cycle later.
if(is_contended_[address >> 14]) {
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
advance(cycle.length + delay);
return delay;
}
}
} break;
case PartialMachineCycle::Internal: {
// Whatever's on the address bus will remain there, without IOREQ or
@@ -273,7 +373,7 @@ template<Model model> class ConcreteMachine:
const auto half_cycles = cycle.length.as<int>();
assert(!(half_cycles & 1));
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
HalfCycles time = video_.time_since_flush();
HalfCycles delay;
for(int c = 0; c < half_cycles; c += 2) {
const auto next_delay = video_.last_valid()->access_delay(time);
@@ -284,15 +384,7 @@ template<Model model> class ConcreteMachine:
advance(cycle.length + delay);
return delay;
}
}
case CPU::Z80::PartialMachineCycle::Input:
case CPU::Z80::PartialMachineCycle::Output:
case CPU::Z80::PartialMachineCycle::Read:
case CPU::Z80::PartialMachineCycle::Write:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// For these, carry on into the actual handler, below.
break;
} break;
}
}
@@ -319,6 +411,7 @@ template<Model model> class ConcreteMachine:
break;
}
}
[[fallthrough]];
case PartialMachineCycle::Read:
if constexpr (model == Model::SixteenK) {
@@ -381,10 +474,6 @@ template<Model model> class ConcreteMachine:
// Set the proper video base pointer.
set_video_address();
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ |= *cycle.value & 0x20;
}
// Test for +2a/+3 paging (i.e. port 1ffd).
@@ -432,6 +521,11 @@ template<Model model> class ConcreteMachine:
bool did_match = false;
*cycle.value = 0xff;
if(!(address&32)) {
did_match = true;
*cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_kempston();
}
if(!(address&1)) {
did_match = true;
@@ -444,11 +538,15 @@ template<Model model> class ConcreteMachine:
*cycle.value &= keyboard_.read(address);
*cycle.value &= tape_player_.get_input() ? 0xbf : 0xff;
// If this read is within 200 cycles of the previous,
// count it as an adjacent hit; if 20 of those have
// occurred then start the tape motor.
// Add Joystick input on top.
if(!(address&0x1000)) *cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_sinclair(0);
if(!(address&0x0800)) *cycle.value &= static_cast<Joystick *>(joysticks_[1].get())->get_sinclair(1);
// If this read is between 50 and 200 cycles since the
// previous, count it as an adjacent hit; if 20 of those
// have occurred then start the tape motor.
if(use_automatic_tape_motor_control_) {
if(cycles_since_tape_input_read_ < HalfCycles(400)) {
if(cycles_since_tape_input_read_ >= HalfCycles(100) && cycles_since_tape_input_read_ < HalfCycles(200)) {
++recent_tape_hits_;
if(recent_tape_hits_ == 20) {
@@ -496,6 +594,13 @@ template<Model model> class ConcreteMachine:
}
}
} break;
case PartialMachineCycle::Interrupt:
// At least one piece of Spectrum software, Escape from M.O.N.J.A.S. explicitly
// assumes that a 0xff value will be on the bus during an interrupt acknowledgment.
// I wasn't otherwise aware that this value is reliable.
*cycle.value = 0xff;
break;
}
return HalfCycles(0);
@@ -513,11 +618,11 @@ template<Model model> class ConcreteMachine:
if(!tape_player_is_sleeping_) tape_player_.run_for(duration.as_integral());
// Update automatic tape motor control, if enabled; if it's been
// 3 seconds since software last possibly polled the tape, stop it.
if(use_automatic_tape_motor_control_ && cycles_since_tape_input_read_ < HalfCycles(clock_rate() * 6)) {
// 0.5 seconds since software last possibly polled the tape, stop it.
if(use_automatic_tape_motor_control_ && cycles_since_tape_input_read_ < HalfCycles(clock_rate())) {
cycles_since_tape_input_read_ += duration;
if(cycles_since_tape_input_read_ >= HalfCycles(clock_rate() * 6)) {
if(cycles_since_tape_input_read_ >= HalfCycles(clock_rate())) {
tape_player_.set_motor_control(false);
recent_tape_hits_ = 0;
}
@@ -615,6 +720,7 @@ template<Model model> class ConcreteMachine:
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
options->output = get_video_signal_configurable();
return options;
}
@@ -703,13 +809,17 @@ template<Model model> class ConcreteMachine:
set_memory(2, 2);
set_memory(3, port7ffd_ & 7);
}
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ = port7ffd_ & 0x20;
}
void set_memory(int bank, uint8_t source) {
if constexpr (model >= Model::Plus2a) {
is_contended_[bank] = (source >= 4 && source < 8);
is_contended_[bank] = source >= 4 && source < 8;
} else {
is_contended_[bank] = source & 1;
is_contended_[bank] = source < 0x80 && source & 1;
}
pages_[bank] = source;
@@ -748,10 +858,10 @@ template<Model model> class ConcreteMachine:
// MARK: - Video.
using VideoType =
std::conditional_t<
model <= Model::FortyEightK, Video<VideoTiming::FortyEightK>,
model <= Model::FortyEightK, Video::Video<Video::Timing::FortyEightK>,
std::conditional_t<
model <= Model::Plus2, Video<VideoTiming::OneTwoEightK>,
Video<VideoTiming::Plus3>
model <= Model::Plus2, Video::Video<Video::Timing::OneTwoEightK>,
Video::Video<Video::Timing::Plus3>
>
>;
JustInTimeActor<VideoType> video_;
@@ -862,6 +972,12 @@ template<Model model> class ConcreteMachine:
// MARK: - Automatic startup.
Cycles duration_to_press_enter_;
// MARK: - Joysticks
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
};

View File

@@ -31,7 +31,7 @@ class Machine {
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
public:
bool automatic_tape_motor_control;
bool automatic_tape_motor_control = true;
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour),

View File

@@ -0,0 +1,26 @@
//
// StateProducer.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_h
#define State_h
#include <memory>
#include "../Analyser/Static/StaticAnalyser.hpp"
namespace MachineTypes {
struct StateProducer {
// TODO.
// virtual bool get_state(Analyser::Static::State *, [[maybe_unused]] bool advance_to_simple = false) {
// return false;
// }
};
};
#endif /* State_h */

View File

@@ -20,6 +20,7 @@
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../Enterprise/Enterprise.hpp"
#include "../MasterSystem/MasterSystem.hpp"
#include "../MSX/MSX.hpp"
#include "../Oric/Oric.hpp"
@@ -34,6 +35,7 @@
#include "../../Analyser/Static/Atari2600/Target.hpp"
#include "../../Analyser/Static/AtariST/Target.hpp"
#include "../../Analyser/Static/Commodore/Target.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../Analyser/Static/Macintosh/Target.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
#include "../../Analyser/Static/Oric/Target.hpp"
@@ -49,7 +51,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
Machine::DynamicMachine *machine = nullptr;
try {
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher)); break;
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<::name::Machine>(name::Machine::m(target, rom_fetcher)); break;
#define Bind(m) BindD(m, m)
switch(target->machine) {
Bind(AmstradCPC)
@@ -61,6 +63,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
BindD(Coleco::Vision, ColecoVision)
BindD(Commodore::Vic20, Vic20)
Bind(Electron)
Bind(Enterprise)
Bind(MSX)
Bind(Oric)
BindD(Sega::MasterSystem, MasterSystem)
@@ -127,6 +130,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
case Analyser::Machine::AtariST: return "AtariST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::Enterprise: return "Enterprise";
case Analyser::Machine::Macintosh: return "Macintosh";
case Analyser::Machine::MasterSystem: return "MasterSystem";
case Analyser::Machine::MSX: return "MSX";
@@ -148,6 +152,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
case Analyser::Machine::AtariST: return "Atari ST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::Enterprise: return "Enterprise";
case Analyser::Machine::Macintosh: return "Apple Macintosh";
case Analyser::Machine::MasterSystem: return "Sega Master System";
case Analyser::Machine::MSX: return "MSX";
@@ -177,11 +182,13 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
AddName(AppleIIgs);
AddName(AtariST);
AddName(Electron);
AddName(Enterprise);
AddName(Macintosh);
AddName(MSX);
AddName(Oric);
AddName(Vic20);
AddName(ZX8081);
AddName(ZXSpectrum);
}
#undef AddName
@@ -200,6 +207,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
Emplace(AtariST, Atari::ST::Machine);
Emplace(ColecoVision, Coleco::Vision::Machine);
Emplace(Electron, Electron::Machine);
Emplace(Enterprise, Enterprise::Machine);
Emplace(Macintosh, Apple::Macintosh::Machine);
Emplace(MasterSystem, Sega::MasterSystem::Machine);
Emplace(MSX, MSX::Machine);
@@ -225,6 +233,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
Add(AppleIIgs);
Add(AtariST);
AddMapped(Electron, Acorn);
Add(Enterprise);
Add(Macintosh);
Add(MSX);
Add(Oric);

View File

@@ -8,6 +8,8 @@
#include "MemoryPacker.hpp"
#include <cstddef>
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
for(size_t c = 0; c < source.size(); c += 2) {
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);

View File

@@ -0,0 +1,545 @@
//
// ROMCatalogue.cpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "ROMCatalogue.hpp"
#include <algorithm>
#include <cassert>
#include <codecvt>
#include <iomanip>
#include <locale>
#include <sstream>
using namespace ROM;
namespace {
constexpr Name MaxName = Name::SpectrumPlus3;
}
Request::Request(Name name, bool optional) {
node.name = name;
node.is_optional = optional;
}
Request Request::append(Node::Type type, const Request &rhs) {
// If either side is empty, act appropriately.
if(node.empty() && !rhs.node.empty()) {
return rhs;
}
if(rhs.node.empty()) {
return *this;
}
// Just copy in the RHS child nodes if types match.
if(node.type == type && rhs.node.type == type) {
Request new_request = *this;
new_request.node.children.insert(new_request.node.children.end(), rhs.node.children.begin(), rhs.node.children.end());
new_request.node.sort();
return new_request;
}
// Possibly: left is appropriate request and rhs is just one more thing?
if(node.type == type && rhs.node.type == Node::Type::One) {
Request new_request = *this;
new_request.node.children.push_back(rhs.node);
new_request.node.sort();
return new_request;
}
// Or: right is appropriate request and this is just one more thing?
if(rhs.node.type == type && node.type == Node::Type::One) {
Request new_request = rhs;
new_request.node.children.push_back(node);
new_request.node.sort();
return new_request;
}
// Otherwise create a new parent node.
Request parent;
parent.node.type = type;
parent.node.children.push_back(this->node);
parent.node.children.push_back(rhs.node);
return parent;
}
Request Request::operator &&(const Request &rhs) {
return append(Node::Type::All, rhs);
}
Request Request::operator ||(const Request &rhs) {
return append(Node::Type::Any, rhs);
}
bool Request::validate(Map &map) const {
return node.validate(map);
}
std::vector<ROM::Description> Request::all_descriptions() const {
std::vector<Description> result;
node.add_descriptions(result);
return result;
}
void Request::Node::add_descriptions(std::vector<Description> &result) const {
if(type == Type::One) {
result.push_back(name);
return;
}
for(const auto &child: children) {
child.add_descriptions(result);
}
}
Request Request::subtract(const ROM::Map &map) const {
Request copy(*this);
if(copy.node.subtract(map)) {
copy.node.name = Name::None;
copy.node.type = Node::Type::One;
}
return copy;
}
bool Request::Node::subtract(const ROM::Map &map) {
switch(type) {
case Type::One:
return map.find(name) != map.end();
default: {
bool has_all = true;
bool has_any = false;
auto iterator = children.begin();
while(iterator != children.end()) {
const bool did_subtract = iterator->subtract(map);
has_all &= did_subtract;
has_any |= did_subtract;
if(did_subtract) {
iterator = children.erase(iterator);
} else {
++iterator;
}
}
return (type == Type::All && has_all) || (type == Type::Any && has_any);
}
}
}
bool Request::empty() {
return node.type == Node::Type::One && node.name == Name::None;
}
bool Request::Node::validate(Map &map) const {
// Leaf nodes are easy: check that the named ROM is present,
// unless it's optional, in which case it is always valid.
//
// If it is present, make sure it's the proper size.
if(type == Type::One) {
auto rom = map.find(name);
if(rom == map.end()) {
return is_optional;
}
const Description description(name);
rom->second.resize(description.size);
return true;
}
// This is a collection node then. Check for both any or all
// simultaneously, since all nodes will need to be visited
// regardless of any/all in order to ensure proper sizing.
bool has_all = true;
bool has_any = false;
for(const auto &child: children) {
const bool is_valid = child.validate(map);
has_all &= is_valid;
has_any |= is_valid;
}
return (type == Type::Any && has_any) || (type == Type::All && has_all);
}
void Request::visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType, const ROM::Description &, bool, size_t)> &add_item
) const {
node.visit(enter_list, exit_list, add_item);
}
void Request::visit(
const std::function<void(LineItem, ListType, int, const ROM::Description *, bool, size_t)> &add_item
) const {
int indentation_level = 0;
node.visit(
[&indentation_level, &add_item] (ROM::Request::ListType type, size_t size) {
add_item(LineItem::NewList, type, indentation_level, nullptr, false, size);
++indentation_level;
},
[&indentation_level] {
--indentation_level;
},
[&indentation_level, &add_item] (ROM::Request::ListType type, const ROM::Description &rom, bool is_optional, size_t remaining) {
add_item(LineItem::Description, type, indentation_level, &rom, is_optional, remaining);
}
);
}
void Request::Node::visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const {
switch(type) {
case Type::One:
enter_list(ListType::Single, 1);
add_item(ROM::Request::ListType::Any, Description(name), is_optional, 0);
exit_list();
break;
case Type::Any:
case Type::All: {
const ListType list_type = type == Type::Any ? ListType::Any : ListType::All;
enter_list(list_type, children.size());
for(size_t index = 0; index < children.size(); index++) {
auto &child = children[index];
if(child.type == Type::One) {
add_item(list_type, Description(child.name), child.is_optional, children.size() - 1 - index);
} else {
child.visit(enter_list, exit_list, add_item);
}
}
exit_list();
} break;
}
}
std::vector<Description> ROM::all_descriptions() {
std::vector<Description> result;
for(int name = 1; name <= MaxName; name++) {
result.push_back(Description(ROM::Name(name)));
}
return result;
}
std::optional<Description> Description::from_crc(uint32_t crc32) {
for(int name = 1; name <= MaxName; name++) {
const Description candidate = Description(ROM::Name(name));
const auto found_crc = std::find(candidate.crc32s.begin(), candidate.crc32s.end(), crc32);
if(found_crc != candidate.crc32s.end()) {
return candidate;
}
}
return std::nullopt;
}
std::string Description::description(int flags) const {
std::stringstream output;
// If there are no CRCs, don't output them.
if(crc32s.empty()) flags &= ~ DescriptionFlag::CRC;
// Print the file name(s) and the descriptive name.
if(flags & DescriptionFlag::Filename) {
flags &= ~DescriptionFlag::Filename;
output << machine_name << '/';
if(file_names.size() == 1) {
output << file_names[0];
} else {
output << "{";
bool is_first = true;
for(const auto &file_name: file_names) {
if(!is_first) output << " or ";
output << file_name;
is_first = false;
}
output << "}";
}
output << " (" << descriptive_name;
if(!flags) {
output << ")";
return output.str();
}
output << "; ";
} else {
output << descriptive_name;
if(!flags) {
return output.str();
}
output << " (";
}
// Print the size.
if(flags & DescriptionFlag::Size) {
flags &= ~DescriptionFlag::Size;
output << size << " bytes";
if(!flags) {
output << ")";
return output.str();
}
output << "; ";
}
// Print the CRC(s).
if(flags & DescriptionFlag::CRC) {
flags &= ~DescriptionFlag::CRC;
output << ((crc32s.size() > 1) ? "usual crc32s: " : "usual crc32: ");
bool is_first = true;
for(const auto crc32: crc32s) {
if(!is_first) output << ", ";
is_first = false;
output << std::hex << std::setfill('0') << std::setw(8) << crc32;
}
if(!flags) {
output << ")";
return output.str();
}
}
return output.str();
}
std::wstring Request::description(int description_flags, wchar_t bullet_point) {
std::wstringstream output;
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
visit(
[&output, description_flags, bullet_point, &wstring_converter] (ROM::Request::LineItem item, ROM::Request::ListType type, int indentation_level, const ROM::Description *description, bool is_optional, size_t remaining) {
if(indentation_level) {
output << std::endl;
for(int c = 0; c < indentation_level; c++) output << '\t';
output << bullet_point << ' ';
}
switch(item) {
case ROM::Request::LineItem::NewList:
if(remaining > 1) {
if(!indentation_level) output << " ";
switch(type) {
default:
case ROM::Request::ListType::All: output << "all of:"; break;
case ROM::Request::ListType::Any:
if(remaining == 2) {
output << "either of:";
} else {
output << "any of:";
}
break;
}
} else {
output << ":";
}
break;
case ROM::Request::LineItem::Description:
if(is_optional) output << "optionally, ";
output << wstring_converter.from_bytes(description->description(description_flags));
if(remaining) {
output << ";";
if(remaining == 1) {
output << ((type == ROM::Request::ListType::All) ? " and" : " or");
}
} else {
output << ".";
}
break;
}
}
);
return output.str();
}
Description::Description(Name name) {
switch(name) {
default: assert(false); break;
case Name::AMSDOS: *this = Description(name, "AmstradCPC", "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecdu); break;
case Name::CPC464Firmware: *this = Description(name, "AmstradCPC", "the CPC 464 firmware", "os464.rom", 16*1024, 0x815752dfu); break;
case Name::CPC464BASIC: *this = Description(name, "AmstradCPC", "the CPC 464 BASIC ROM", "basic464.rom", 16*1024, 0x7d9a3bacu); break;
case Name::CPC664Firmware: *this = Description(name, "AmstradCPC", "the CPC 664 firmware", "os664.rom", 16*1024, 0x3f5a6dc4u); break;
case Name::CPC664BASIC: *this = Description(name, "AmstradCPC", "the CPC 664 BASIC ROM", "basic664.rom", 16*1024, 0x32fee492u); break;
case Name::CPC6128Firmware: *this = Description(name, "AmstradCPC", "the CPC 6128 firmware", "os6128.rom", 16*1024, 0x0219bb74u); break;
case Name::CPC6128BASIC: *this = Description(name, "AmstradCPC", "the CPC 6128 BASIC ROM", "basic6128.rom", 16*1024, 0xca6af63du); break;
case Name::AppleIIEnhancedE: *this = Description(name, "AppleII", "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942u); break;
case Name::AppleIIe: *this = Description(name, "AppleII", "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18du); break;
case Name::AppleIIPlus: *this = Description(name, "AppleII", "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26u); break;
case Name::AppleIIOriginal: *this = Description(name, "AppleII", "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588u); break;
case Name::AppleIICharacter: *this = Description(name, "AppleII", "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6u); break;
case Name::AppleIIeCharacter: *this = Description(name, "AppleII", "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1u); break;
case Name::AppleIIEnhancedECharacter:
*this = Description(name, "AppleII", "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014du);
break;
case Name::AppleIIgsROM00: /* TODO */
case Name::AppleIIgsROM01: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0u); break;
case Name::AppleIIgsROM03: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29u); break;
case Name::AppleIIgsCharacter: *this = Description(name, "AppleIIgs", "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8u); break;
case AppleIIgsMicrocontrollerROM03:
*this = Description(name, "AppleIIgs", "the Apple IIgs ROM03 ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0u);
break;
case Name::DiskIIBoot16Sector:
*this = Description(name, "DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6u);
break;
case Name::DiskIIStateMachine16Sector:
*this = Description(name, "DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, std::initializer_list<uint32_t>{ 0x9796a238, 0xb72a2c70 } );
break;
case Name::DiskIIBoot13Sector:
*this = Description(name, "DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ffu);
break;
case Name::DiskIIStateMachine13Sector:
*this = Description(name, "DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620u);
break;
case Name::EnterpriseEXOS10: {
const std::initializer_list<std::string> filenames = {"exos10.bin", "Exos (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v1.0", filenames, 32 * 1024, 0x30b26387u);
} break;
case Name::EnterpriseEXOS20: {
const std::initializer_list<std::string> filenames = {"exos20.bin", "Expandible OS v2.0 (1984)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.0", filenames, 32 * 1024, 0xd421795fu);
} break;
case Name::EnterpriseEXOS21: {
const std::initializer_list<std::string> filenames = {"exos21.bin", "Expandible OS v2.1 (1985)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 32 * 1024, 0x982a3b44u);
} break;
case Name::EnterpriseEXOS23: {
const std::initializer_list<std::string> filenames = {"exos23.bin", "Expandible OS v2.3 (1987)(Intelligent Software).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 64 * 1024, 0x24838410u);
} break;
case Name::EnterpriseBASIC10: {
const std::initializer_list<std::string> filenames = {"basic10.bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0", filenames, 16 * 1024, 0xd62e4fb7u);
} break;
case Name::EnterpriseBASIC10Part1: {
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 1", filenames, 8193, 0x37bf48e1u);
} break;
case Name::EnterpriseBASIC10Part2: {
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 2", filenames, 8193, 0xc5298c79u);
} break;
case Name::EnterpriseBASIC11: {
const std::initializer_list<std::string> filenames = {"basic11.bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1", filenames, 16 * 1024, 0x683cf455u);
} break;
case Name::EnterpriseBASIC11Suffixed: {
const std::initializer_list<std::string> filenames = {"BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1, with trailing byte", filenames, 16385, 0xc96b7602u);
} break;
case Name::EnterpriseBASIC21: {
const std::initializer_list<std::string> filenames = {
"basic21.bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software).bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
};
const std::initializer_list<uint32_t> crcs = { 0x55f96251, 0x683cf455 };
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v2.1", filenames, 16 * 1024, crcs);
} break;
case Name::EnterpriseEPDOS: {
const std::initializer_list<std::string> filenames = {"epdos.bin", "EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"};
*this = Description(name, "Enterprise", "the Enterprise EPDOS ROM", filenames, 32 * 1024, 0x201319ebu);
} break;
case Name::EnterpriseEXDOS: {
const std::initializer_list<std::string> filenames = {"exdos.bin", "EX-DOS EPROM (198x)(Enterprise).bin"};
*this = Description(name, "Enterprise", "the Enterprise EXDOS ROM", filenames, 16 * 1024, 0xe6daa0e9u);
} break;
case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break;
case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break;
case Name::MacintoshPlus: {
const std::initializer_list<uint32_t> crcs = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
*this = Description(name, "Macintosh", "the Macintosh Plus ROM", "macplus.rom", 128*1024, crcs);
} break;
case Name::AtariSTTOS100: *this = Description(name, "AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64u); break;
case Name::AtariSTTOS104: *this = Description(name, "AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43u); break;
case Name::AtariSTEmuTOS192: *this = Description(name, "AtariST", "the UK EmuTOS 1.92 ROM", "etos192uk.img", 192*1024, 0xfc3b9e61u); break;
case Name::ColecoVisionBIOS:
*this = Description(name, "ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3u);
break;
case Name::ZX80: *this = Description(name, "ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597u); break;
case Name::ZX81: *this = Description(name, "ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6ebu); break;
case Name::Spectrum48k: *this = Description(name, "ZXSpectrum", "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531fu); break;
case Name::Spectrum128k: *this = Description(name, "ZXSpectrum", "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995u); break;
case Name::SpecrumPlus2: *this = Description(name, "ZXSpectrum", "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dcu); break;
case Name::SpectrumPlus3: {
const std::initializer_list<uint32_t> crcs = { 0x96e3c17a, 0xbe0d9ec4 };
*this = Description(name, "ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crcs);
} break;
case Name::AcornBASICII: *this = Description(name, "Electron", "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781u); break;
case Name::PRESADFSSlot1: *this = Description(name, "Electron", "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993u); break;
case Name::PRESADFSSlot2: *this = Description(name, "Electron", "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0eu); break;
case Name::AcornADFS: *this = Description(name, "Electron", "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6u); break;
case Name::Acorn1770DFS: *this = Description(name, "Electron", "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5u); break;
case Name::PRESAdvancedPlus6:
*this = Description(name, "Electron", "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfcu);
break;
case Name::AcornElectronMOS100:
*this = Description(name, "Electron", "the Electron MOS ROM v1.00", "os.rom", 16*1024, 0xbf63fb1fu);
break;
case Name::MasterSystemJapaneseBIOS: *this = Description(name, "MasterSystem", "the Japanese Master System BIOS", "japanese-bios.sms", 8*1024, 0x48d44a13u); break;
case Name::MasterSystemWesternBIOS: *this = Description(name, "MasterSystem", "the European/US Master System BIOS", "bios.sms", 8*1024, 0x0072ed54u); break;
case Name::Commodore1540: *this = Description(name, "Commodore1540", "the 1540 ROM", "1540.bin", 16*1024, 0x718d42b1u); break;
case Name::Commodore1541: *this = Description(name, "Commodore1540", "the 1541 ROM", "1541.bin", 16*1024, 0xfb760019); break;
case Name::Vic20BASIC: *this = Description(name, "Vic20", "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1u); break;
case Name::Vic20EnglishCharacters: *this = Description(name, "Vic20", "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6u); break;
case Name::Vic20EnglishPALKernel: *this = Description(name, "Vic20", "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4u); break;
case Name::Vic20EnglishNTSCKernel: *this = Description(name, "Vic20", "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174u); break;
case Name::Vic20DanishCharacters: *this = Description(name, "Vic20", "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454u); break;
case Name::Vic20DanishKernel: *this = Description(name, "Vic20", "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16u); break;
case Name::Vic20JapaneseCharacters: *this = Description(name, "Vic20", "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4bu); break;
case Name::Vic20JapaneseKernel: *this = Description(name, "Vic20", "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7u); break;
case Name::Vic20SwedishCharacters: *this = Description(name, "Vic20", "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551du); break;
case Name::Vic20SwedishKernel: *this = Description(name, "Vic20", "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662u); break;
case Name::OricColourROM: *this = Description(name, "Oric", "the Oric colour ROM", "colour.rom", 128, 0xd50fca65u); break;
case Name::OricBASIC10: *this = Description(name, "Oric", "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4u); break;
case Name::OricBASIC11: *this = Description(name, "Oric", "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92befu); break;
case Name::OricPravetzBASIC: *this = Description(name, "Oric", "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502u); break;
case Name::OricByteDrive500: *this = Description(name, "Oric", "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34u); break;
case Name::OricJasmin: *this = Description(name, "Oric", "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89u); break;
case Name::OricMicrodisc: *this = Description(name, "Oric", "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9cu); break;
case Name::Oric8DOSBoot: *this = Description(name, "Oric", "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06u); break;
case Name::MSXGenericBIOS: *this = Description(name, "MSX", "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break;
case Name::MSXJapaneseBIOS: *this = Description(name, "MSX", "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390u); break;
case Name::MSXAmericanBIOS: *this = Description(name, "MSX", "an American MSX BIOS", "msx-american.rom", 32*1024, 0u); break;
case Name::MSXEuropeanBIOS: *this = Description(name, "MSX", "a European MSX BIOS", "msx-european.rom", 32*1024, 0u); break;
case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break;
case Name::SinclairQLJS:
*this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u);
break;
}
}

View File

@@ -0,0 +1,289 @@
//
// ROMCatalogue.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef ROMCatalogue_hpp
#define ROMCatalogue_hpp
#include <algorithm>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
namespace ROM {
enum Name {
None,
// Acorn.
AcornBASICII,
AcornElectronMOS100,
PRESADFSSlot1,
PRESADFSSlot2,
AcornADFS,
PRESAdvancedPlus6,
Acorn1770DFS,
// Amstrad CPC.
AMSDOS,
CPC464Firmware, CPC464BASIC,
CPC664Firmware, CPC664BASIC,
CPC6128Firmware, CPC6128BASIC,
// Apple II.
AppleIIOriginal,
AppleIIPlus,
AppleIICharacter,
AppleIIe,
AppleIIeCharacter,
AppleIIEnhancedE,
AppleIIEnhancedECharacter,
// Apple IIgs.
AppleIIgsROM00,
AppleIIgsROM01,
AppleIIgsROM03,
AppleIIgsMicrocontrollerROM03,
AppleIIgsCharacter,
// Atari ST.
AtariSTTOS100,
AtariSTTOS104,
AtariSTEmuTOS192,
// ColecoVision.
ColecoVisionBIOS,
// Commodore 1540/1541.
Commodore1540,
Commodore1541,
// Disk II.
DiskIIStateMachine16Sector,
DiskIIBoot16Sector,
DiskIIStateMachine13Sector,
DiskIIBoot13Sector,
// Enterprise.
EnterpriseEXOS10,
EnterpriseEXOS20,
EnterpriseEXOS21,
EnterpriseEXOS23,
EnterpriseBASIC10,
EnterpriseBASIC10Part1,
EnterpriseBASIC10Part2,
EnterpriseBASIC11,
EnterpriseBASIC11Suffixed,
EnterpriseBASIC21,
EnterpriseEPDOS,
EnterpriseEXDOS,
// Macintosh.
Macintosh128k,
Macintosh512k,
MacintoshPlus,
// Master System.
MasterSystemJapaneseBIOS,
MasterSystemWesternBIOS,
// MSX.
MSXGenericBIOS,
MSXJapaneseBIOS,
MSXAmericanBIOS,
MSXEuropeanBIOS,
MSXDOS,
// Oric.
OricColourROM,
OricBASIC10,
OricBASIC11,
OricPravetzBASIC,
OricByteDrive500,
OricJasmin,
OricMicrodisc,
Oric8DOSBoot,
// Sinclair QL.
SinclairQLJS,
// Vic-20.
Vic20BASIC,
Vic20EnglishCharacters,
Vic20EnglishPALKernel,
Vic20EnglishNTSCKernel,
Vic20DanishCharacters,
Vic20DanishKernel,
Vic20JapaneseCharacters,
Vic20JapaneseKernel,
Vic20SwedishCharacters,
Vic20SwedishKernel,
// ZX80/81.
ZX80,
ZX81,
// ZX Spectrum.
Spectrum48k,
Spectrum128k,
SpecrumPlus2,
SpectrumPlus3,
};
using Map = std::map<ROM::Name, std::vector<uint8_t>>;
struct Description {
/// The ROM's enum name.
Name name = Name::None;
/// The machine with which this ROM is associated, in a form that is safe for using as
/// part of a file name.
std::string machine_name;
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
/// clause, etc, e.g. "the Electron MOS 1.0".
std::string descriptive_name;
/// All idiomatic file name for this ROM, e.g. "os10.rom".
std::vector<std::string> file_names;
/// The expected size of this ROM in bytes, e.g. 32768.
size_t size = 0;
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
/// to exclude ROMs where the user's intent is otherwise clear.
std::set<uint32_t> crc32s;
/// Constructs the @c Description that correlates to @c name.
Description(Name name);
/// Constructs the @c Description that correlates to @c crc32, if any.
static std::optional<Description> from_crc(uint32_t crc32);
enum DescriptionFlag {
Size = 1 << 0,
CRC = 1 << 1,
Filename = 1 << 2,
};
/// Provides a single-line of text describing this ROM, including the usual base text
/// plus all the fields provided as @c flags .
std::string description(int flags) const;
private:
template <typename FileNameT, typename CRC32T> Description(
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0)
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} {
// Slightly lazy: deal with the case where the constructor wasn't provided with any
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative
// would be to provide a partial specialisation that never put anything into the set.
if(this->crc32s.size() == 1 && !*this->crc32s.begin()) {
this->crc32s.clear();
}
}
};
/// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM
/// currently known to the ROM catalogue.
std::vector<Description> all_descriptions();
struct Request {
Request(Name name, bool optional = false);
Request() {}
/// Forms the request that would be satisfied by @c this plus the right-hand side.
Request operator &&(const Request &);
/// Forms the request that would be satisfied by either @c this or the right-hand side.
Request operator ||(const Request &);
/// Inspects the ROMMap to ensure that it satisfies this @c Request.
/// @c returns @c true if the request is satisfied; @c false otherwise.
///
/// All ROMs in the map will be resized to their idiomatic sizes.
bool validate(Map &) const;
/// Returns a flattened array of all @c ROM::Descriptions that relate to anything
/// anywhere in this ROM request.
std::vector<Description> all_descriptions() const;
/// @returns @c true if this request is empty, i.e. would be satisfied with no ROMs; @c false otherwise.
bool empty();
/// @returns what remains of this ROM request given that everything in @c map has been found.
Request subtract(const ROM::Map &map) const;
enum class ListType {
Any, All, Single
};
void visit(
const std::function<void(ListType, size_t size)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const;
enum class LineItem {
NewList, Description
};
void visit(
const std::function<void(LineItem, ListType, int level, const ROM::Description *, bool is_optional, size_t remaining)> &add_item
) const;
/// @returns a full bullet-pointed list of the requirements of this request, including
/// appropriate conjuntives. This text is intended to be glued to the end of an opening
/// portion of a sentence, e.g. "Please supply" + request.description(0, L'*').
std::wstring description(int description_flags, wchar_t bullet_point);
private:
struct Node {
enum class Type {
Any, All, One
};
Type type = Type::One;
Name name = Name::None;
/// @c true if this ROM is optional for machine startup. Generally indicates something
/// that would make emulation more accurate, but not sufficiently so to make it
/// a necessity.
bool is_optional = false;
std::vector<Node> children;
bool empty() const {
return type == Type::One && name == Name::None;
}
void add_descriptions(std::vector<Description> &) const;
bool validate(Map &) const;
void visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const;
bool subtract(const ROM::Map &map);
void sort() {
// Don't do a full sort, but move anything optional to the back.
// This makes them print more nicely; it's a human-facing tweak only.
ssize_t index = ssize_t(children.size() - 1);
bool has_seen_non_optional = false;
while(index >= 0) {
has_seen_non_optional |= !children[size_t(index)].is_optional;
if(children[size_t(index)].is_optional && has_seen_non_optional) {
std::rotate(children.begin() + index, children.begin() + index + 1, children.end());
}
--index;
}
}
};
Node node;
Request append(Node::Type type, const Request &rhs);
};
}
#endif /* ROMCatalogue_hpp */

View File

@@ -131,13 +131,3 @@ bool Typer::type_next_character() {
return true;
}
// MARK: - Character mapper
const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= (length / sizeof(KeySequence))) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}

View File

@@ -44,11 +44,15 @@ class CharacterMapper {
typedef uint16_t KeySequence[16];
/*!
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
with @c length entries, returns the sequence for character @c character if it exists; otherwise
returns @c nullptr.
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/
const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const;
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}
};
/*!

View File

@@ -12,6 +12,8 @@
#include <cstdint>
#include <cstdlib>
#include "Sizes.hpp"
namespace Numeric {
template <typename IntType> struct LSFRPolynomial {};
@@ -67,8 +69,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
determining the bit that was just shifted out.
*/
IntType next() {
const auto result = value_ & 1;
value_ = (value_ >> 1) ^ (result * polynomial);
const auto result = IntType(value_ & 1);
value_ = IntType((value_ >> 1) ^ (result * polynomial));
return result;
}
@@ -76,6 +78,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
IntType value_ = 0;
};
template <uint64_t polynomial> class LFSRv: public LFSR<typename MinIntTypeValue<polynomial>::type, polynomial> {};
}
#endif /* LFSR_h */

View File

@@ -10,6 +10,7 @@
#define Sizes_h
#include <limits>
#include <type_traits>
/*!
Maps to the smallest integral type that can contain max_value, from the following options:

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -56,6 +56,18 @@
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Desktop/Soft/Apple II/WOZs/Prince of Persia side A.woz&quot;"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--rompath=~/ROMs"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--has-disk-drive"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image&quot;"
isEnabled = "NO">
@@ -70,11 +82,15 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--volume=0.001"
isEnabled = "NO">
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=amstradcpc"
isEnabled = "NO">
argument = "--new=enterprise"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--basic-version=any"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col&quot;"
@@ -106,14 +122,22 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--rompath=/Users/thomasharte/Projects/CLK/ROMImages"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--help"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--model=cpc6128"
argument = "--help"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--model=mac512k"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=macintosh"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--use-square-pixels"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -73,6 +73,7 @@
debugDocumentVersioning = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
enableGPUShaderValidationMode = "2"
allowLocationSimulation = "NO">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -11,12 +11,24 @@ import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
private var failedMetalCheck = false
func applicationDidFinishLaunching(_ notification: Notification) {
// Insert code here to initialize your application.
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application.
// Check for at least one Metal-capable GPU; this check
// will become unnecessary if/when the minimum OS version
// that this project supports reascends to 10.14.
if (MTLCopyAllDevices().count == 0) {
self.failedMetalCheck = true
let alert = NSAlert()
alert.messageText = "This application requires a Metal-capable GPU"
alert.addButton(withTitle: "Exit")
alert.runModal()
let application = notification.object as! NSApplication
application.terminate(self)
}
}
private var hasShownOpenDocument = false

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -13,17 +13,27 @@
</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">
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="AppleIIOptionsPanel" 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="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="contentRect" x="80" y="150" width="200" height="159"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
<rect key="frame" x="0.0" y="0.0" width="200" height="84"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kDb-7g-cVx">
<rect key="frame" x="18" y="47" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Use Square Pixels" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="h9q-Wb-em8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionDidChange:" target="ZW7-Bw-4RP" id="pNS-aK-0no"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ex3-VM-58z">
<rect key="frame" x="18" y="17" width="165" height="25"/>
<rect key="frame" x="17" y="16" width="167" height="25"/>
<popUpButtonCell key="cell" type="push" title="Colour" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="gOu-dv-tre" id="u3N-Je-c2L">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -40,16 +50,20 @@
</popUpButton>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" id="4ZS-q5-TJL"/>
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" symbolic="YES" id="4ZS-q5-TJL"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="8Pj-Ns-TrJ"/>
<constraint firstAttribute="trailing" secondItem="kDb-7g-cVx" secondAttribute="trailing" constant="20" symbolic="YES" id="KHa-of-eY7"/>
<constraint firstItem="kDb-7g-cVx" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" symbolic="YES" id="OcZ-Xa-394"/>
<constraint firstAttribute="trailing" secondItem="ex3-VM-58z" secondAttribute="trailing" constant="20" id="QWA-lY-ugz"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="szw-WO-3tS"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="kDb-7g-cVx" secondAttribute="bottom" constant="8" id="jDW-N8-c4V"/>
<constraint firstItem="kDb-7g-cVx" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" symbolic="YES" id="wdj-PF-zxO"/>
</constraints>
</view>
<connections>
<outlet property="displayTypeButton" destination="ex3-VM-58z" id="lmZ-aN-lcj"/>
<outlet property="squarePixelButton" destination="kDb-7g-cVx" id="zbx-o8-7yC"/>
</connections>
<point key="canvasLocation" x="-161" y="38.5"/>
<point key="canvasLocation" x="-161" y="104.5"/>
</window>
</objects>
</document>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -13,17 +13,17 @@
</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" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" 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="121"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" id="aQh-Pm-DEo">
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rQO-uD-fwn">
<rect key="frame" x="14" y="73" width="86" height="32"/>
<rect key="frame" x="13" y="74" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l3H-0m-aK0">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -33,7 +33,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="3qw-C1-NYW">
<rect key="frame" x="18" y="58" width="164" height="18"/>
<rect key="frame" x="18" y="58" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Black and White" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UP7-mf-IKo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -43,7 +43,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Xbc-cw-Sc2">
<rect key="frame" x="18" y="38" width="164" height="18"/>
<rect key="frame" x="18" y="36" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Left Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="wlJ-8s-PEh">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -53,7 +53,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="kPV-Tm-TTc">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<rect key="frame" x="18" y="14" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Right Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="F05-cA-66S">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -63,7 +63,7 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nt7-8K-xY9">
<rect key="frame" x="100" y="73" width="86" height="32"/>
<rect key="frame" x="99" y="74" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Select" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8Na-Z1-EXS">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>

View File

@@ -0,0 +1,37 @@
//
// AppleIIOptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
class AppleIIOptionsPanel: MachinePanel {
var appleII: CSAppleII! {
get {
return self.machine.appleII
}
}
var squarePixelsUserDefaultsKey: String {
return prefixedUserDefaultsKey("useSquarePixels")
}
@IBOutlet var squarePixelButton: NSButton!
@IBAction func optionDidChange(_ sender: AnyObject!) {
let useSquarePixels = squarePixelButton.state == .on
appleII.useSquarePixels = useSquarePixels
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.set(useSquarePixels, forKey: squarePixelsUserDefaultsKey)
}
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
let useSquarePixels = standardUserDefaults.bool(forKey: squarePixelsUserDefaultsKey)
appleII.useSquarePixels = useSquarePixels
squarePixelButton.state = useSquarePixels ? .on : .off
}
}

View File

@@ -87,6 +87,14 @@ class MachineDocument:
}
}
private func dismissPanels() {
activityPanel?.setIsVisible(false)
activityPanel = nil
optionsPanel?.setIsVisible(false)
optionsPanel = nil
}
override func close() {
// Close any dangling sheets.
//
@@ -105,11 +113,7 @@ class MachineDocument:
machine?.stop()
// Dismiss panels.
activityPanel?.setIsVisible(false)
activityPanel = nil
optionsPanel?.setIsVisible(false)
optionsPanel = nil
dismissPanels()
// End the update cycle.
actionLock.lock()
@@ -133,23 +137,27 @@ class MachineDocument:
volumeSlider.floatValue = userDefaultsVolume()
}
private var missingROMs: [CSMissingROM] = []
private var missingROMs: String = ""
func configureAs(_ analysis: CSStaticAnalyser) {
self.machineDescription = analysis
let missingROMs = NSMutableArray()
actionLock.lock()
drawLock.lock()
let missingROMs = NSMutableString()
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
setRomRequesterIsVisible(false)
self.machine = machine
setupActivityDisplay()
machine.setVolume(userDefaultsVolume())
setupMachineOutput()
} else {
// Store the selected machine and list of missing ROMs, and
// show the missing ROMs dialogue.
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
self.missingROMs = missingROMs as String
requestRoms()
}
actionLock.unlock()
drawLock.unlock()
}
enum InteractionMode {
@@ -200,6 +208,9 @@ class MachineDocument:
let aspectRatio = self.aspectRatio()
machine.setView(scanTargetView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
// Get rid of all existing accessory panels.
dismissPanels()
// Attach an options panel if one is available.
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
@@ -208,11 +219,10 @@ class MachineDocument:
showOptions(self)
}
machine.delegate = self
// Create and populate an activity display if required.
setupActivityDisplay()
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate.
// scanTargetView.delegate = self
machine.delegate = self
scanTargetView.responderDelegate = self
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
@@ -252,7 +262,7 @@ class MachineDocument:
let isStereo = self.machine.isStereo
if selectedSamplingRate > 0 {
// [Re]create the audio queue only if necessary.
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate {
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate || self.audioQueue != self.machine.audioQueue {
self.machine.audioQueue = nil
self.audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate), isStereo:isStereo)
self.audioQueue.delegate = self
@@ -280,8 +290,7 @@ class MachineDocument:
/// Delegate message to receive drag and drop files.
final func scanTargetView(_ view: CSScanTargetView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
mediaSet.apply(to: self.machine)
insertFile(URL)
}
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
@@ -292,13 +301,30 @@ class MachineDocument:
openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in
if response == .OK {
for url in openPanel.urls {
let mediaSet = CSMediaSet(fileAt: url)
mediaSet.apply(to: self.machine)
self.insertFile(url)
}
}
}
}
private func insertFile(_ URL: URL) {
// Try to insert media.
let mediaSet = CSMediaSet(fileAt: URL)
if !mediaSet.empty {
mediaSet.apply(to: self.machine)
return
}
// Failing that see whether a new machine is required.
// TODO.
if let newMachine = CSStaticAnalyser(fileAt: URL) {
machine?.stop()
self.interactionMode = .notStarted
self.scanTargetView.willChangeScanTargetOwner()
configureAs(newMachine)
}
}
// MARK: - Input Management.
/// Upon a resign key, immediately releases all ongoing input mechanisms any currently pressed keys,
@@ -387,23 +413,42 @@ class MachineDocument:
@IBOutlet var romReceiverErrorField: NSTextField?
@IBOutlet var romReceiverView: CSROMReceiverView?
private var romRequestBaseText = ""
private func setRomRequesterIsVisible(_ visible : Bool) {
if !visible && self.romRequesterPanel == nil {
return;
}
if self.romRequesterPanel!.isVisible == visible {
return
}
if visible {
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
} else {
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
}
}
func requestRoms() {
// Don't act yet if there's no window controller yet.
if self.windowControllers.count == 0 {
return
}
// Load the ROM requester dialogue.
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
self.romReceiverView!.delegate = self
self.romRequestBaseText = romRequesterText!.stringValue
romReceiverErrorField?.alphaValue = 0.0
// Load the ROM requester dialogue if it's not already loaded.
if self.romRequesterPanel == nil {
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
self.romReceiverView!.delegate = self
self.romRequestBaseText = romRequesterText!.stringValue
romReceiverErrorField?.alphaValue = 0.0
}
// Populate the current absentee list.
populateMissingRomList()
// Show the thing.
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
setRomRequesterIsVisible(true)
}
@IBAction func cancelRequestROMs(_ sender: NSButton?) {
@@ -411,89 +456,17 @@ class MachineDocument:
}
func populateMissingRomList() {
// Fill in the missing details; first build a list of all the individual
// line items.
var requestLines: [String] = []
for missingROM in self.missingROMs {
if let descriptiveName = missingROM.descriptiveName {
requestLines.append("" + descriptiveName)
} else {
requestLines.append("" + missingROM.fileName)
}
}
// Suffix everything up to the penultimate line with a semicolon;
// the penultimate line with a semicolon and a conjunctive; the final
// line with a full stop.
for x in 0 ..< requestLines.count {
if x < requestLines.count - 2 {
requestLines[x].append(";")
} else if x < requestLines.count - 1 {
requestLines[x].append("; and")
} else {
requestLines[x].append(".")
}
}
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
romRequesterText!.stringValue = self.romRequestBaseText + self.missingROMs
}
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
// Test whether the file identified matches any of the currently missing ROMs.
// If so then remove that ROM from the missing list and update the request screen.
// If no ROMs are still missing, start the machine.
do {
let fileData = try Data(contentsOf: URL)
var didInstallRom = false
// Try to match by size first, CRC second. Accept that some ROMs may have
// some additional appended data. Arbitrarily allow them to be up to 10kb
// too large.
var index = 0
for missingROM in self.missingROMs {
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
// Trim to size.
let trimmedData = fileData[0 ..< missingROM.size]
// Get CRC.
if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) {
// This ROM matches; copy it into the application library,
// strike it from the missing ROM list and decide how to
// proceed.
let fileManager = FileManager.default
let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("ROMImages")
.appendingPathComponent(missingROM.machineName)
let targetFile = targetPath
.appendingPathComponent(missingROM.fileName)
do {
try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil)
try trimmedData.write(to: targetFile)
} catch let error {
showRomReceiverError(error: "Couldn't write to application support directory: \(error)")
}
self.missingROMs.remove(at: index)
didInstallRom = true
break
}
}
index = index + 1
}
if didInstallRom {
if self.missingROMs.count == 0 {
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
configureAs(self.machineDescription!)
} else {
populateMissingRomList()
}
} else {
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
}
} catch let error {
showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)")
if CSMachine.attemptInstallROM(URL) {
configureAs(self.machineDescription!)
} else {
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
}
}

View File

@@ -592,6 +592,66 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sna</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SNA snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>z80</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum Z80 snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>szx</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SZX snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@@ -368,7 +368,7 @@ API_AVAILABLE(macos(11.0))
return self;
}
-(void)update {
- (void)update {
// Update buttons.
for(CSGCJoystickButton *button in _buttons) {
// This assumes that the values provided by GCDeviceButtonInput are
@@ -379,7 +379,11 @@ API_AVAILABLE(macos(11.0))
float val = axis.axis.value;
val += 1;
val /= 2;
axis.position = val;
if(axis.type == CSJoystickAxisTypeY) {
axis.position = 1 - val;
} else {
axis.position = val;
}
}
for(CSGCJoystickHat *hat in _hats) {
// This assumes that the values provided by GCDeviceDirectionPad are

View File

@@ -33,20 +33,15 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
CSMachineKeyboardInputModeJoystick,
};
@interface CSMissingROM: NSObject
@property (nonatomic, readonly, nonnull) NSString *machineName;
@property (nonatomic, readonly, nonnull) NSString *fileName;
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
@property (nonatomic, readonly) NSUInteger size;
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
@end
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
#import "CSZX8081.h"
#import "CSAppleII.h"
@interface CSMachine : NSObject
+ (BOOL)attemptInstallROM:(nonnull NSURL *)url;
- (nonnull instancetype)init NS_UNAVAILABLE;
/*!
@@ -56,7 +51,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
were not found; populated only if this `init` has failed.
*/
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableString *)missingROMs NS_DESIGNATED_INITIALIZER;
- (float)idealSamplingRateFromRange:(NSRange)range;
@property (readonly, getter=isStereo) BOOL stereo;
@@ -109,5 +104,6 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
@property (nonatomic, readonly, nullable) CSZX8081 *zx8081;
@property (nonatomic, readonly, nullable) CSAppleII *appleII;
@end

View File

@@ -31,6 +31,8 @@
#include <atomic>
#include <bitset>
#include <codecvt>
#include <locale>
@interface CSMachine() <CSScanTargetViewDisplayLinkDelegate>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
@@ -74,28 +76,6 @@ struct ActivityObserver: public Activity::Observer {
__unsafe_unretained CSMachine *machine;
};
@interface CSMissingROM (/*Mutability*/)
@property (nonatomic, nonnull, copy) NSString *machineName;
@property (nonatomic, nonnull, copy) NSString *fileName;
@property (nonatomic, nullable, copy) NSString *descriptiveName;
@property (nonatomic, readwrite) NSUInteger size;
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
@end
@implementation CSMissingROM
@synthesize machineName=_machineName;
@synthesize fileName=_fileName;
@synthesize descriptiveName=_descriptiveName;
@synthesize size=_size;
@synthesize crc32s=_crc32s;
- (NSString *)description {
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
}
@end
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
ActivityObserver _activityObserver;
@@ -126,35 +106,18 @@ struct ActivityObserver: public Activity::Observer {
NSMutableArray<dispatch_block_t> *_inputEvents;
}
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableString *)missingROMs {
self = [super init];
if(self) {
_analyser = result;
Machine::Error error;
std::vector<ROMMachine::ROM> missing_roms;
ROM::Request missing_roms;
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
if(!_machine) {
for(const auto &missing_rom : missing_roms) {
CSMissingROM *rom = [[CSMissingROM alloc] init];
// Copy/convert the primitive fields.
rom.machineName = @(missing_rom.machine_name.c_str());
rom.fileName = @(missing_rom.file_name.c_str());
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : @(missing_rom.descriptive_name.c_str());
rom.size = missing_rom.size;
// Convert the CRC list.
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] initWithCapacity:missing_rom.crc32s.size()];
for(const auto &crc : missing_rom.crc32s) {
[crc32s addObject:@(crc)];
}
rom.crc32s = crc32s;
// Add to the missing list.
[missingROMs addObject:rom];
}
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
const std::wstring description = missing_roms.description(0, L'•');
[missingROMs appendString:[NSString stringWithUTF8String:wstring_converter.to_bytes(description).c_str()]];
return nil;
}
@@ -614,6 +577,10 @@ struct ActivityObserver: public Activity::Observer {
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
}
- (CSAppleII *)appleII {
return [[CSAppleII alloc] initWithAppleII:_machine->raw_pointer() owner:self];
}
#pragma mark - Input device queries
- (BOOL)hasJoystick {
@@ -783,4 +750,8 @@ struct ActivityObserver: public Activity::Observer {
_timer = nil;
}
+ (BOOL)attemptInstallROM:(NSURL *)url {
return CSInstallROM(url);
}
@end

View File

@@ -8,4 +8,5 @@
#include "ROMMachine.hpp"
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr);
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
BOOL CSInstallROM(NSURL *);

View File

@@ -11,47 +11,96 @@
#import "NSBundle+DataResource.h"
#import "NSData+StdVector.h"
#import "NSData+CRC32.h"
#include <string>
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) {
return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
namespace {
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(const auto &rom: roms) {
NSData *fileData;
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]];
NSString *directoryFor(const ROM::Description &description) {
return [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:description.machine_name.c_str()]];
}
// Check for this file first within the application support directories.
for(NSURL *supportURL in supportURLs) {
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]];
fileData = [NSData dataWithContentsOfURL:fullURL];
if(fileData) break;
NSArray<NSURL *> *urlsFor(const ROM::Description &description, const std::string &file_name) {
NSMutableArray<NSURL *> *const urls = [[NSMutableArray alloc] init];
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
NSString *const subdirectory = directoryFor(description);
for(NSURL *supportURL in supportURLs) {
[urls addObject:[[supportURL URLByAppendingPathComponent:subdirectory]
URLByAppendingPathComponent:[NSString stringWithUTF8String:file_name.c_str()]]];
}
return urls;
}
}
BOOL CSInstallROM(NSURL *url) {
NSData *const data = [NSData dataWithContentsOfURL:url];
if(!data) return NO;
// Try for a direct CRC match.
std::optional<ROM::Description> target_description;
target_description = ROM::Description::from_crc(uint32_t(data.crc32.integerValue));
// See whether there's an acceptable trimming that creates a CRC match.
if(!target_description) {
const std::vector<ROM::Description> descriptions = ROM::all_descriptions();
for(const auto &description: descriptions) {
if(description.size > data.length) continue;
NSData *const trimmedData = [data subdataWithRange:NSMakeRange(0, description.size)];
if(description.crc32s.find(uint32_t(trimmedData.crc32.unsignedIntValue)) != description.crc32s.end()) {
target_description = description;
break;
}
}
}
// Failing that, check inside the application bundle.
if(!fileData) {
fileData = [[NSBundle mainBundle]
dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()]
withExtension:nil
subdirectory:subdirectory];
}
// If no destination was found, stop.
if(!target_description) {
return NO;
}
// Store an appropriate result, accumulating a list of the missing if requested.
if(!fileData) {
results.emplace_back(nullptr);
// Copy the data to its destination and report success.
NSURL *const targetURL = [urlsFor(*target_description, target_description->file_names[0]) firstObject];
[[NSFileManager defaultManager] createDirectoryAtPath:targetURL.URLByDeletingLastPathComponent.path withIntermediateDirectories:YES attributes:nil error:nil];
[data writeToURL:targetURL atomically:NO];
if(missing_roms) {
missing_roms->push_back(rom);
return YES;
}
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
return [missing] (const ROM::Request &roms) -> ROM::Map {
ROM::Map results;
for(const auto &description: roms.all_descriptions()) {
for(const auto &file_name: description.file_names) {
NSData *fileData;
// Check for this file first within the application support directories.
for(NSURL *fileURL in urlsFor(description, file_name)) {
fileData = [NSData dataWithContentsOfURL:fileURL];
if(fileData) break;
}
// Failing that, check inside the application bundle.
if(!fileData) {
fileData = [[NSBundle mainBundle]
dataForResource:[NSString stringWithUTF8String:file_name.c_str()]
withExtension:nil
subdirectory:directoryFor(description)];
}
// Store an appropriate result.
if(fileData) {
results[description.name] = fileData.stdVector8;
}
}
else {
auto data = std::make_unique<std::vector<std::uint8_t>>();
*data = fileData.stdVector8;
results.emplace_back(std::move(data));
}
}
if(missing) {
*missing = roms.subtract(results);
}
return results;

View File

@@ -41,6 +41,30 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
CSMachineCPCModel6128
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseModel) {
CSMachineEnterpriseModel64,
CSMachineEnterpriseModel128,
CSMachineEnterpriseModel256,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseEXOS) {
CSMachineEnterpriseEXOSVersion21,
CSMachineEnterpriseEXOSVersion20,
CSMachineEnterpriseEXOSVersion10,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseBASIC) {
CSMachineEnterpriseBASICVersion21,
CSMachineEnterpriseBASICVersion11,
CSMachineEnterpriseBASICVersion10,
CSMachineEnterpriseBASICNone,
};
typedef NS_ENUM(NSInteger, CSMachineEnterpriseDOS) {
CSMachineEnterpriseDOSEXDOS,
CSMachineEnterpriseDOSNone,
};
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
CSMachineMacintoshModel128k,
CSMachineMacintoshModel512k,
@@ -96,6 +120,7 @@ typedef int Kilobytes;
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
@@ -114,6 +139,8 @@ typedef int Kilobytes;
- (instancetype)initWithFileAtURL:(NSURL *)url;
- (void)applyToMachine:(CSMachine *)machine;
@property(nonatomic, readonly) BOOL empty;
@end
NS_ASSUME_NONNULL_END

View File

@@ -19,6 +19,7 @@
#include "../../../../../Analyser/Static/AppleIIgs/Target.hpp"
#include "../../../../../Analyser/Static/AtariST/Target.hpp"
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../../../Analyser/Static/Enterprise/Target.hpp"
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
@@ -130,6 +131,45 @@
return self;
}
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
self = [super init];
if(self) {
using Target = Analyser::Static::Enterprise::Target;
auto target = std::make_unique<Target>();
switch(model) {
case CSMachineEnterpriseModel64: target->model = Target::Model::Enterprise64; break;
default:
case CSMachineEnterpriseModel128: target->model = Target::Model::Enterprise128; break;
case CSMachineEnterpriseModel256: target->model = Target::Model::Enterprise256; break;
}
switch(exosVersion) {
case CSMachineEnterpriseEXOSVersion21: target->exos_version = Target::EXOSVersion::v21; break;
default:
case CSMachineEnterpriseEXOSVersion20: target->exos_version = Target::EXOSVersion::v20; break;
case CSMachineEnterpriseEXOSVersion10: target->exos_version = Target::EXOSVersion::v10; break;
}
switch(basicVersion) {
case CSMachineEnterpriseBASICNone: target->basic_version = Target::BASICVersion::None; break;
default:
case CSMachineEnterpriseBASICVersion21: target->basic_version = Target::BASICVersion::v21; break;
case CSMachineEnterpriseBASICVersion11: target->basic_version = Target::BASICVersion::v11; break;
case CSMachineEnterpriseBASICVersion10: target->basic_version = Target::BASICVersion::v10; break;
}
switch(dos) {
case CSMachineEnterpriseDOSEXDOS: target->dos = Target::DOS::EXDOS; break;
default:
case CSMachineEnterpriseDOSNone: target->dos = Target::DOS::None; break;
}
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
self = [super init];
if(self) {
@@ -276,6 +316,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
case Analyser::Machine::AtariST: return @"CompositeOptions";
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::Enterprise: return @"CompositeOptions";
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
@@ -309,4 +350,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
[machine applyMedia:_media];
}
- (BOOL)empty {
return _media.empty();
}
@end

View File

@@ -0,0 +1,18 @@
//
// CSAppleII.h
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
@class CSAppleII;
#import "CSMachine.h"
@interface CSAppleII : NSObject
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine;
@property (nonatomic, assign) BOOL useSquarePixels;
@end

View File

@@ -0,0 +1,57 @@
//
// CSAppleII.m
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import "CSAppleII.h"
#include "AppleII.hpp"
@implementation CSAppleII {
Apple::II::Machine *_appleII;
__weak CSMachine *_machine;
}
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine {
self = [super init];
if(self) {
_appleII = (Apple::II::Machine *)appleII;
_machine = machine;
}
return self;
}
#pragma mark - Options
- (void)setUseSquarePixels:(BOOL)useSquarePixels {
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
#ifndef NDEBUG
assert(configurable);
#endif
@synchronized(_machine) {
auto options = configurable->get_options();
#ifndef NDEBUG
assert(dynamic_cast<Apple::II::Machine::Options *>(options.get()));
#endif
auto appleii_configurable = static_cast<Apple::II::Machine::Options *>(options.get());
appleii_configurable->use_square_pixels = useSquarePixels;
configurable->set_options(options);
}
}
- (BOOL)useSquarePixels {
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
@synchronized(_machine) {
auto options = configurable->get_options();
auto appleii_configurable = dynamic_cast<Apple::II::Machine::Options *>(options.get());
return appleii_configurable->use_square_pixels ? YES : NO;
}
}
@end

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,10 +17,10 @@
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
<rect key="contentRect" x="196" y="240" width="590" height="353"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
<rect key="frame" x="0.0" y="0.0" width="590" height="353"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
@@ -59,16 +59,16 @@ Gw
</textFieldCell>
</textField>
<tabView type="noTabsBezelBorder" translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
<rect key="frame" x="154" y="56" width="420" height="206"/>
<rect key="frame" x="154" y="56" width="420" height="243"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" 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="18" y="133" width="96" height="16"/>
<rect key="frame" x="18" y="155" 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"/>
@@ -84,7 +84,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
<rect key="frame" x="67" y="157" width="117" height="25"/>
<rect key="frame" x="67" y="179" width="117" height="25"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -99,7 +99,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
<rect key="frame" x="117" y="127" width="134" height="25"/>
<rect key="frame" x="117" y="149" width="134" height="25"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -115,11 +115,11 @@ Gw
</subviews>
<constraints>
<constraint firstItem="WnO-ef-IC6" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="5RF-1w-9HW"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="5" id="865-cv-qVk"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="20" symbolic="YES" id="865-cv-qVk"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="trailing" constant="20" symbolic="YES" id="9GL-al-1qi"/>
<constraint firstItem="WnO-ef-IC6" firstAttribute="centerY" secondItem="LSB-WP-FMi" secondAttribute="centerY" id="Fuj-zT-MIm"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="jli-ac-Sij" secondAttribute="trailing" constant="20" symbolic="YES" id="I8d-OR-ICN"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="5" id="Qi1-CV-A0c"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="20" symbolic="YES" id="Qi1-CV-A0c"/>
<constraint firstItem="V5Z-dX-Ns4" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="SWc-iX-1We"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="leading" secondItem="WnO-ef-IC6" secondAttribute="trailing" constant="8" symbolic="YES" id="bte-XA-xNQ"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="top" secondItem="jli-ac-Sij" secondAttribute="bottom" constant="10" symbolic="YES" id="ki5-JR-vRe"/>
@@ -129,12 +129,12 @@ Gw
</view>
</tabViewItem>
<tabViewItem label="Apple IIgs" identifier="appleiigs" id="u5E-8n-ghF">
<view key="view" ambiguous="YES" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<view key="view" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -142,7 +142,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<rect key="frame" x="18" y="23" width="85" height="16"/>
<rect key="frame" x="18" y="155" width="85" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory size:" id="OLJ-nC-yyj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -150,7 +150,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gcS-uy-mzl">
<rect key="frame" x="67" y="47" width="89" height="25"/>
<rect key="frame" x="67" y="179" width="89" height="25"/>
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -164,7 +164,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nQa-YS-utT">
<rect key="frame" x="106" y="17" width="82" height="25"/>
<rect key="frame" x="106" y="149" width="82" height="25"/>
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -182,24 +182,24 @@ Gw
<constraint firstItem="LES-76-Ovz" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="2D3-Ve-6CN"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="8Xz-86-tDf"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="centerY" secondItem="gcS-uy-mzl" secondAttribute="centerY" id="Eww-Qq-eBT"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="5" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="20" symbolic="YES" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="leading" secondItem="0d9-IG-gKU" secondAttribute="trailing" constant="8" symbolic="YES" id="LUm-rI-LYP"/>
<constraint firstItem="LES-76-Ovz" firstAttribute="centerY" secondItem="nQa-YS-utT" secondAttribute="centerY" id="UdP-U6-OFE"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="leading" secondItem="LES-76-Ovz" secondAttribute="trailing" constant="8" symbolic="YES" id="Xm1-iG-Dgj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gcS-uy-mzl" secondAttribute="trailing" constant="20" symbolic="YES" id="a38-Cx-CEh"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="top" secondItem="gcS-uy-mzl" secondAttribute="bottom" constant="10" symbolic="YES" id="aM9-m8-s7z"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="trailing" constant="20" symbolic="YES" id="cqx-Jc-rUb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="5" id="sbT-If-NTU"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="20" symbolic="YES" id="sbT-If-NTU"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="67" y="47" width="96" height="25"/>
<rect key="frame" x="67" y="179" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -213,7 +213,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -224,8 +224,8 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="20" symbolic="YES" id="4AF-5C-2IF"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="Wof-5h-gfD"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="5" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="5" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="20" symbolic="YES" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" symbolic="YES" id="mA8-US-ndo"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
</constraints>
@@ -233,11 +233,11 @@ Gw
</tabViewItem>
<tabViewItem label="Atari ST" identifier="atarist" id="a6Y-mx-yFn">
<view key="view" id="nnv-Wi-7hc">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nLf-LI-nWO">
<rect key="frame" x="18" y="57" width="728" height="16"/>
<rect key="frame" x="18" y="204" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present only a 512k Atari ST is supported." id="gBA-ke-mur">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -254,32 +254,32 @@ Gw
</tabViewItem>
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
<view key="view" id="SRc-2D-95G">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
<rect key="frame" x="18" y="56" width="168" height="18"/>
<rect key="frame" x="18" y="186" width="168" height="18"/>
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
<rect key="frame" x="18" y="34" width="232" height="18"/>
<rect key="frame" x="18" y="164" width="232" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cG2-Ph-S3Z">
<rect key="frame" x="18" y="12" width="231" height="18"/>
<rect key="frame" x="18" y="142" width="231" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Plus 6 Utility ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yjF-XS-zx6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
<rect key="frame" x="18" y="-10" width="284" height="18"/>
<rect key="frame" x="18" y="120" width="284" height="18"/>
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -289,13 +289,13 @@ Gw
<constraints>
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="1iM-70-oZq"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="E9b-RP-9vj"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="5" id="FM6-AA-Vhf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="20" symbolic="YES" id="FM6-AA-Vhf"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="NfY-dE-aJw"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="cG2-Ph-S3Z" secondAttribute="bottom" constant="6" symbolic="YES" id="S45-42-Gtv"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="trailing" constant="20" symbolic="YES" id="X3p-qJ-ENH"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="b5a-SX-2ty"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" symbolic="YES" id="dmY-PV-ap4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="20" symbolic="YES" id="ggl-QH-mV4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cG2-Ph-S3Z" secondAttribute="trailing" constant="20" symbolic="YES" id="m6t-dP-71d"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" symbolic="YES" id="mvO-UZ-BtT"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="npw-IZ-6xU"/>
@@ -303,13 +303,133 @@ Gw
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Enterprise" identifier="enterprise" id="zhO-EO-wUe">
<view key="view" id="1cs-PX-RAH">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PhH-bu-pb5">
<rect key="frame" x="80" y="179" width="129" height="25"/>
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="ojI-Vb-iHz">
<items>
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
<menuItem title="Enterprise 128" state="on" tag="128" id="roH-nL-f8o"/>
<menuItem title="Enterprise 64" tag="64" id="vNG-Tv-bDI"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
<rect key="frame" x="64" y="149" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="uNC-hA-d5z">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
<menuItem title="Version 2.0" tag="20" id="XTj-l7-KX3"/>
<menuItem title="Version 1.0" tag="10" id="Ky2-2D-wcY"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
<rect key="frame" x="67" y="119" width="105" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="VcH-Xm-1hY">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
<menuItem title="Version 1.1" tag="11" id="7P2-aF-6fp"/>
<menuItem title="Version 1.0" tag="10" id="j8p-uY-BhG"/>
<menuItem title="None" id="eGk-Bj-IVT"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
<rect key="frame" x="57" y="89" width="83" height="25"/>
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="DOS" id="sdr-al-7mi">
<items>
<menuItem title="EXDOS" tag="1" id="8rP-2w-PdU"/>
<menuItem title="None" state="on" id="qoS-KO-iEZ"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<rect key="frame" x="18" y="155" width="43" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<rect key="frame" x="18" y="185" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<rect key="frame" x="18" y="125" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<rect key="frame" x="18" y="95" width="36" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="hIr-GH-7xi" secondAttribute="trailing" constant="20" symbolic="YES" id="44v-9O-y7L"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="ENF-TY-TQ7"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="leading" secondItem="ykc-W1-YaS" secondAttribute="trailing" constant="8" symbolic="YES" id="GWR-VI-9PG"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="20" symbolic="YES" id="K3s-FA-zMB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="PhH-bu-pb5" secondAttribute="trailing" constant="20" symbolic="YES" id="LwB-ef-uF4"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="top" secondItem="1cs-PX-RAH" secondAttribute="top" constant="20" symbolic="YES" id="Myt-i4-jVq"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="T3e-u7-fiQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="trailing" constant="20" symbolic="YES" id="TEX-Nw-y2K"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="TgR-RR-eA1"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="centerY" secondItem="syE-e7-TjU" secondAttribute="centerY" id="UYw-uz-Am0"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="top" secondItem="nen-Za-7zH" secondAttribute="bottom" constant="10" symbolic="YES" id="VOc-2v-s3u"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="top" secondItem="hIr-GH-7xi" secondAttribute="bottom" constant="10" symbolic="YES" id="W9S-on-Heq"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Yes-o3-V0m"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="bAP-lx-pdi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nen-Za-7zH" secondAttribute="trailing" constant="20" symbolic="YES" id="eEI-5Q-TnO"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="leading" secondItem="pxr-Bq-yh0" secondAttribute="trailing" constant="8" symbolic="YES" id="fmM-Ma-Jyu"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="leading" secondItem="dzd-tH-BjX" secondAttribute="trailing" constant="8" symbolic="YES" id="jDQ-TF-Ogf"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
<view key="view" id="7Yf-vi-Q0W">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -317,7 +437,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5">
<rect key="frame" x="67" y="47" width="75" height="25"/>
<rect key="frame" x="67" y="179" width="75" height="25"/>
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -333,9 +453,9 @@ Gw
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="5" id="3hY-Ca-mnR"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="20" symbolic="YES" id="3hY-Ca-mnR"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="20" symbolic="YES" id="5s6-87-VT6"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="5" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="20" symbolic="YES" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="20" symbolic="YES" 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" symbolic="YES" id="ktS-sr-F8L"/>
@@ -344,18 +464,18 @@ Gw
</tabViewItem>
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
<view key="view" id="mWD-An-tR7">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
<rect key="frame" x="18" y="26" width="128" height="18"/>
<rect key="frame" x="18" y="158" width="128" height="18"/>
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LG6-mP-SeG">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -369,7 +489,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -380,9 +500,9 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="20" symbolic="YES" id="0Oc-n7-gaM"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="8" symbolic="YES" id="LBt-4m-GDc"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="5" id="bcb-ZZ-VpQ"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="20" symbolic="YES" id="bcb-ZZ-VpQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="20" symbolic="YES" id="l8P-UW-8ig"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="3" id="mga-YX-Bek"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="20" symbolic="YES" id="mga-YX-Bek"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="q8Q-kh-Opj"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="leading" secondItem="ZaD-7v-rMS" secondAttribute="trailing" constant="8" symbolic="YES" id="svb-nH-GlP"/>
<constraint firstItem="ZaD-7v-rMS" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="zgh-a5-FNF"/>
@@ -392,11 +512,11 @@ Gw
</tabViewItem>
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
<view key="view" id="sOR-e0-8iZ">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -404,7 +524,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ENP-hI-BVZ">
<rect key="frame" x="67" y="47" width="107" height="25"/>
<rect key="frame" x="67" y="179" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -418,7 +538,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
<rect key="frame" x="113" y="17" width="130" height="25"/>
<rect key="frame" x="113" y="149" width="130" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -434,7 +554,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<rect key="frame" x="18" y="23" width="92" height="16"/>
<rect key="frame" x="18" y="155" 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"/>
@@ -443,7 +563,7 @@ Gw
</textField>
</subviews>
<constraints>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="5" id="Bgq-8R-8I4"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="20" symbolic="YES" id="Bgq-8R-8I4"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="leading" secondItem="okM-ZI-NbF" secondAttribute="trailing" constant="8" symbolic="YES" id="Pra-lC-JPB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ENP-hI-BVZ" secondAttribute="trailing" constant="20" symbolic="YES" id="Sr5-QZ-xU3"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="leading" secondItem="0ct-tf-uRH" secondAttribute="trailing" constant="8" symbolic="YES" id="Wcg-1P-z6l"/>
@@ -453,17 +573,17 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="trailing" constant="20" symbolic="YES" id="ewV-Dw-zj2"/>
<constraint firstItem="0ct-tf-uRH" firstAttribute="leading" secondItem="sOR-e0-8iZ" secondAttribute="leading" constant="20" symbolic="YES" id="huH-9L-97Y"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="top" secondItem="ENP-hI-BVZ" secondAttribute="bottom" constant="10" symbolic="YES" id="l3T-Ve-0Jw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="5" id="mN9-AZ-wSn"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="20" symbolic="YES" id="mN9-AZ-wSn"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
<view key="view" id="fLI-XB-QCr">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -479,7 +599,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2eV-Us-eEv">
<rect key="frame" x="108" y="17" width="116" height="25"/>
<rect key="frame" x="108" y="149" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -493,7 +613,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -501,7 +621,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<rect key="frame" x="18" y="23" width="87" height="16"/>
<rect key="frame" x="18" y="155" 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"/>
@@ -509,7 +629,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lrf-gL-6EI">
<rect key="frame" x="18" y="-4" width="177" height="18"/>
<rect key="frame" x="18" y="128" width="177" height="18"/>
<buttonCell key="cell" type="check" title="Attach C-1540 disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tsq-YD-xw8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -521,7 +641,7 @@ Gw
<constraint firstItem="MTh-9p-FqC" firstAttribute="centerY" secondItem="ueK-gq-gaF" secondAttribute="centerY" id="8KD-Bm-KRA"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="leading" secondItem="MTh-9p-FqC" secondAttribute="trailing" constant="8" symbolic="YES" id="9m6-6j-8j2"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="trailing" constant="20" symbolic="YES" id="M08-mP-Plz"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="5" id="XN3-GK-BX9"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="20" symbolic="YES" id="XN3-GK-BX9"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ueK-gq-gaF" secondAttribute="trailing" constant="20" symbolic="YES" id="YZ9-7N-ssA"/>
<constraint firstItem="MTh-9p-FqC" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="bgZ-k9-IQC"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2eV-Us-eEv" secondAttribute="trailing" constant="20" symbolic="YES" id="eiB-vH-17d"/>
@@ -529,18 +649,18 @@ Gw
<constraint firstItem="2eV-Us-eEv" firstAttribute="leading" secondItem="gRS-DK-rIy" secondAttribute="trailing" constant="8" symbolic="YES" id="hQ9-uy-KHg"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="iyp-1K-rdC"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="centerY" secondItem="2eV-Us-eEv" secondAttribute="centerY" id="qWf-Nb-PfJ"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="3" id="vNb-Sw-Mxw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="20" symbolic="YES" id="vNb-Sw-Mxw"/>
<constraint firstItem="Lrf-gL-6EI" firstAttribute="top" secondItem="2eV-Us-eEv" secondAttribute="bottom" constant="8" symbolic="YES" id="zBX-Qq-j5f"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
<view key="view" id="8hL-Vn-Hg0">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
<rect key="frame" x="108" y="47" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -554,7 +674,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="18" y="53" width="87" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -562,7 +682,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
<rect key="frame" x="18" y="26" width="118" height="18"/>
<rect key="frame" x="18" y="158" width="118" height="18"/>
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -574,8 +694,8 @@ Gw
<constraint firstItem="NCX-4e-lSu" firstAttribute="centerY" secondItem="I1a-Eu-5UB" secondAttribute="centerY" id="1ve-sc-QwI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="leading" secondItem="NCX-4e-lSu" secondAttribute="trailing" constant="8" symbolic="YES" id="Bu6-60-74x"/>
<constraint firstItem="NCX-4e-lSu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="W09-iG-ARI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="5" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="3" id="fmT-7E-hUT"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="20" symbolic="YES" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="20" symbolic="YES" id="fmT-7E-hUT"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="top" secondItem="I1a-Eu-5UB" secondAttribute="bottom" constant="8" symbolic="YES" id="hYk-xC-63o"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="qen-KS-rWi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="trailing" constant="20" symbolic="YES" id="r7F-DT-oRc"/>
@@ -584,11 +704,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="108" y="157" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -602,7 +722,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="163" width="87" height="16"/>
<rect key="frame" x="18" y="185" 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"/>
@@ -613,37 +733,37 @@ Gw
<constraints>
<constraint firstItem="8tU-73-XEE" firstAttribute="centerY" secondItem="5aO-UX-HnX" secondAttribute="centerY" id="1Jm-YL-OKU"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="trailing" constant="20" symbolic="YES" id="ALj-0x-bFb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="5" id="LJ2-W9-fOf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="20" symbolic="YES" id="LJ2-W9-fOf"/>
<constraint firstItem="8tU-73-XEE" firstAttribute="leading" secondItem="bmd-gL-gzT" secondAttribute="leading" constant="20" symbolic="YES" id="M6Y-jN-LAf"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="5" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="20" symbolic="YES" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="leading" secondItem="8tU-73-XEE" secondAttribute="trailing" constant="8" symbolic="YES" id="j1u-n4-2IK"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
<view key="view" id="bMx-F6-JUb">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
<rect key="frame" x="67" y="157" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<rect key="frame" x="67" y="179" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+2a" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -655,23 +775,23 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="trailing" constant="20" symbolic="YES" id="90E-uI-MQg"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="leading" secondItem="bMx-F6-JUb" secondAttribute="leading" constant="20" symbolic="YES" id="9kE-iQ-dxd"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="centerY" secondItem="gFZ-d4-WFv" secondAttribute="centerY" id="LxG-5E-Q5Y"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="5" id="d8S-vX-B5e"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="20" symbolic="YES" id="d8S-vX-B5e"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="leading" secondItem="fJ3-ma-Byy" secondAttribute="trailing" constant="8" symbolic="YES" id="hKS-47-R2y"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="5" id="wsX-Wq-iPt"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="20" symbolic="YES" id="wsX-Wq-iPt"/>
</constraints>
</view>
</tabViewItem>
</tabViewItems>
</tabView>
<scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="z5Q-Bs-hJj">
<rect key="frame" x="20" y="60" width="130" height="201"/>
<rect key="frame" x="20" y="60" width="130" height="238"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<clipView key="contentView" id="O8s-Vw-9yQ">
<rect key="frame" x="1" y="1" width="128" height="199"/>
<rect key="frame" x="1" y="1" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" id="3go-Eb-GOy">
<rect key="frame" x="0.0" y="0.0" width="128" height="199"/>
<rect key="frame" x="0.0" y="0.0" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@@ -708,7 +828,7 @@ Gw
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<rect key="frame" x="18" y="269" width="554" height="27"/>
<rect key="frame" x="18" y="306" width="554" height="27"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -737,7 +857,7 @@ Gw
<constraint firstItem="9YM-5x-pc0" firstAttribute="top" secondItem="z5Q-Bs-hJj" secondAttribute="bottom" constant="14" id="suf-rn-Bmy"/>
</constraints>
</view>
<point key="canvasLocation" x="-1" y="88"/>
<point key="canvasLocation" x="-1" y="106.5"/>
</window>
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
<connections>
@@ -750,6 +870,10 @@ Gw
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
<outlet property="electronSidewaysRAMButton" destination="lzo-8g-o4S" id="LtS-wv-tMf"/>
<outlet property="enterpriseBASICButton" destination="hIr-GH-7xi" id="fM6-It-9UO"/>
<outlet property="enterpriseDOSButton" destination="syE-e7-TjU" id="sCW-Bj-ZTW"/>
<outlet property="enterpriseEXOSButton" destination="nen-Za-7zH" id="NwS-ua-FdA"/>
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>

View File

@@ -27,14 +27,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet var appleIIgsModelButton: NSPopUpButton!
@IBOutlet var appleIIgsMemorySizeButton: NSPopUpButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Electron properties
@IBOutlet var electronDFSButton: NSButton!
@IBOutlet var electronADFSButton: NSButton!
@IBOutlet var electronAP6Button: NSButton!
@IBOutlet var electronSidewaysRAMButton: NSButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Enterprise properties
@IBOutlet var enterpriseModelButton: NSPopUpButton!
@IBOutlet var enterpriseEXOSButton: NSPopUpButton!
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
// MARK: - Macintosh properties
@IBOutlet var macintoshModelTypeButton: NSPopUpButton!
@@ -93,14 +99,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel"))
appleIIgsMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsMemorySize"))
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Electron settings
electronDFSButton.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off
electronADFSButton.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off
electronAP6Button.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off
electronSidewaysRAMButton.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Enterprise settings
enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel"))
enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion"))
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
// Macintosh settings
macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
@@ -143,14 +155,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel")
standardUserDefaults.set(appleIIgsMemorySizeButton.selectedTag(), forKey: "new.appleIIgsMemorySize")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Electron settings
standardUserDefaults.set(electronDFSButton.state == .on, forKey: "new.electronDFS")
standardUserDefaults.set(electronADFSButton.state == .on, forKey: "new.electronADFS")
standardUserDefaults.set(electronAP6Button.state == .on, forKey: "new.electronAP6")
standardUserDefaults.set(electronSidewaysRAMButton.state == .on, forKey: "new.electronSidewaysRAM")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Enterprise settings
standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel")
standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion")
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
// Macintosh settings
standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel")
@@ -205,12 +223,6 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
storeOptions()
switch machineSelector.selectedTabViewItem!.identifier as! String {
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "appleii":
var model: CSMachineAppleIIModel = .appleII
@@ -255,6 +267,48 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
}
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "enterprise":
var model: CSMachineEnterpriseModel = .model128
switch enterpriseModelButton.selectedItem!.tag {
case 64: model = .model64
case 256: model = .model256
case 128: fallthrough
default: model = .model128
}
var exos: CSMachineEnterpriseEXOS = .version21
switch enterpriseEXOSButton.selectedItem!.tag {
case 10: exos = .version10
case 20: exos = .version20
case 21: fallthrough
default: exos = .version21
}
var basic: CSMachineEnterpriseBASIC = .version21
switch enterpriseBASICButton.selectedTag() {
case 0: basic = .none
case 10: basic = .version10
case 11: basic = .version11
case 21: fallthrough
default: basic = .version21
}
var dos: CSMachineEnterpriseDOS = .dosNone
switch enterpriseDOSButton.selectedTag() {
case 1: dos = .DOSEXDOS
case 0: fallthrough
default: dos = .dosNone
}
return CSStaticAnalyser(enterpriseModel: model, exosVersion: exos, basicVersion: basic, dos: dos)
case "mac":
switch macintoshModelTypeButton.selectedItem!.tag {
case 0: return CSStaticAnalyser(macintoshModel: .model128k)

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -20,18 +20,16 @@
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" customClass="CSROMReceiverView">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<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"/>
<rect key="frame" x="18" y="186" width="444" height="64"/>
<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 text:
</string>
<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 over this text</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>

View File

@@ -22,4 +22,6 @@
- (nonnull NSBitmapImageRep *)imageRepresentation;
- (void)willChangeOwner;
@end

Some files were not shown because too many files have changed in this diff Show More