]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Added Apple II/IIe firmware, fixed HD write emulation.
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James Hammons
5 // © 2018 Underground Software
6 //
7 // Parts loosely inspired by AppleWin by Tom Charlesworth which was based on
8 // AppleWin by Oliver Schmidt which was based on AppleWin by Michael O'Brien.
9 // :-)  Some parts (mainly TV rendering) are derived from ApplePC.  Too bad it
10 // was closed source--it could have been *the* premier Apple II emulator out
11 // there.
12 //
13 // JLH = James Hammons <jlhamm@acm.org>
14 //
15 // WHO  WHEN        WHAT
16 // ---  ----------  -----------------------------------------------------------
17 // JLH  11/12/2005  Initial port to SDL
18 // JLH  11/18/2005  Wired up graphic soft switches
19 // JLH  12/02/2005  Setup timer subsystem for more accurate time keeping
20 // JLH  12/12/2005  Added preliminary state saving support
21 // JLH  09/24/2013  Added //e support
22 //
23
24 // STILL TO DO:
25 //
26 // - Port to SDL [DONE]
27 // - Weed out unneeded functions [DONE]
28 // - Disk I/O [DONE]
29 // - 128K IIe related stuff [DONE]
30 // - State loading/saving [DONE]
31 // - GUI goodies
32 //
33 // BUGS:
34 //
35 // - Having a directory in the ${disks} directory causes a segfault in floppy
36 //
37
38 #include "apple2.h"
39
40 #include <SDL2/SDL.h>
41 #include <fstream>
42 #include <iomanip>
43 #include <iostream>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string>
47 #include <time.h>
48 #include "firmware/apple2-fw.h"
49 #include "firmware/apple2e-enh.h"
50 #include "firmware/firmware.h"
51 #include "floppydrive.h"
52 #include "harddrive.h"
53 #include "log.h"
54 #include "mmu.h"
55 #include "mockingboard.h"
56 #include "settings.h"
57 #include "sound.h"
58 #include "timing.h"
59 #include "video.h"
60 #include "gui/gui.h"
61 #include "gui/diskselector.h"
62
63 // Debug and misc. defines
64
65 #define THREADED_65C02
66 #define CPU_THREAD_OVERFLOW_COMPENSATION
67 #define DEBUG_LC
68 //#define CPU_CLOCK_CHECKING
69 //#define THREAD_DEBUGGING
70 #define SOFT_SWITCH_DEBUGGING
71
72 // Global variables
73
74 uint8_t ram[0x10000], rom[0x10000];                     // RAM & ROM spaces
75 uint8_t ram2[0x10000];                                          // Auxillary RAM
76 V65C02REGS mainCPU;                                                     // v65C02 execution context
77 uint8_t appleType = APPLE_TYPE_IIE;
78 bool powerStateChangeRequested = false;
79 uint64_t frameCycleStart;
80
81 uint64_t frameTicks = 0;
82 uint64_t frameTime[60];
83 uint32_t frameTimePtr = 0;
84
85 // Exported variables
86
87 uint8_t lastKeyPressed = 0;
88 bool keyDown = false;
89 bool openAppleDown = false;
90 bool closedAppleDown = false;
91 bool store80Mode = false;
92 bool vbl = false;
93 bool intCXROM = false;
94 bool slotC3ROM = false;
95 bool intC8ROM = false;
96 bool ramrd = false;
97 bool ramwrt = false;
98 bool altzp = false;
99 bool ioudis = true;
100 bool dhires = false;
101 // Language card state (ROM read, no write)
102 uint8_t lcState = 0x02;
103 uint8_t blinkTimer = 0;
104
105 static bool running = true;                                     // Machine running state flag...
106 static uint64_t startTicks;
107 static bool pauseMode = false;
108 static bool fullscreenDebounce = false;
109 static bool resetKeyDown = false;
110 static int8_t hideMouseTimeout = 60;
111
112 // Vars to handle the //e's 2-key rollover
113 static SDL_Keycode keysHeld[2];
114 static uint8_t keysHeldAppleCode[2];
115 static uint8_t keyDownCount = 0;
116 static uint8_t keyDelay = 0;
117
118 // Local functions
119
120 static void SaveApple2State(const char * filename);
121 static bool LoadApple2State(const char * filename);
122 static void ResetApple2State(void);
123 static void AppleTimer(uint16_t);
124
125 // Local timer callback functions
126
127 static void FrameCallback(void);
128 static void BlinkTimer(void);
129
130 #ifdef THREADED_65C02
131 // Test of threaded execution of 6502
132 static SDL_Thread * cpuThread = NULL;
133 static SDL_cond * cpuCond = NULL;
134 static SDL_sem * mainSem = NULL;
135 static bool cpuFinished = false;
136
137 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
138 //     This is a lie. At the end of each 65 cycle line, there is an elongated
139 //     cycle (the so-called 'long' cycle) that throws the calcs out of whack.
140 //     So actually, it's supposed to be 1,020,484.32 Hz
141
142 // Let's try a thread...
143 //
144 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
145 // it up.
146 //
147 static uint32_t sampleCount;
148 static uint64_t sampleClock, lastSampleClock;
149 int CPUThreadFunc(void * data)
150 {
151         // Mutex must be locked for conditional to work...
152         // Also, must be created in the thread that uses it...
153         SDL_mutex * cpuMutex = SDL_CreateMutex();
154
155 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
156 //      float overflow = 0.0;
157 #endif
158
159         do
160         {
161 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
162 uint64_t oldClock = mainCPU.clock;
163 sampleCount = 0;
164 sampleClock = lastSampleClock = mainCPU.clock;
165 // decrement mainSem...
166 #ifdef THREAD_DEBUGGING
167 WriteLog("CPU: SDL_SemWait(mainSem);\n");
168 #endif
169                 SDL_SemWait(mainSem);
170
171                 // There are exactly 800 slices of 21.333 cycles per frame, so it works
172                 // out evenly.
173                 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
174
175                 // Set our frame cycle counter to the correct # of cycles at the start
176                 // of this frame
177                 frameCycleStart = mainCPU.clock - mainCPU.overflow;
178 #ifdef THREAD_DEBUGGING
179 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
180 #endif
181                 for(int i=0; i<262; i++)
182                 {
183                         // If the CTRL+Reset key combo is being held, make sure the RESET
184                         // line stays asserted:
185                         if (resetKeyDown)
186                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
187
188                         Execute65C02(&mainCPU, 65);
189
190                         // According to "Understanding The Apple IIe", VBL asserted after
191                         // the last byte of the screen is read and let go on the first read
192                         // of the first byte of the screen. We now know that the screen
193                         // starts on line #6 and ends on line #197 (of the vertical
194                         // counter--actual VBLANK proper happens on lines 230 thru 233).
195                         vbl = ((i >= 6) && (i <= 197) ? true : false);
196                 }
197
198 //WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
199 //      frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
200 /*
201 Other timings from UTA2E:
202 Power-up reset                          32.6 msec / 512 horizontal scans
203 Flash cycle                                     1.87 Hz / Vertical freq./32
204 Delay before auto repeat        534-801 msec / 32-48 vertical scans
205 Auto repeat frequency           15 Hz / Vertical freq./4
206 Vertical frequency                      59.94 Hz (actually, 59.92 Hz [59.92274339401])
207 Horizontal frequency            15,734 Hz (actually, 15700 Hz)
208 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
209 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
210 1 line = 65 cycles
211 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
212 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
213 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
214 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
215
216 pg. 3-24 says one cycle before VBL the counters will be at
217 010111111/1111111 (that doesn't look right to me...)
218
219 Video address bits:
220
221 A0 <- H0
222 A1 <- H1
223 A2 <- H2
224 A3 <- SUM-A3
225 A4 <- SUM-A4
226 A5 <- SUM-A5
227 A6 <- SUM-A6
228 A7 <- V0
229 A8 <- V1
230 A9 <- V2
231
232 SUMS are calculated like so:
233
234   1        1       0       1
235           H5      H4      H3
236   V4      V3      V4      V3
237 ------------------------------
238 SUM-A6  SUM-A5  SUM-A4  SUM-A3
239
240 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
241 A12-A15 == 0
242
243 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
244 A10 == VA, A11 == VB, A12 == VC, A15 == 0
245
246 N.B.: VA-C are lower bits than V5-0
247
248 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
249 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
250
251 */
252
253 #ifdef THREAD_DEBUGGING
254 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
255 #endif
256                 SDL_mutexP(cpuMutex);
257                 // increment mainSem...
258 #ifdef THREAD_DEBUGGING
259 WriteLog("CPU: SDL_SemPost(mainSem);\n");
260 #endif
261                 SDL_SemPost(mainSem);
262 #ifdef THREAD_DEBUGGING
263 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
264 #endif
265                 SDL_CondWait(cpuCond, cpuMutex);
266
267 #ifdef THREAD_DEBUGGING
268 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
269 #endif
270                 SDL_mutexV(cpuMutex);
271         }
272         while (!cpuFinished);
273
274         SDL_DestroyMutex(cpuMutex);
275
276         return 0;
277 }
278 #endif
279
280
281 //
282 // Request a change in the power state of the emulated Apple
283 //
284 void SetPowerState(void)
285 {
286         powerStateChangeRequested = true;
287 }
288
289
290 //
291 // Load a file into RAM/ROM image space
292 //
293 bool LoadImg(char * filename, uint8_t * ram, int size)
294 {
295         FILE * fp = fopen(filename, "rb");
296
297         if (fp == NULL)
298                 return false;
299
300         fread(ram, 1, size, fp);
301         fclose(fp);
302
303         return true;
304 }
305
306
307 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.2";
308 static void SaveApple2State(const char * filename)
309 {
310         WriteLog("Main: Saving Apple2 state...\n");
311         FILE * file = fopen(filename, "wb");
312
313         if (!file)
314         {
315                 WriteLog("Could not open file \"%s\" for writing!\n", filename);
316                 return;
317         }
318
319         // Write out header
320         fwrite(stateHeader, 1, 18, file);
321
322         // Write out CPU state
323         fwrite(&mainCPU, 1, sizeof(mainCPU), file);
324
325         // Write out main memory
326         fwrite(ram, 1, 0x10000, file);
327         fwrite(ram2, 1, 0x10000, file);
328
329         // Write out state variables
330         fputc((uint8_t)keyDown, file);
331         fputc((uint8_t)openAppleDown, file);
332         fputc((uint8_t)closedAppleDown, file);
333         fputc((uint8_t)store80Mode, file);
334         fputc((uint8_t)vbl, file);
335         fputc((uint8_t)intCXROM, file);
336         fputc((uint8_t)slotC3ROM, file);
337         fputc((uint8_t)intC8ROM, file);
338         fputc((uint8_t)ramrd, file);
339         fputc((uint8_t)ramwrt, file);
340         fputc((uint8_t)altzp, file);
341         fputc((uint8_t)ioudis, file);
342         fputc((uint8_t)dhires, file);
343         fputc((uint8_t)flash, file);
344         fputc((uint8_t)textMode, file);
345         fputc((uint8_t)mixedMode, file);
346         fputc((uint8_t)displayPage2, file);
347         fputc((uint8_t)hiRes, file);
348         fputc((uint8_t)alternateCharset, file);
349         fputc((uint8_t)col80Mode, file);
350         fputc(lcState, file);
351
352         // Write out floppy state
353         floppyDrive[0].SaveState(file);
354
355         // Write out Mockingboard state
356         MBSaveState(file);
357         fclose(file);
358 }
359
360
361 static bool LoadApple2State(const char * filename)
362 {
363         WriteLog("Main: Loading Apple2 state...\n");
364         FILE * file = fopen(filename, "rb");
365
366         if (!file)
367         {
368                 WriteLog("Could not open file \"%s\" for reading!\n", filename);
369                 return false;
370         }
371
372         uint8_t buffer[18];
373         fread(buffer, 1, 18, file);
374
375         // Sanity check...
376         if (memcmp(buffer, stateHeader, 18) != 0)
377         {
378                 fclose(file);
379                 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
380                 return false;
381         }
382
383         // Read CPU state
384         fread(&mainCPU, 1, sizeof(mainCPU), file);
385
386         // Read main memory
387         fread(ram, 1, 0x10000, file);
388         fread(ram2, 1, 0x10000, file);
389
390         // Read in state variables
391         keyDown = (bool)fgetc(file);
392         openAppleDown = (bool)fgetc(file);
393         closedAppleDown = (bool)fgetc(file);
394         store80Mode = (bool)fgetc(file);
395         vbl = (bool)fgetc(file);
396         intCXROM = (bool)fgetc(file);
397         slotC3ROM = (bool)fgetc(file);
398         intC8ROM = (bool)fgetc(file);
399         ramrd = (bool)fgetc(file);
400         ramwrt = (bool)fgetc(file);
401         altzp = (bool)fgetc(file);
402         ioudis = (bool)fgetc(file);
403         dhires = (bool)fgetc(file);
404         flash = (bool)fgetc(file);
405         textMode = (bool)fgetc(file);
406         mixedMode = (bool)fgetc(file);
407         displayPage2 = (bool)fgetc(file);
408         hiRes = (bool)fgetc(file);
409         alternateCharset = (bool)fgetc(file);
410         col80Mode = (bool)fgetc(file);
411         lcState = fgetc(file);
412
413         // Read in floppy state
414         floppyDrive[0].LoadState(file);
415
416         // Read in Mockingboard state
417         MBLoadState(file);
418         fclose(file);
419
420         // Make sure things are in a sane state before execution :-P
421         mainCPU.RdMem = AppleReadMem;
422         mainCPU.WrMem = AppleWriteMem;
423         mainCPU.Timer = AppleTimer;
424         ResetMMUPointers();
425
426         return true;
427 }
428
429
430 static void ResetApple2State(void)
431 {
432         keyDown = false;
433         openAppleDown = false;
434         closedAppleDown = false;
435         store80Mode = false;
436         vbl = false;
437         intCXROM = false;
438         slotC3ROM = false;
439         intC8ROM = false;
440         ramrd = false;
441         ramwrt = false;
442         altzp = false;
443         ioudis = true;
444         dhires = false;
445         lcState = 0x02;
446         ResetMMUPointers();
447         MBReset();
448
449         // Without this, you can wedge the system :-/
450         memset(ram, 0, 0x10000);
451         memset(ram2, 0, 0x10000);
452         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
453 }
454
455
456 static double cyclesForSample = 0;
457 static void AppleTimer(uint16_t cycles)
458 {
459         // Handle PHI2 clocked stuff here...
460         MBRun(cycles);
461         floppyDrive[0].RunSequencer(cycles);
462
463 #if 1
464         // Handle sound
465         // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
466         // 16.688154500083 ms = 1 frame
467         cyclesForSample += (double)cycles;
468
469         if (cyclesForSample >= 21.26009)
470         {
471 #if 0
472 sampleClock = mainCPU.clock;
473 WriteLog("    cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
474 sampleCount++;
475 lastSampleClock = sampleClock;
476 #endif
477                 WriteSampleToBuffer();
478                 cyclesForSample -= 21.26009;
479         }
480 #endif
481 }
482
483
484 #ifdef CPU_CLOCK_CHECKING
485 uint8_t counter = 0;
486 uint32_t totalCPU = 0;
487 uint64_t lastClock = 0;
488 #endif
489 //
490 // Main loop
491 //
492 int main(int /*argc*/, char * /*argv*/[])
493 {
494         InitLog("./apple2.log");
495         LoadSettings();
496         srand(time(NULL));                      // Initialize RNG
497
498 #if 0
499 // Make some timing/address tables
500
501         for(uint32_t line=0; line<262; line++)
502         {
503                 WriteLog("LINE %03i: ", line);
504
505                 for(uint32_t col=0; col<65; col++)
506                 {
507                         // Convert these to H/V counters
508                         uint32_t hcount = col - 1;
509
510                         // HC sees zero twice:
511                         if (hcount == 0xFFFFFFFF)
512                                 hcount = 0;
513
514                         uint32_t vcount = line + 0xFA;
515
516                         // Now do the address calculations
517                         uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
518                                 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
519                         uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
520
521                         // Add in particulars for the gfx mode we're in...
522                         if (false)
523                                 // non hires
524                                 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
525                                         | (!store80Mode && displayPage2 ? 0x800 : 0);
526                         else
527                                 // hires
528                                 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
529                                         | (!store80Mode && displayPage2 ? 0x4000 : 0)
530                                         | ((vcount & 0x07) << 10);
531
532                         WriteLog("$%04X ", address);
533                 }
534
535                 WriteLog("\n");
536         }
537
538 #endif
539
540         // Zero out memory
541         memset(ram, 0, 0x10000);
542         memset(rom, 0, 0x10000);
543         memset(ram2, 0, 0x10000);
544
545         // Set up MMU
546         SetupAddressMap();
547         ResetMMUPointers();
548
549         // Install devices in slots
550         InstallFloppy(SLOT6);
551         InstallMockingboard(SLOT4);
552         InstallHardDrive(SLOT7);
553
554         // Set up V65C02 execution context
555         memset(&mainCPU, 0, sizeof(V65C02REGS));
556         mainCPU.RdMem = AppleReadMem;
557         mainCPU.WrMem = AppleWriteMem;
558         mainCPU.Timer = AppleTimer;
559         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
560
561 #if 0
562         if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
563         {
564                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
565                 return -1;
566         }
567 #else
568         memcpy(rom + 0xC000, apple2eEnhROM, 0x4000);
569 #endif
570
571         WriteLog("About to initialize video...\n");
572
573         if (!InitVideo())
574         {
575                 printf("Could not init screen: aborting!\n");
576                 return -1;
577         }
578
579         GUI::Init(sdlRenderer);
580
581         WriteLog("About to initialize audio...\n");
582         SoundInit();
583
584         if (settings.autoStateSaving)
585         {
586                 // Load last state from file...
587                 if (!LoadApple2State(settings.autoStatePath))
588                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
589         }
590
591 /*
592 So, how to run this then?  Right now, we have two separate threads, one for the CPU and one for audio.  The screen refresh is tied to the CPU thread.
593
594 To do this properly, however, we need to execute approximately 1,020,484.32 cycles per second, and we need to tie the AY/regular sound to this rate.  Video should happen still approximately 60 times a second, even though the real thing has a frame rate of 59.92 Hz.
595
596 Right now, the speed of the CPU is tied to the host system's refresh rate (which seems to be somewhere around 59.9 Hz).  Under this regime, the sound thread is starved much of the time, which means there's a timing mismatch somewhere between the sound thread and the CPU thread and the video (main) thread.
597
598 Other considerations: Even though we know the exact amount of cycles for one frame (17030 cycles to be exact), because the video frame rate is slightly under 60 (~59.92) the amount of time those cycles take can't be tied to the host system refresh rate, as they won't be the same (it will have about 8,000 or so more cycles in one second than it should have using 60 frames per second as the base frequency).  However, we do know that the system clock is 14.318180 MHz, and we do know that 1 out of every 65 cycles will take 2 extra ticks of the system clock (cycles are normally 14 ticks of the system clock).  So, by virtue of this, we know how long one frame is in seconds exactly (which would be (((65 * 14) + 2) * 262) / 14318180 = 16.688154500083 milliseconds).
599
600 So we need to decouple the CPU thread from the host video thread, and have the CPU frame run at its rate so that it will complete its running in its alloted time.  We also need to have a little bit of cushion for the sound thread, so that its buffer doesn't starve.  Assuming we get the timing correct, it will pull ahead and fall behind and all average out in the end.
601
602
603 */
604
605         running = true;
606         InitializeEventList();
607         // Set frame to fire at 1/60 s interval
608         SetCallbackTime(FrameCallback, 16666.66666667);
609         // Set up blinking at 1/4 s intervals
610 //      SetCallbackTime(BlinkTimer, 250000);
611         startTicks = SDL_GetTicks();
612
613 #ifdef THREADED_65C02
614         // Kick off the CPU...
615         cpuCond = SDL_CreateCond();
616         mainSem = SDL_CreateSemaphore(1);
617         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
618 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
619 //      SDL_sem * mainMutex = SDL_CreateMutex();
620 #endif
621
622         WriteLog("Entering main loop...\n");
623
624         while (running)
625         {
626 #ifdef CPU_CLOCK_CHECKING
627                 double timeToNextEvent = GetTimeToNextEvent();
628 #endif
629 #ifndef THREADED_65C02
630                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
631
632         #ifdef CPU_CLOCK_CHECKING
633 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
634         #endif
635 #endif
636                 HandleNextEvent();
637         }
638
639 #ifdef THREADED_65C02
640 WriteLog("Main: cpuFinished = true;\n");
641         cpuFinished = true;
642
643 WriteLog("Main: SDL_SemWait(mainSem);\n");
644         // Only do this if NOT in power off/emulation paused mode!
645         if (!pauseMode)
646                 // Should lock until CPU thread is waiting...
647                 SDL_SemWait(mainSem);
648 #endif
649
650 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
651         SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
652 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
653         SDL_WaitThread(cpuThread, NULL);
654 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
655         SDL_DestroyCond(cpuCond);
656         SDL_DestroySemaphore(mainSem);
657
658         // Autosave state here, if requested...
659         if (settings.autoStateSaving)
660                 SaveApple2State(settings.autoStatePath);
661
662         floppyDrive[0].SaveImage(0);
663         floppyDrive[0].SaveImage(1);
664
665 #if 0
666 #include "dis65c02.h"
667 static char disbuf[80];
668 uint16_t pc=0x801;
669 while (pc < 0x9FF)
670 {
671         pc += Decode65C02(&mainCPU, disbuf, pc);
672         WriteLog("%s\n", disbuf);
673 }
674 #endif
675
676         SoundDone();
677         VideoDone();
678         SaveSettings();
679         LogDone();
680
681         return 0;
682 }
683
684
685 //
686 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
687 // +CTRL+SHIFT (3). Order of keys is:
688 // Delete, left, tab, down, up, return, right, escape
689 // Space, single quote, comma, minus, period, slash
690 // Numbers 0-9
691 // Semicolon, equals, left bracket, backslash, right bracket, backquote
692 // Letters a-z (lowercase)
693 //
694 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
695 //       keyboards, so this table should suffice for the shifted keys just
696 //       fine.
697 //
698 uint8_t apple2e_keycode[4][56] = {
699         {       // Normal
700                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
701                 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
702                 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
703                 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
704                 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
705                 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
706                 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
707         },
708         {       // With CTRL held
709                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
710                 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
711                 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
712                 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
713                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
714                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
715                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
716         },
717         {       // With Shift held
718                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
719                 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
720                 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
721                 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
722                 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
723                 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
724                 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
725         },
726         {       // With CTRL+Shift held
727                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
728                 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
729                 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
730                 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
731                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
732                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
733                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
734         }
735 };
736
737 static uint32_t frameCount = 0;
738 static void FrameCallback(void)
739 {
740         SDL_Event event;
741         uint8_t keyIndex;
742
743         frameTimePtr = (frameTimePtr + 1) % 60;
744         frameTime[frameTimePtr] = startTicks;
745
746         while (SDL_PollEvent(&event))
747         {
748                 switch (event.type)
749                 {
750                 case SDL_KEYDOWN:
751                         // We do our own repeat handling thank you very much! :-)
752                         if (event.key.repeat != 0)
753                                 break;
754
755                         // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
756                         // method
757                         if ((event.key.keysym.mod & KMOD_CTRL)
758                                 && (event.key.keysym.mod & KMOD_SHIFT)
759                                 && (event.key.keysym.sym == SDLK_q))
760                         {
761                                 running = false;
762                                 // We return here, because we don't want to pick up any
763                                 // spurious keypresses with our exit sequence.
764                                 return;
765                         }
766
767                         // CTRL+RESET key emulation (mapped to CTRL+HOME)
768                         if ((event.key.keysym.mod & KMOD_CTRL)
769                                 && (event.key.keysym.sym == SDLK_HOME))
770                         {
771 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
772                                 resetKeyDown = true;
773                                 // Need to reset the MMU switches as well on RESET
774                                 intCXROM = false;
775                                 slotC3ROM = false;
776                                 intC8ROM = false;
777                                 break;
778                         }
779
780                         // There has GOT to be a better way off mapping SDLKs to our
781                         // keyindex. But for now, this should suffice.
782                         keyIndex = 0xFF;
783
784                         switch (event.key.keysym.sym)
785                         {
786                         case SDLK_BACKSPACE:    keyIndex =  0; break;
787                         case SDLK_LEFT:         keyIndex =  1; break;
788                         case SDLK_TAB:          keyIndex =  2; break;
789                         case SDLK_DOWN:         keyIndex =  3; break;
790                         case SDLK_UP:           keyIndex =  4; break;
791                         case SDLK_RETURN:       keyIndex =  5; break;
792                         case SDLK_RIGHT:        keyIndex =  6; break;
793                         case SDLK_ESCAPE:       keyIndex =  7; break;
794                         case SDLK_SPACE:        keyIndex =  8; break;
795                         case SDLK_QUOTE:        keyIndex =  9; break;
796                         case SDLK_COMMA:        keyIndex = 10; break;
797                         case SDLK_MINUS:        keyIndex = 11; break;
798                         case SDLK_PERIOD:       keyIndex = 12; break;
799                         case SDLK_SLASH:        keyIndex = 13; break;
800                         case SDLK_0:            keyIndex = 14; break;
801                         case SDLK_1:            keyIndex = 15; break;
802                         case SDLK_2:            keyIndex = 16; break;
803                         case SDLK_3:            keyIndex = 17; break;
804                         case SDLK_4:            keyIndex = 18; break;
805                         case SDLK_5:            keyIndex = 19; break;
806                         case SDLK_6:            keyIndex = 20; break;
807                         case SDLK_7:            keyIndex = 21; break;
808                         case SDLK_8:            keyIndex = 22; break;
809                         case SDLK_9:            keyIndex = 23; break;
810                         case SDLK_SEMICOLON:    keyIndex = 24; break;
811                         case SDLK_EQUALS:       keyIndex = 25; break;
812                         case SDLK_LEFTBRACKET:  keyIndex = 26; break;
813                         case SDLK_BACKSLASH:    keyIndex = 27; break;
814                         case SDLK_RIGHTBRACKET: keyIndex = 28; break;
815                         case SDLK_BACKQUOTE:    keyIndex = 29; break;
816                         case SDLK_a:            keyIndex = 30; break;
817                         case SDLK_b:            keyIndex = 31; break;
818                         case SDLK_c:            keyIndex = 32; break;
819                         case SDLK_d:            keyIndex = 33; break;
820                         case SDLK_e:            keyIndex = 34; break;
821                         case SDLK_f:            keyIndex = 35; break;
822                         case SDLK_g:            keyIndex = 36; break;
823                         case SDLK_h:            keyIndex = 37; break;
824                         case SDLK_i:            keyIndex = 38; break;
825                         case SDLK_j:            keyIndex = 39; break;
826                         case SDLK_k:            keyIndex = 40; break;
827                         case SDLK_l:            keyIndex = 41; break;
828                         case SDLK_m:            keyIndex = 42; break;
829                         case SDLK_n:            keyIndex = 43; break;
830                         case SDLK_o:            keyIndex = 44; break;
831                         case SDLK_p:            keyIndex = 45; break;
832                         case SDLK_q:            keyIndex = 46; break;
833                         case SDLK_r:            keyIndex = 47; break;
834                         case SDLK_s:            keyIndex = 48; break;
835                         case SDLK_t:            keyIndex = 49; break;
836                         case SDLK_u:            keyIndex = 50; break;
837                         case SDLK_v:            keyIndex = 51; break;
838                         case SDLK_w:            keyIndex = 52; break;
839                         case SDLK_x:            keyIndex = 53; break;
840                         case SDLK_y:            keyIndex = 54; break;
841                         case SDLK_z:            keyIndex = 55; break;
842                         }
843
844                         // Stuff the key in if we have a valid one...
845                         if (keyIndex != 0xFF)
846                         {
847                                 // Handle Shift, CTRL, & Shift+CTRL combos
848                                 uint8_t table = 0;
849
850                                 if (event.key.keysym.mod & KMOD_CTRL)
851                                         table |= 1;
852
853                                 if (event.key.keysym.mod & KMOD_SHIFT)
854                                         table |= 2;
855
856                                 lastKeyPressed = apple2e_keycode[table][keyIndex];
857                                 keyDown = true;
858
859                                 // Handle Caps Lock
860                                 if ((SDL_GetModState() & KMOD_CAPS)
861                                         && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
862                                         lastKeyPressed -= 0x20;
863
864                                 // Handle key repeat if the key hasn't been held
865                                 keyDelay = 15;
866                                 keyDownCount++;
867
868                                 // Buffer the key held. Note that the last key is always
869                                 // stuffed into keysHeld[0].
870                                 if (keyDownCount >= 2)
871                                 {
872                                         keysHeld[1] = keysHeld[0];
873                                         keysHeldAppleCode[1] = keysHeldAppleCode[0];
874
875                                         if (keyDownCount > 2)
876                                                 keyDownCount = 2;
877                                 }
878
879                                 keysHeld[0] = event.key.keysym.sym;
880                                 keysHeldAppleCode[0] = lastKeyPressed;
881                                 break;
882                         }
883
884                         if (event.key.keysym.sym == SDLK_PAUSE)
885                         {
886                                 pauseMode = !pauseMode;
887
888                                 if (pauseMode)
889                                 {
890                                         SoundPause();
891                                         SpawnMessage("*** PAUSED ***");
892                                 }
893                                 else
894                                 {
895                                         SoundResume();
896                                         SpawnMessage("*** RESUME ***");
897                                 }
898                         }
899                         // Buttons 0 & 1
900                         else if (event.key.keysym.sym == SDLK_LALT)
901                                 openAppleDown = true;
902                         else if (event.key.keysym.sym == SDLK_RALT)
903                                 closedAppleDown = true;
904                         else if (event.key.keysym.sym == SDLK_F2)
905                                 TogglePalette();
906                         else if (event.key.keysym.sym == SDLK_F3)
907                                 CycleScreenTypes();
908                         else if (event.key.keysym.sym == SDLK_F4)
909                                 ToggleTickDisplay();
910                         else if (event.key.keysym.sym == SDLK_F5)
911                         {
912                                 VolumeDown();
913                                 char volStr[19] = "[****************]";
914
915                                 for(int i=GetVolume(); i<16; i++)
916                                         volStr[1 + i] = '-';
917
918                                 SpawnMessage("Volume: %s", volStr);
919                         }
920                         else if (event.key.keysym.sym == SDLK_F6)
921                         {
922                                 VolumeUp();
923                                 char volStr[19] = "[****************]";
924
925                                 for(int i=GetVolume(); i<16; i++)
926                                         volStr[1 + i] = '-';
927
928                                 SpawnMessage("Volume: %s", volStr);
929                         }
930                         else if (event.key.keysym.sym == SDLK_F7)
931                         {
932                                 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
933                                 // This attenuates by ~3 dB
934                                 VAY_3_8910::maxVolume /= 1.4142135f;
935                                 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
936                         }
937                         else if (event.key.keysym.sym == SDLK_F8)
938                         {
939                                 VAY_3_8910::maxVolume *= 1.4142135f;
940                                 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
941                         }
942 else if (event.key.keysym.sym == SDLK_F9)
943 {
944         floppyDrive[0].CreateBlankImage(1);
945 //      SpawnMessage("Image cleared...");
946 }//*/
947 /*else if (event.key.keysym.sym == SDLK_F10)
948 {
949         floppyDrive[0].SwapImages();
950 //      SpawnMessage("Image swapped...");
951 }//*/
952                         // Toggle the disassembly process
953                         else if (event.key.keysym.sym == SDLK_F11)
954                         {
955                                 dumpDis = !dumpDis;
956                                 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
957                         }
958                         else if (event.key.keysym.sym == SDLK_F12)
959                         {
960                                 if (!fullscreenDebounce)
961                                 {
962                                         ToggleFullScreen();
963                                         fullscreenDebounce = true;
964                                 }
965                         }
966
967                         break;
968
969                 case SDL_KEYUP:
970                         if (event.key.keysym.sym == SDLK_F12)
971                                 fullscreenDebounce = false;
972                         // Paddle buttons 0 & 1
973                         else if (event.key.keysym.sym == SDLK_LALT)
974                                 openAppleDown = false;
975                         else if (event.key.keysym.sym == SDLK_RALT)
976                                 closedAppleDown = false;
977                         else if ((event.key.keysym.mod & KMOD_CTRL)
978                                 && (event.key.keysym.sym == SDLK_HOME))
979                                 resetKeyDown = false;
980                         else
981                         {
982                                 // Handle key buffering 'key up' event (2 key rollover)
983                                 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
984                                 {
985                                         keyDownCount--;
986                                         keyDelay = 0;   // Reset key delay
987                                 }
988                                 else if (keyDownCount == 2)
989                                 {
990                                         if (event.key.keysym.sym == keysHeld[0])
991                                         {
992                                                 keyDownCount--;
993                                                 keysHeld[0] = keysHeld[1];
994                                                 keysHeldAppleCode[0] = keysHeldAppleCode[1];
995                                         }
996                                         else if (event.key.keysym.sym == keysHeld[1])
997                                         {
998                                                 keyDownCount--;
999                                         }
1000                                 }
1001                         }
1002
1003                         break;
1004
1005                 case SDL_MOUSEBUTTONDOWN:
1006                         GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1007                         break;
1008
1009                 case SDL_MOUSEBUTTONUP:
1010                         GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1011                         break;
1012
1013                 case SDL_MOUSEMOTION:
1014                         GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1015
1016                         // Handle mouse showing when the mouse is hidden...
1017                         if (hideMouseTimeout == -1)
1018                                 SDL_ShowCursor(1);
1019
1020                         hideMouseTimeout = 60;
1021                         break;
1022
1023                 case SDL_WINDOWEVENT:
1024                         if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1025                                 GUI::MouseMove(0, 0, 0);
1026
1027                         break;
1028
1029                 case SDL_QUIT:
1030                         running = false;
1031                 }
1032         }
1033
1034         // Hide the mouse if it's been 1s since the last time it was moved
1035         // N.B.: Should disable mouse hiding if it's over the GUI...
1036         if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1037                 hideMouseTimeout--;
1038         else if (hideMouseTimeout == 0)
1039         {
1040                 hideMouseTimeout--;
1041                 SDL_ShowCursor(0);
1042         }
1043
1044         // Stuff the Apple keyboard buffer, if any keys are pending
1045         // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1046         //       According to "Understanding the Apple IIe", the initial delay is
1047         //       between 32 & 48 jiffies and the repeat is every 4 jiffies.
1048         if (keyDownCount > 0)
1049         {
1050                 keyDelay--;
1051
1052                 if (keyDelay == 0)
1053                 {
1054                         keyDelay = 3;
1055                         lastKeyPressed = keysHeldAppleCode[0];
1056                         keyDown = true;
1057                 }
1058         }
1059
1060         // Handle power request from the GUI
1061         if (powerStateChangeRequested)
1062         {
1063                 if (GUI::powerOnState)
1064                 {
1065                         pauseMode = false;
1066                         // Unlock the CPU thread...
1067                         SDL_SemPost(mainSem);
1068                 }
1069                 else
1070                 {
1071                         pauseMode = true;
1072                         // Should lock until CPU thread is waiting...
1073                         SDL_SemWait(mainSem);
1074                         ResetApple2State();
1075                 }
1076
1077                 powerStateChangeRequested = false;
1078         }
1079
1080         blinkTimer = (blinkTimer + 1) & 0x1F;
1081
1082         if (blinkTimer == 0)
1083                 flash = !flash;
1084
1085         // Render the Apple screen + GUI overlay
1086         RenderAppleScreen(sdlRenderer);
1087         GUI::Render(sdlRenderer);
1088         SDL_RenderPresent(sdlRenderer);
1089         SetCallbackTime(FrameCallback, 16666.66666667);
1090
1091 #ifdef CPU_CLOCK_CHECKING
1092 //We know it's stopped, so we can get away with this...
1093 counter++;
1094 if (counter == 60)
1095 {
1096         uint64_t clock = GetCurrentV65C02Clock();
1097 //totalCPU += (uint32_t)(clock - lastClock);
1098
1099         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1100         lastClock = clock;
1101 //      totalCPU = 0;
1102         counter = 0;
1103 }
1104 #endif
1105
1106 // This is the problem: If you set the interval to 16, it runs faster than
1107 // 1/60s per frame.  If you set it to 17, it runs slower.  What we need is to
1108 // have it do 16 for one frame, then 17 for two others.  Then it should average
1109 // out to 1/60s per frame every 3 frames.  [And now it does!]
1110 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1111 // to jitter all over the place...
1112         frameCount = (frameCount + 1) % 3;
1113 //      uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1114         // Get number of ticks burned in this frame, for displaying elsewhere
1115 #if 0
1116         frameTicks = SDL_GetTicks() - startTicks;
1117 #else
1118         frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1119 #endif
1120
1121         // Wait for next frame...
1122 //      while (SDL_GetTicks() - startTicks < waitFrameTime)
1123 //              SDL_Delay(1);
1124
1125 #if 0
1126         startTicks = SDL_GetTicks();
1127 #else
1128         startTicks = SDL_GetPerformanceCounter();
1129 #endif
1130 #if 0
1131         uint64_t cpuCycles = GetCurrentV65C02Clock();
1132         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1133         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1134         lastCPUCycles = cpuCycles
1135 #endif
1136
1137 //let's wait, then signal...
1138 //works longer, but then still falls behind... [FIXED, see above]
1139 #ifdef THREADED_65C02
1140         if (!pauseMode)
1141                 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1142 #endif
1143 }
1144
1145
1146 static void BlinkTimer(void)
1147 {
1148         // Set up blinking at 1/4 sec intervals
1149         flash = !flash;
1150         SetCallbackTime(BlinkTimer, 250000);
1151 }
1152
1153
1154 /*
1155 Next problem is this: How to have events occur and synchronize with the rest
1156 of the threads?
1157
1158   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1159     remainder CPU cycles over...)
1160
1161 One way would be to use a fractional accumulator, then subtract 1 every
1162 time it overflows. Like so:
1163
1164 double overflow = 0;
1165 uint32_t time = 20;
1166 while (!done)
1167 {
1168         Execute6808(&soundCPU, time);
1169         overflow += 0.289115646;
1170         if (overflow > 1.0)
1171         {
1172                 overflow -= 1.0;
1173                 time = 21;
1174         }
1175         else
1176                 time = 20;
1177 }
1178 */
1179