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