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