]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Fixed problem with frames running ahead of the sound thread.
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James L. Hammons
5 // (C) 2005 Underground Software
6 //
7 // Loosely based on AppleWin by Tom Charlesworth which was based on AppleWin by
8 // Oliver Schmidt which was based on AppleWin by Michael O'Brien. :-) Parts are
9 // also derived from ApplePC. Too bad it was closed source--it could have been
10 // *the* premier Apple II emulator out there.
11 //
12 // JLH = James L. Hammons <jlhamm@acm.org>
13 //
14 // WHO  WHEN        WHAT
15 // ---  ----------  ------------------------------------------------------------
16 // JLH  11/12/2005  Initial port to SDL
17 // JLH  11/18/2005  Wired up graphic soft switches
18 // JLH  12/02/2005  Setup timer subsystem for more accurate time keeping
19 // JLH  12/12/2005  Added preliminary state saving support
20 //
21
22 // STILL TO DO:
23 //
24 // - Port to SDL [DONE]
25 // - GUI goodies
26 // - Weed out unneeded functions [DONE]
27 // - Disk I/O [DONE]
28 // - 128K IIe related stuff
29 // - State loading/saving
30 //
31
32 #include "apple2.h"
33
34 #include <SDL2/SDL.h>
35 #include <fstream>
36 #include <string>
37 #include <iomanip>
38 #include <iostream>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <time.h>
42 #include "log.h"
43 #include "video.h"
44 #include "sound.h"
45 #include "settings.h"
46 #include "v65c02.h"
47 #include "applevideo.h"
48 #include "timing.h"
49 #include "floppy.h"
50 #include "firmware.h"
51
52 #include "gui/gui.h"
53 #include "gui/window.h"
54 #include "gui/draggablewindow2.h"
55 #include "gui/textedit.h"
56
57 // Debug and misc. defines
58
59 #define THREADED_65C02
60 #define CPU_THREAD_OVERFLOW_COMPENSATION
61 //#define DEBUG_LC
62 //#define CPU_CLOCK_CHECKING
63 #define THREAD_DEBUGGING
64
65 // Global variables
66
67 uint8_t ram[0x10000], rom[0x10000];                             // RAM & ROM spaces
68 uint8_t ram2[0x10000];
69 uint8_t diskRom[0x100];                                                 // Disk ROM space
70 V65C02REGS mainCPU;                                                             // v65C02 execution context
71 uint8_t appleType = APPLE_TYPE_II;
72 FloppyDrive floppyDrive;
73
74 // Local variables
75
76 static uint8_t lastKeyPressed = 0;
77 static bool keyDown = false;
78 static bool openAppleDown = false;
79 static bool closedAppleDown = false;
80
81 //static FloppyDrive floppyDrive;
82
83 enum { LC_BANK_1, LC_BANK_2 };
84
85 static uint8_t visibleBank = LC_BANK_1;
86 static bool readRAM = false;
87 static bool writeRAM = false;
88
89 static bool running = true;                                             // Machine running state flag...
90 static uint32_t startTicks;
91
92 static GUI * gui = NULL;
93
94 // Local functions (technically, they're global...)
95
96 bool LoadImg(char * filename, uint8_t * ram, int size);
97 uint8_t RdMem(uint16_t addr);
98 void WrMem(uint16_t addr, uint8_t b);
99 static void SaveApple2State(const char * filename);
100 static bool LoadApple2State(const char * filename);
101
102 // Local timer callback functions
103
104 static void FrameCallback(void);
105 static void BlinkTimer(void);
106
107 #ifdef THREADED_65C02
108 // Test of threaded execution of 6502
109 static SDL_Thread * cpuThread = NULL;
110 //static SDL_mutex * cpuMutex = NULL;
111 static SDL_cond * cpuCond = NULL;
112 static SDL_sem * mainSem = NULL;
113 static bool cpuFinished = false;
114 static bool cpuSleep = false;
115
116
117 // Let's try a thread...
118 /*
119 Here's how it works: Execute 1 frame's worth, then sleep.
120 Other stuff wakes it up
121 */
122 int CPUThreadFunc(void * data)
123 {
124         // Mutex must be locked for conditional to work...
125         // Also, must be created in the thread that uses it...
126         SDL_mutex * cpuMutex = SDL_CreateMutex();
127
128 // decrement mainSem...
129 //SDL_SemWait(mainSem);
130 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
131         float overflow = 0.0;
132 #endif
133
134         do
135         {
136                 if (cpuSleep)
137                         SDL_CondWait(cpuCond, cpuMutex);
138
139 // decrement mainSem...
140 #ifdef THREAD_DEBUGGING
141 WriteLog("CPU: SDL_SemWait(mainSem);\n");
142 #endif
143 SDL_SemWait(mainSem);
144
145 // There are exactly 800 slices of 21.333 cycles per frame, so it works out
146 // evenly.
147 #if 0
148                 uint32_t cycles = 17066;
149 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
150 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
151                 overflow += 0.666666667;
152
153                 if (overflow > 1.0)
154                 {
155                         overflow -= 1.0;
156                         cycles++;
157                 }
158 #endif
159
160 #ifdef THREAD_DEBUGGING
161 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
162 #endif
163                 Execute65C02(&mainCPU, cycles); // how much? 1 frame (after 1 s, off by 40 cycles) not any more--it's off by as much as 240 now!
164
165                 // Adjust the sound routine's last cycle toggled time base
166                 // Also, since we're finished executing, .clock is now valid
167 #ifdef THREAD_DEBUGGING
168 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
169 #endif
170                 AdjustLastToggleCycles(mainCPU.clock);
171 #else
172 #ifdef THREAD_DEBUGGING
173 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
174 #endif
175                 for(int i=0; i<800; i++)
176                 {
177                         uint32_t cycles = 21;
178                         overflow += 0.333333334;
179
180                         if (overflow > 1.0)
181                         {
182                                 cycles++;
183                                 overflow -= 1.0;
184                         }
185
186                         Execute65C02(&mainCPU, cycles);
187                         WriteSampleToBuffer();
188                 }
189 #endif
190
191 WriteLog("CPUThread: Supposedly end of frame...\n");
192
193 #ifdef THREAD_DEBUGGING
194 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
195 #endif
196                 SDL_mutexP(cpuMutex);
197 #if 0
198                 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
199                 {
200                         printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
201                         exit(-1);
202                 }
203 #else
204 // increment mainSem...
205 #ifdef THREAD_DEBUGGING
206 WriteLog("CPU: SDL_SemPost(mainSem);\n");
207 #endif
208 SDL_SemPost(mainSem);
209 //              SDL_CondSignal(mainCond);       // In case something is waiting on us...
210
211 #ifdef THREAD_DEBUGGING
212 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
213 #endif
214                 SDL_CondWait(cpuCond, cpuMutex);
215
216 #endif
217 #ifdef THREAD_DEBUGGING
218 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
219 #endif
220                 SDL_mutexV(cpuMutex);
221         }
222         while (!cpuFinished);
223
224         SDL_DestroyMutex(cpuMutex);
225
226         return 0;
227 }
228 #endif
229
230
231 // Test GUI function
232
233 Element * TestWindow(void)
234 {
235         Element * win = new DraggableWindow2(10, 10, 128, 128);
236 //      ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
237
238         return win;
239 }
240
241
242 Element * QuitEmulator(void)
243 {
244         gui->Stop();
245         running = false;
246
247         return NULL;
248 }
249
250
251 /*
252  Small Apple II memory map:
253
254 $C010 - Clear bit 7 of keyboard data ($C000)
255 $C030 - Toggle speaker diaphragm
256 $C051 - Display text
257 $C054 - Select page 1
258 $C056 - Select lo-res
259 $C058 - Set annuciator-0 output to 0
260 $C05A - Set annuciator-0 output to 0
261 $C05D - Set annuciator-0 output to 1
262 $C05F - Set annuciator-0 output to 1
263 $C0E0 - Disk control stepper ($C0E0-7)
264 $C0E9 - Disk control motor (on)
265 $C0EA - Disk enable (drive 1)
266 $C0EC - Disk R/W
267 $C0EE - Disk set read mode
268 */
269
270 //
271 // V65C02 read/write memory functions
272 //
273
274 uint8_t RdMem(uint16_t addr)
275 {
276         uint8_t b;
277
278 #if 0
279 if (addr >= 0xC000 && addr <= 0xC0FF)
280         WriteLog("\n*** Read at I/O address %04X\n", addr);
281 #endif
282 #if 0
283 if (addr >= 0xC080 && addr <= 0xC08F)
284         WriteLog("\n*** Read at I/O address %04X\n", addr);
285 #endif
286
287         if ((addr & 0xFFF0) == 0xC000)                  // Read $C000-$C00F
288         {
289                 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
290         }
291         else if ((addr & 0xFFF0) == 0xC010)             // Read $C010-$C01F
292         {
293 //This is bogus: keyDown is set to false, so return val NEVER is set...
294 //Fixed...
295 //Also, this is IIe/IIc only...!
296                 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
297                 keyDown = false;
298                 return retVal;
299         }
300         else if ((addr & 0xFFF0) == 0xC030)             // Read $C030-$C03F
301         {
302 /*
303 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
304 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
305 How to fix?
306
307 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
308 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
309 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
310 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
311 How to keep track?
312
313 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
314 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
315
316
317
318 */
319                 ToggleSpeaker(GetCurrentV65C02Clock());
320 //should it return something else here???
321                 return 0x00;
322         }
323         else if (addr == 0xC050)                                // Read $C050
324         {
325                 textMode = false;
326         }
327         else if (addr == 0xC051)                                // Read $C051
328         {
329                 textMode = true;
330         }
331         else if (addr == 0xC052)                                // Read $C052
332         {
333                 mixedMode = false;
334         }
335         else if (addr == 0xC053)                                // Read $C053
336         {
337                 mixedMode = true;
338         }
339         else if (addr == 0xC054)                                // Read $C054
340         {
341                 displayPage2 = false;
342         }
343         else if (addr == 0xC055)                                // Read $C055
344         {
345                 displayPage2 = true;
346         }
347         else if (addr == 0xC056)                                // Read $C056
348         {
349                 hiRes = false;
350         }
351         else if (addr == 0xC057)                                // Read $C057
352         {
353                 hiRes = true;
354         }
355         else if (addr == 0xC061)                                // Read $C061
356         {
357                 // Open Apple key (or push button 0)
358                 return (openAppleDown ? 0x80 : 0x00);
359         }
360         else if (addr == 0xC062)                                // Read $C062
361         {
362                 // Open Apple key (or push button 0)
363                 return (closedAppleDown ? 0x80 : 0x00);
364         }
365
366 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
367 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
368 //[SHOULD BE FIXED NOW]
369 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
370 //visible, two makes it R/W.
371
372 /*
373 301  LDA $E000
374 304  PHA
375 305  LDA $C081
376 308  PLA
377 309  PHA
378 30A  CMP $E000
379 30D  BNE $332
380 30F  LDA $C083
381 312  LDA $C083
382 315  LDA #$A5
383 317  STA $D000
384 31A  CMP $D000
385 31D  BNE $332
386 31F  LSR A
387 320  STA $D000
388 323  CMP $D000
389 326  BNE $332
390 328  LDA $C081
391 32B  LDA $C081
392 32E  LDA #$01
393 330  BNE $334
394 332  LDA #$00
395 334  STA $300
396 337  PLA
397 338  CMP $E000
398 33B  BEQ $340
399 33D  LDA $C080
400 340  RTS
401
402 A = PEEK($C082)
403 */
404
405         else if ((addr & 0xFFFB) == 0xC080)
406         {
407 #ifdef DEBUG_LC
408 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
409 #endif
410 //$C080 49280              OECG  R   Read RAM bank 2; no write
411                 visibleBank = LC_BANK_2;
412                 readRAM = true;
413                 writeRAM = false;
414         }
415         else if ((addr & 0xFFFB) == 0xC081)
416         {
417 #ifdef DEBUG_LC
418 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
419 #endif
420 //$C081 49281 ROMIN        OECG  RR  Read ROM; write RAM bank 2
421                 visibleBank = LC_BANK_2;
422                 readRAM = false;
423                 writeRAM = true;
424         }
425         else if ((addr & 0xFFFB) == 0xC082)
426         {
427 #ifdef DEBUG_LC
428 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
429 #endif
430 //$C082 49282              OECG  R   Read ROM; no write
431                 visibleBank = LC_BANK_2;
432                 readRAM = false;
433                 writeRAM = false;
434         }
435         else if ((addr & 0xFFFB) == 0xC083)
436         {
437 #ifdef DEBUG_LC
438 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
439 #endif
440 //$C083 49283 LCBANK2      OECG  RR  Read/write RAM bank 2
441                 visibleBank = LC_BANK_2;
442                 readRAM = true;
443                 writeRAM = true;
444         }
445         else if ((addr & 0xFFFB) == 0xC088)
446         {
447 #ifdef DEBUG_LC
448 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
449 #endif
450 //$C088 49288              OECG  R   Read RAM bank 1; no write
451                 visibleBank = LC_BANK_1;
452                 readRAM = true;
453                 writeRAM = false;
454 //Hm. Some stuff seems to want this.
455 //nope, was looking at $C0E8... return 0xFF;
456         }
457         else if ((addr & 0xFFFB) == 0xC089)
458         {
459 #ifdef DEBUG_LC
460 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
461 #endif
462 //$C089 49289              OECG  RR  Read ROM; write RAM bank 1
463                 visibleBank = LC_BANK_1;
464                 readRAM = false;
465                 writeRAM = true;
466         }
467         else if ((addr & 0xFFFB) == 0xC08A)
468         {
469 #ifdef DEBUG_LC
470 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
471 #endif
472 //$C08A 49290              OECG  R   Read ROM; no write
473                 visibleBank = LC_BANK_1;
474                 readRAM = false;
475                 writeRAM = false;
476         }
477         else if ((addr & 0xFFFB) == 0xC08B)
478         {
479 #ifdef DEBUG_LC
480 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
481 #endif
482 //$C08B 49291              OECG  RR  Read/write RAM bank 1
483                 visibleBank = LC_BANK_1;
484                 readRAM = true;
485                 writeRAM = true;
486         }
487         else if ((addr & 0xFFF8) == 0xC0E0)
488         {
489                 floppyDrive.ControlStepper(addr & 0x07);
490         }
491         else if ((addr & 0xFFFE) == 0xC0E8)
492         {
493                 floppyDrive.ControlMotor(addr & 0x01);
494         }
495         else if ((addr & 0xFFFE) == 0xC0EA)
496         {
497                 floppyDrive.DriveEnable(addr & 0x01);
498         }
499         else if (addr == 0xC0EC)
500         {
501                 return floppyDrive.ReadWrite();
502         }
503         else if (addr == 0xC0ED)
504         {
505                 return floppyDrive.GetLatchValue();
506         }
507         else if (addr == 0xC0EE)
508         {
509                 floppyDrive.SetReadMode();
510         }
511         else if (addr == 0xC0EF)
512         {
513                 floppyDrive.SetWriteMode();
514         }
515
516 //#define LC_DEBUGGING
517 #ifdef LC_DEBUGGING
518 bool showpath = false;
519 if (addr >= 0xD000 && addr <= 0xD00F)
520         showpath = true;
521 #endif
522 //This sux...
523         if (addr >= 0xC100 && addr <= 0xCFFF)   // The $C000-$CFFF block is *never* RAM
524 #ifdef LC_DEBUGGING
525         {
526 #endif
527                 b = rom[addr];
528 #ifdef LC_DEBUGGING
529 if (showpath)
530         WriteLog("b is from $C100-$CFFF block...\n");
531         }
532 #endif
533         else if (addr >= 0xD000)
534         {
535                 if (readRAM)
536                 {
537                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
538 #ifdef LC_DEBUGGING
539         {
540 #endif
541                                 b = ram[addr - 0x1000];
542 #ifdef LC_DEBUGGING
543 if (showpath)
544         WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
545         }
546 #endif
547                         else
548 #ifdef LC_DEBUGGING
549         {
550 #endif
551                                 b = ram[addr];
552 #ifdef LC_DEBUGGING
553 if (showpath)
554         WriteLog("b is from LC bank #2 (ram[addr])...\n");
555         }
556 #endif
557                 }
558                 else
559 #ifdef LC_DEBUGGING
560         {
561 #endif
562                         b = rom[addr];
563 #ifdef LC_DEBUGGING
564 if (showpath)
565         WriteLog("b is from LC ROM (rom[addr])...\n");
566         }
567 #endif
568         }
569         else
570 #ifdef LC_DEBUGGING
571         {
572 #endif
573                 b = ram[addr];
574 #ifdef LC_DEBUGGING
575 if (showpath)
576         WriteLog("b is from ram[addr]...\n");
577         }
578 #endif
579
580 #ifdef LC_DEBUGGING
581 if (addr >= 0xD000 && addr <= 0xD00F)
582 {
583         WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
584 }
585 #endif
586
587         return b;
588 }
589
590 /*
591 A-9 (Mockingboard)
592 APPENDIX F Assembly Language Program Listings
593
594         1       *PRIMARY ROUTINES
595         2       *FOR SLOT 4
596         3       *
597         4                       ORG     $9000
598         5       *                               ;ADDRESSES FOR FIRST 6522
599         6       ORB             EQU     $C400           ;PORT B
600         7       ORA             EQU     $C401           ;PORT A
601         8       DDRB            EQU     $C402           ;DATA DIRECTION REGISTER (A)
602         9       DDRA            EQU     $C403           ;DATA DIRECTION REGISTER (B)
603         10      *                                       ;ADDRESSES FOR SECOND 6522
604         11      ORB2            EQU     $C480           ;PORT B
605         12      ORA2            EQU     $C481           ;PORT A
606         13      DDRB2   EQU     $C482           ;DATA DIRECTION REGISTER (B)
607         14      DDRA2   EQU     $C483           ;DATA DIRECTION REGISTER (A)
608 */
609 void WrMem(uint16_t addr, uint8_t b)
610 {
611 //temp...
612 //extern V6809REGS regs;
613 //if (addr >= 0xC800 && addr <= 0xCBFE)
614 //if (addr == 0xC80F || addr == 0xC80D)
615 //      WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
616
617 #if 0
618 if (addr >= 0xC000 && addr <= 0xC0FF)
619         WriteLog("\n*** Write at I/O address %04X\n", addr);
620 #endif
621 /*
622 Check the BIKO version on Asimov to see if it's been cracked or not...
623
624 7F3D: 29 07          AND   #$07       [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
625 7F3F: C9 06          CMP   #$06       [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
626 7F41: 90 03          BCC   $7F46      [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
627 [7F43: 4C 83 7E      JMP   $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
628 7F46: AA             TAX              [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
629
630 ; INX here *ensures* 1 - 6!!! BUG!!!
631 ; Or is it? Could this be part of a braindead copy protection scheme? It's
632 ; awfully close to NOP ($EA)...
633 ; Nothing else touches it once it's been written... Hmm...
634
635 7F47: E8             INX              [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
636 7F48: F8             SED              [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
637 7F49: 18             CLC              [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
638 7F4A: BD 15 4E       LDA   $4E15,X    [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
639
640 ; 4E13: 03 00
641 ; 4E15: 25 25 15 15 10 20
642 ; 4E1B: 03 41 99 99 01 00 12
643 ; 4E22: 99 70
644
645 7F4D: 65 FC          ADC   $FC        [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
646 7F4F: 65 FC          ADC   $FC        [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
647 7F51: 65 FC          ADC   $FC        [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
648 7F53: 65 FC          ADC   $FC        [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
649
650 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
651
652 7F55: 9D 15 4E       STA   $4E15,X    [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
653 7F58: D8             CLD              [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
654
655 ; Print "ALAKAZAM!" and so on...
656
657 7F59: 20 2C 40       JSR   $402C      [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
658 */
659 #if 0
660 if (addr == 0x7F47)
661         WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
662 #endif
663 /*
664 I think this is IIc/IIe only...
665
666 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
667 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
668
669 CLRAUXRD = $C002 ;read from main 48K (WR-only)
670 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
671
672 CLRAUXWR = $C004 ;write to main 48K (WR-only)
673 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
674
675 CLRCXROM = $C006 ;use ROM on cards (WR-only)
676 SETCXROM = $C007 ;use internal ROM (WR-only)
677
678 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
679 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
680
681 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
682 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
683
684 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
685 SET80VID = $C00D ;enable 80-column display mode (WR-only)
686
687 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
688 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
689 */
690         if (addr == 0xC00E)
691         {
692                 alternateCharset = false;
693         }
694         else if (addr == 0xC00F)
695         {
696                 alternateCharset = true;
697         }
698         else if ((addr & 0xFFF0) == 0xC010)             // Keyboard strobe
699         {
700 //Actually, according to the A2 ref, this should do nothing since a write
701 //is immediately preceded by a read leaving it in the same state it was...
702 //But leaving this out seems to fuck up the key handling of some games...
703                 keyDown = false;
704         }
705         else if (addr == 0xC050)
706         {
707                 textMode = false;
708         }
709         else if (addr == 0xC051)
710         {
711                 textMode = true;
712         }
713         else if (addr == 0xC052)
714         {
715                 mixedMode = false;
716         }
717         else if (addr == 0xC053)
718         {
719                 mixedMode = true;
720         }
721         else if (addr == 0xC054)
722         {
723                 displayPage2 = false;
724         }
725         else if (addr == 0xC055)
726         {
727                 displayPage2 = true;
728         }
729         else if (addr == 0xC056)
730         {
731                 hiRes = false;
732         }
733         else if (addr == 0xC057)
734         {
735                 hiRes = true;
736         }
737         else if ((addr & 0xFFFB) == 0xC080)
738         {
739 #ifdef DEBUG_LC
740 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
741 #endif
742 //$C080 49280              OECG  R   Read RAM bank 2; no write
743                 visibleBank = LC_BANK_2;
744                 readRAM = true;
745                 writeRAM = false;
746         }
747         else if ((addr & 0xFFFB) == 0xC081)
748         {
749 #ifdef DEBUG_LC
750 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
751 #endif
752 //$C081 49281 ROMIN        OECG  RR  Read ROM; write RAM bank 2
753                 visibleBank = LC_BANK_2;
754                 readRAM = false;
755                 writeRAM = true;
756         }
757         else if ((addr & 0xFFFB) == 0xC082)
758         {
759 #ifdef DEBUG_LC
760 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
761 #endif
762 //$C082 49282              OECG  R   Read ROM; no write
763                 visibleBank = LC_BANK_2;
764                 readRAM = false;
765                 writeRAM = false;
766         }
767         else if ((addr & 0xFFFB) == 0xC083)
768         {
769 #ifdef DEBUG_LC
770 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
771 #endif
772 //$C083 49283 LCBANK2      OECG  RR  Read/write RAM bank 2
773                 visibleBank = LC_BANK_2;
774                 readRAM = true;
775                 writeRAM = true;
776         }
777         else if ((addr & 0xFFFB) == 0xC088)
778         {
779 #ifdef DEBUG_LC
780 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
781 #endif
782 //$C088 49288              OECG  R   Read RAM bank 1; no write
783                 visibleBank = LC_BANK_1;
784                 readRAM = true;
785                 writeRAM = false;
786         }
787         else if ((addr & 0xFFFB) == 0xC089)
788         {
789 #ifdef DEBUG_LC
790 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
791 #endif
792 //$C089 49289              OECG  RR  Read ROM; write RAM bank 1
793                 visibleBank = LC_BANK_1;
794                 readRAM = false;
795                 writeRAM = true;
796         }
797         else if ((addr & 0xFFFB) == 0xC08A)
798         {
799 #ifdef DEBUG_LC
800 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
801 #endif
802 //$C08A 49290              OECG  R   Read ROM; no write
803                 visibleBank = LC_BANK_1;
804                 readRAM = false;
805                 writeRAM = false;
806         }
807         else if ((addr & 0xFFFB) == 0xC08B)
808         {
809 #ifdef DEBUG_LC
810 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
811 #endif
812 //$C08B 49291              OECG  RR  Read/write RAM bank 1
813                 visibleBank = LC_BANK_1;
814                 readRAM = true;
815                 writeRAM = true;
816         }
817 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
818         else if ((addr & 0xFFF8) == 0xC0E0)
819         {
820                 floppyDrive.ControlStepper(addr & 0x07);
821         }
822         else if ((addr & 0xFFFE) == 0xC0E8)
823         {
824                 floppyDrive.ControlMotor(addr & 0x01);
825         }
826         else if ((addr & 0xFFFE) == 0xC0EA)
827         {
828                 floppyDrive.DriveEnable(addr & 0x01);
829         }
830         else if (addr == 0xC0EC)
831         {
832 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
833 //or DoIO
834                 floppyDrive.ReadWrite();
835         }
836         else if (addr == 0xC0ED)
837         {
838                 floppyDrive.SetLatchValue(b);
839         }
840         else if (addr == 0xC0EE)
841         {
842                 floppyDrive.SetReadMode();
843         }
844         else if (addr == 0xC0EF)
845         {
846                 floppyDrive.SetWriteMode();
847         }
848 //Still need to add missing I/O switches here...
849
850 //DEEE: BD 10 BF       LDA   $BF10,X    [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
851 #if 0
852 if (addr >= 0xD000 && addr <= 0xD00F)
853 {
854         WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
855 }
856 #endif
857         if (addr >= 0xC000 && addr <= 0xCFFF)
858                 return; // Protect LC bank #1 from being written to!
859
860         if (addr >= 0xD000)
861         {
862                 if (writeRAM)
863                 {
864                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
865                                 ram[addr - 0x1000] = b;
866                         else
867                                 ram[addr] = b;
868                 }
869
870                 return;
871         }
872
873         ram[addr] = b;
874 }
875
876
877 //
878 // Load a file into RAM/ROM image space
879 //
880 bool LoadImg(char * filename, uint8_t * ram, int size)
881 {
882         FILE * fp = fopen(filename, "rb");
883
884         if (fp == NULL)
885                 return false;
886
887         fread(ram, 1, size, fp);
888         fclose(fp);
889
890         return true;
891 }
892
893
894 static void SaveApple2State(const char * filename)
895 {
896 }
897
898
899 static bool LoadApple2State(const char * filename)
900 {
901         return false;
902 }
903
904
905 #ifdef CPU_CLOCK_CHECKING
906 uint8_t counter = 0;
907 uint32_t totalCPU = 0;
908 uint64_t lastClock = 0;
909 #endif
910 //
911 // Main loop
912 //
913 int main(int /*argc*/, char * /*argv*/[])
914 {
915         InitLog("./apple2.log");
916         LoadSettings();
917         srand(time(NULL));                                                                      // Initialize RNG
918
919         // Zero out memory
920 //Need to bankify this stuff for the IIe emulation...
921         memset(ram, 0, 0x10000);
922         memset(rom, 0, 0x10000);
923         memset(ram2, 0, 0x10000);
924
925         // Set up V65C02 execution context
926         memset(&mainCPU, 0, sizeof(V65C02REGS));
927         mainCPU.RdMem = RdMem;
928         mainCPU.WrMem = WrMem;
929         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
930
931         if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
932         {
933                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
934                 return -1;
935         }
936
937 //This is now included...
938 /*      if (!LoadImg(settings.diskPath, diskRom, 0x100))
939         {
940                 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
941 //              return -1;
942         }//*/
943
944 //Load up disk image from config file (for now)...
945         floppyDrive.LoadImage(settings.diskImagePath1, 0);
946         floppyDrive.LoadImage(settings.diskImagePath2, 1);
947 //      floppyDrive.LoadImage("./disks/temp.nib", 1);   // Load temp .nib file into second drive...
948
949 //Kill the DOS ROM in slot 6 for now...
950 //not
951         memcpy(rom + 0xC600, diskROM, 0x100);
952 //      memcpy(rom + 0xC700, diskROM, 0x100);   // Slot 7???
953
954         WriteLog("About to initialize video...\n");
955
956         if (!InitVideo())
957         {
958                 std::cout << "Aborting!" << std::endl;
959                 return -1;
960         }
961
962         // Have to do this *after* video init but *before* sound init...!
963 //Shouldn't be necessary since we're not doing emulation in the ISR...
964         if (settings.autoStateSaving)
965         {
966                 // Load last state from file...
967                 if (!LoadApple2State(settings.autoStatePath))
968                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
969         }
970
971
972 #if 0
973 // State loading!
974 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
975 {
976         cout << "Couldn't load state file!" << endl;
977         cout << "Aborting!!" << endl;
978         return -1;
979 }
980
981 //A  P  Y  X  S     PC
982 //-- -- -- -- ----- -----
983 //00 75 3B 53 FD 01 41 44
984
985 mainCPU.cpuFlags = 0;
986 mainCPU.a = 0x00;
987 mainCPU.x = 0x53;
988 mainCPU.y = 0x3B;
989 mainCPU.cc = 0x75;
990 mainCPU.sp = 0xFD;
991 mainCPU.pc = 0x4441;
992
993 textMode = false;
994 mixedMode = false;
995 displayPage2 = false;
996 hiRes = true;
997
998 //kludge...
999 readHiRam = true;
1000 //dumpDis=true;
1001 //kludge II...
1002 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1003 #endif
1004
1005         WriteLog("About to initialize audio...\n");
1006         SoundInit();
1007 //nope  SDL_EnableUNICODE(1);                                           // Needed to do key translation shit
1008
1009 //      gui = new GUI(surface);                                         // Set up the GUI system object...
1010 //      gui = new GUI(mainSurface);                                     // Set up the GUI system object...
1011 // SDL 2... this will likely cause Apple 2 to crash
1012 //      gui = new GUI(NULL);                                    // Set up the GUI system object...
1013 #if 0
1014         gui->AddMenuTitle("Apple2");
1015         gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1016         gui->AddMenuItem("");
1017         gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1018         gui->CommitItemsToMenu();
1019 #endif
1020
1021         SetupBlurTable();                                                       // Set up the color TV emulation blur table
1022         running = true;                                                         // Set running status...
1023
1024         InitializeEventList();                                          // Clear the event list before we use it...
1025         SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1026         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 s intervals
1027         startTicks = SDL_GetTicks();
1028
1029 #ifdef THREADED_65C02
1030         cpuCond = SDL_CreateCond();
1031         mainSem = SDL_CreateSemaphore(1);
1032         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1033 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1034 //      SDL_sem * mainMutex = SDL_CreateMutex();
1035 #endif
1036
1037         WriteLog("Entering main loop...\n");
1038         while (running)
1039         {
1040                 double timeToNextEvent = GetTimeToNextEvent();
1041 #ifndef THREADED_65C02
1042                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1043 #endif
1044 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1045 //(Fix so that this is not a requirement!)
1046 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1047 //              mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1048
1049 #ifdef CPU_CLOCK_CHECKING
1050 #ifndef THREADED_65C02
1051 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1052 #endif
1053 #endif
1054                 // Handle CPU time delta for sound...
1055 //Don't need this anymore now that we use absolute time...
1056 //              AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1057                 HandleNextEvent();
1058         }
1059
1060 #ifdef THREADED_65C02
1061 WriteLog("Main: cpuFinished = true;\n");
1062 cpuFinished = true;
1063 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1064 //What to do? How do you know when the CPU is sleeping???
1065 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1066 #if 0
1067 SDL_mutexP(mainMutex);
1068 SDL_CondWait(mainCond, mainMutex);      // Wait for CPU thread to get to signal point...
1069 SDL_mutexV(mainMutex);
1070 #else
1071 //Nope, use a semaphore...
1072 WriteLog("Main: SDL_SemWait(mainSem);\n");
1073 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1074 #endif
1075
1076 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1077 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1078 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1079 SDL_WaitThread(cpuThread, NULL);
1080 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1081 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1082 SDL_DestroyCond(cpuCond);
1083
1084 //SDL_DestroyMutex(mainMutex);
1085 SDL_DestroySemaphore(mainSem);
1086 #endif
1087
1088         if (settings.autoStateSaving)
1089         {
1090                 // Save state here...
1091                 SaveApple2State(settings.autoStatePath);
1092         }
1093 floppyDrive.SaveImage();
1094
1095         SoundDone();
1096         VideoDone();
1097         SaveSettings();
1098         LogDone();
1099
1100         return 0;
1101 }
1102
1103
1104 /*
1105 Apple II keycodes
1106 -----------------
1107
1108 Key     Aln CTL SHF BTH
1109 -----------------------
1110 space   $A0     $A0     $A0 $A0         No xlation
1111 RETURN  $8D     $8D     $8D     $8D             No xlation
1112 0               $B0     $B0     $B0     $B0             Need to screen shift+0 (?)
1113 1!              $B1 $B1 $A1 $A1         No xlation
1114 2"              $B2     $B2     $A2     $A2             No xlation
1115 3#              $B3     $B3     $A3     $A3             No xlation
1116 4$              $B4     $B4     $A4     $A4             No xlation
1117 5%              $B5     $B5     $A5     $A5             No xlation
1118 6&              $B6     $B6     $A6     $A6             No xlation
1119 7'              $B7     $B7     $A7     $A7             No xlation
1120 8(              $B8     $B8     $A8     $A8             No xlation
1121 9)              $B9     $B9     $A9     $A9             No xlation
1122 :*              $BA     $BA     $AA     $AA             No xlation
1123 ;+              $BB     $BB     $AB     $AB             No xlation
1124 ,<              $AC     $AC     $BC     $BC             No xlation
1125 -=              $AD     $AD     $BD     $BD             No xlation
1126 .>              $AE     $AE     $BE     $BE             No xlation
1127 /?              $AF     $AF     $BF     $BF             No xlation
1128 A               $C1     $81     $C1     $81
1129 B               $C2     $82     $C2     $82
1130 C               $C3     $83     $C3     $83
1131 D               $C4     $84     $C4     $84
1132 E               $C5     $85     $C5     $85
1133 F               $C6     $86     $C6     $86
1134 G               $C7     $87     $C7     $87
1135 H               $C8     $88     $C8     $88
1136 I               $C9     $89     $C9     $89
1137 J               $CA     $8A     $CA     $8A
1138 K               $CB     $8B     $CB     $8B
1139 L               $CC     $8C     $CC     $8C
1140 M               $CD     $8D     $DD     $9D             -> ODD
1141 N^              $CE     $8E     $DE     $9E             -> ODD
1142 O               $CF     $8F     $CF     $8F
1143 P@              $D0     $90     $C0     $80             Need to xlate CTL+SHFT+P & SHFT+P (?)
1144 Q               $D1     $91     $D1     $91
1145 R               $D2     $92     $D2     $92
1146 S               $D3     $93     $D3     $93
1147 T               $D4     $94     $D4     $94
1148 U               $D5     $95     $D5     $95
1149 V               $D6     $96     $D6     $96
1150 W               $D7     $97     $D7     $97
1151 X               $D8     $98     $D8     $98
1152 Y               $D9     $99     $D9     $99
1153 Z               $DA     $9A     $DA     $9A
1154 <-              $88     $88     $88     $88
1155 ->              $95     $95     $95     $95
1156 ESC             $9B     $9B     $9B     $9B             No xlation
1157
1158 */
1159 static uint64_t lastCPUCycles = 0;
1160 static uint32_t frameCount = 0;
1161 static void FrameCallback(void)
1162 {
1163         SDL_Event event;
1164
1165         while (SDL_PollEvent(&event))
1166         {
1167                 switch (event.type)
1168                 {
1169                 case SDL_TEXTINPUT:
1170 //Need to do some key translation here, and screen out non-apple keys as well...
1171 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1172 // everything else done separately. this is slightly easier. :-P)
1173 //                      if (event.key.keysym.sym == SDLK_TAB)   // Prelim key screening...
1174                         if (event.edit.text[0] == '\t') // Prelim key screening...
1175                                 break;
1176
1177                         lastKeyPressed = event.edit.text[0];
1178                         keyDown = true;
1179
1180                         //kludge: should have a caps lock thingy here...
1181                         //or all uppercase for ][+...
1182                         if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1183                                 lastKeyPressed &= 0xDF;         // Convert to upper case...
1184
1185                         break;
1186                 case SDL_KEYDOWN:
1187                         // CTRL+RESET key emulation (mapped to CTRL+`)
1188 // This doesn't work...
1189 //                      if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1190 //                      if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1191                         if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1192 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1193 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1194                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1195
1196                         if (event.key.keysym.sym == SDLK_RIGHT)
1197                                 lastKeyPressed = 0x15, keyDown = true;
1198                         else if (event.key.keysym.sym == SDLK_LEFT)
1199                                 lastKeyPressed = 0x08, keyDown = true;
1200                         else if (event.key.keysym.sym == SDLK_RETURN)
1201                                 lastKeyPressed = 0x0D, keyDown = true;
1202                         else if (event.key.keysym.sym == SDLK_ESCAPE)
1203                                 lastKeyPressed = 0x1B, keyDown = true;
1204
1205                         // Fix CTRL+key combo...
1206                         if (event.key.keysym.mod & KMOD_CTRL)
1207                         {
1208                                 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1209                                 {
1210                                         lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1211                                         keyDown = true;
1212 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1213                                 }
1214                         }
1215
1216                         // Use ALT+Q to exit, as well as the usual window decoration method
1217                         if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1218                                 running = false;
1219
1220                         // Paddle buttons 0 & 1
1221                         if (event.key.keysym.sym == SDLK_INSERT)
1222                                 openAppleDown = true;
1223                         if (event.key.keysym.sym == SDLK_PAGEUP)
1224                                 closedAppleDown = true;
1225
1226                         if (event.key.keysym.sym == SDLK_F11)
1227                                 dumpDis = !dumpDis;                             // Toggle the disassembly process
1228 //                      else if (event.key.keysym.sym == SDLK_F11)
1229 //                              floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1230 else if (event.key.keysym.sym == SDLK_F9)
1231 {
1232         floppyDrive.CreateBlankImage();
1233 //      SpawnMessage("Image cleared...");
1234 }//*/
1235 else if (event.key.keysym.sym == SDLK_F10)
1236 {
1237         floppyDrive.SwapImages();
1238 //      SpawnMessage("Image swapped...");
1239 }//*/
1240
1241                         if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1242                                 TogglePalette();
1243                         else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1244                                 CycleScreenTypes();
1245
1246 //                      if (event.key.keysym.sym == SDLK_F5)    // Temp GUI launch key
1247                         if (event.key.keysym.sym == SDLK_F1)    // GUI launch key
1248 //NOTE: Should parse the output to determine whether or not the user requested
1249 //      to quit completely... !!! FIX !!!
1250                                 gui->Run();
1251
1252                         if (event.key.keysym.sym == SDLK_F5)
1253                         {
1254                                 VolumeDown();
1255                                 char volStr[19] = "[****************]";
1256 //                              volStr[GetVolume()] = 0;
1257                                 for(int i=GetVolume(); i<16; i++)
1258                                         volStr[1 + i] = '-';
1259                                 SpawnMessage("Volume: %s", volStr);
1260                         }
1261                         else if (event.key.keysym.sym == SDLK_F6)
1262                         {
1263                                 VolumeUp();
1264                                 char volStr[19] = "[****************]";
1265 //                              volStr[GetVolume()] = 0;
1266                                 for(int i=GetVolume(); i<16; i++)
1267                                         volStr[1 + i] = '-';
1268                                 SpawnMessage("Volume: %s", volStr);
1269                         }
1270
1271                         static bool fullscreenDebounce = false;
1272
1273                         if (event.key.keysym.sym == SDLK_F12)
1274                         {
1275                                 if (!fullscreenDebounce)
1276                                 {
1277                                         ToggleFullScreen();
1278                                         fullscreenDebounce = true;
1279                                 }
1280                         }
1281 //                      else
1282
1283                         break;
1284                 case SDL_KEYUP:
1285                         if (event.key.keysym.sym == SDLK_F12)
1286                                 fullscreenDebounce = false;
1287
1288                         // Paddle buttons 0 & 1
1289                         if (event.key.keysym.sym == SDLK_INSERT)
1290                                 openAppleDown = false;
1291                         if (event.key.keysym.sym == SDLK_PAGEUP)
1292                                 closedAppleDown = false;
1293
1294 //                      if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1295 //                              keyDown = false;
1296
1297                         break;
1298                 case SDL_QUIT:
1299                         running = false;
1300                 }
1301         }
1302
1303 //#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1304         RenderVideoFrame();
1305         SetCallbackTime(FrameCallback, 16666.66666667);
1306
1307 #ifdef CPU_CLOCK_CHECKING
1308 //We know it's stopped, so we can get away with this...
1309 counter++;
1310 if (counter == 60)
1311 {
1312         uint64_t clock = GetCurrentV65C02Clock();
1313 //totalCPU += (uint32_t)(clock - lastClock);
1314
1315         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1316         lastClock = clock;
1317 //      totalCPU = 0;
1318         counter = 0;
1319 }
1320 #endif
1321 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1322 //lessee...
1323 //nope.
1324 //Actually, slows things down too much...
1325 //SDL_Delay(10);
1326 //      while (SDL_GetTicks() - startTicks < 16);       // Wait for next frame...
1327
1328 // This is the problem: If you set the interval to 16, it runs faster than
1329 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1330 // have it do 16 for one frame, then 17 for two others. Then it should average
1331 // out to 1/60s per frame every 3 frames.
1332         frameCount = (frameCount + 1) % 3;
1333
1334         uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1335
1336         while (SDL_GetTicks() - startTicks < waitFrameTime)
1337                 SDL_Delay(1);                                                   // Wait for next frame...
1338
1339         startTicks = SDL_GetTicks();
1340 #if 1
1341         uint64_t cpuCycles = GetCurrentV65C02Clock();
1342         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1343         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1344         lastCPUCycles = cpuCycles;
1345 #endif
1346
1347 //let's wait, then signal...
1348 //works longer, but then still falls behind...
1349 #ifdef THREADED_65C02
1350         SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1351 #endif
1352 }
1353
1354
1355 static void BlinkTimer(void)
1356 {
1357         flash = !flash;
1358         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 sec intervals
1359 }
1360
1361
1362 /*
1363 Next problem is this: How to have events occur and synchronize with the rest
1364 of the threads?
1365
1366   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1367     remainder CPU cycles over...)
1368
1369 One way would be to use a fractional accumulator, then subtract 1 every
1370 time it overflows. Like so:
1371
1372 double overflow = 0;
1373 uint32_t time = 20;
1374 while (!done)
1375 {
1376         Execute6808(&soundCPU, time);
1377         overflow += 0.289115646;
1378         if (overflow > 1.0)
1379         {
1380                 overflow -= 1.0;
1381                 time = 21;
1382         }
1383         else
1384                 time = 20;
1385 }
1386 */