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