robmcmullen-apple2/src/apple2.cpp

1199 lines
29 KiB
C++
Raw Normal View History

2007-05-29 03:06:33 +00:00
//
// Apple 2 SDL Portable Apple Emulator
//
// by James L. Hammons
// (C) 2005 Underground Software
//
// Loosely based on AppleWin by Tom Charlesworth which was based on AppleWin by
// Oliver Schmidt which was based on AppleWin by Michael O'Brien. :-) Parts are
// also derived from ApplePC. Too bad it was closed source--it could have been
// *the* premier Apple II emulator out there.
//
// JLH = James L. Hammons <jlhamm@acm.org>
//
// WHO WHEN WHAT
// --- ---------- ------------------------------------------------------------
// JLH 11/12/2005 Initial port to SDL
// JLH 11/18/2005 Wired up graphic soft switches
2007-05-29 03:06:33 +00:00
// JLH 12/02/2005 Setup timer subsystem for more accurate time keeping
// JLH 12/12/2005 Added preliminary state saving support
//
// STILL TO DO:
//
// - Port to SDL [DONE]
// - GUI goodies
// - Weed out unneeded functions [DONE]
// - Disk I/O [DONE]
// - 128K IIe related stuff
// - State loading/saving
//
#include "apple2.h"
#include <SDL.h>
#include <fstream>
#include <string>
#include <iomanip>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "log.h"
#include "video.h"
#include "sound.h"
#include "settings.h"
#include "v65c02.h"
#include "applevideo.h"
#include "timing.h"
#include "floppy.h"
#include "firmware.h"
#include "gui/gui.h"
#include "gui/window.h"
#include "gui/draggablewindow2.h"
2007-05-29 03:06:33 +00:00
#include "gui/textedit.h"
// Debug and misc. defines
#define THREADED_65C02
//#define CPU_THREAD_OVERFLOW_COMPENSATION
//#define DEBUG_LC
#define CPU_CLOCK_CHECKING
2007-05-29 03:06:33 +00:00
// Global variables
uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces
uint8 ram2[0x10000];
2007-05-29 03:06:33 +00:00
uint8 diskRom[0x100]; // Disk ROM space
V65C02REGS mainCPU; // v65C02 execution context
2007-05-29 03:06:33 +00:00
uint8 appleType = APPLE_TYPE_II;
FloppyDrive floppyDrive;
2007-05-29 03:06:33 +00:00
// Local variables
static uint8 lastKeyPressed = 0;
static bool keyDown = false;
//static FloppyDrive floppyDrive;
2007-05-29 03:06:33 +00:00
enum { LC_BANK_1, LC_BANK_2 };
static uint8 visibleBank = LC_BANK_1;
static bool readRAM = false;
static bool writeRAM = false;
static bool running = true; // Machine running state flag...
static uint32 startTicks;
static GUI * gui = NULL;
// Local functions (technically, they're global...)
bool LoadImg(char * filename, uint8 * ram, int size);
uint8 RdMem(uint16 addr);
void WrMem(uint16 addr, uint8 b);
static void SaveApple2State(const char * filename);
static bool LoadApple2State(const char * filename);
// Local timer callback functions
static void FrameCallback(void);
static void BlinkTimer(void);
#ifdef THREADED_65C02
// Test of threaded execution of 6502
static SDL_Thread * cpuThread = NULL;
//static SDL_mutex * cpuMutex = NULL;
static SDL_cond * cpuCond = NULL;
static bool cpuFinished = false;
static bool cpuSleep = false;
// Let's try a thread...
/*
Here's how it works: Execute 1 frame's worth, then sleep.
Other stuff wakes it up
*/
int CPUThreadFunc(void * data)
{
// Mutex must be locked for conditional to work...
// Also, must be created in the thread that uses it...
SDL_mutex * cpuMutex = SDL_CreateMutex();
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
float overflow = 0.0;
#endif
do
{
if (cpuSleep)
SDL_CondWait(cpuCond, cpuMutex);
uint32 cycles = 17066;
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
// ODD! It's closer *without* this overflow compensation. ??? WHY ???
overflow += 0.666666667;
if (overflow > 1.0)
{
overflow -= 1.0;
cycles++;
}
#endif
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!
SDL_mutexP(cpuMutex);
// Adjust the sound routine's last cycle toggled time base
// Also, since we're finished executing, .clock is now valid
AdjustLastToggleCycles(mainCPU.clock);
#if 0
if (SDL_CondWait(cpuCond, cpuMutex) != 0)
{
printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
exit(-1);
}
#else
SDL_CondWait(cpuCond, cpuMutex);
#endif
SDL_mutexV(cpuMutex);
}
while (!cpuFinished);
return 0;
}
#endif
2007-05-29 03:06:33 +00:00
// Test GUI function
Element * TestWindow(void)
{
Element * win = new DraggableWindow2(10, 10, 128, 128);
2007-05-29 03:06:33 +00:00
// ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
return win;
}
Element * QuitEmulator(void)
{
gui->Stop();
running = false;
return NULL;
}
/*
Small Apple II memory map:
$C010 - Clear bit 7 of keyboard data ($C000)
$C030 - Toggle speaker diaphragm
$C051 - Display text
$C054 - Select page 1
$C056 - Select lo-res
$C058 - Set annuciator-0 output to 0
$C05A - Set annuciator-0 output to 0
$C05D - Set annuciator-0 output to 1
$C05F - Set annuciator-0 output to 1
$C0E0 - Disk control stepper ($C0E0-7)
$C0E9 - Disk control motor (on)
$C0EA - Disk enable (drive 1)
$C0EC - Disk R/W
$C0EE - Disk set read mode
*/
//
// V65C02 read/write memory functions
//
uint8 RdMem(uint16 addr)
{
uint8 b;
#if 0
if (addr >= 0xC000 && addr <= 0xC0FF)
WriteLog("\n*** Read at I/O address %04X\n", addr);
#endif
#if 0
if (addr >= 0xC080 && addr <= 0xC08F)
WriteLog("\n*** Read at I/O address %04X\n", addr);
#endif
if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
2007-05-29 03:06:33 +00:00
{
return lastKeyPressed | (keyDown ? 0x80 : 0x00);
}
else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
2007-05-29 03:06:33 +00:00
{
//This is bogus: keyDown is set to false, so return val NEVER is set...
//Fixed...
//Also, this is IIe/IIc only...!
uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
keyDown = false;
return retVal;
}
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
2007-05-29 03:06:33 +00:00
{
/*
This is problematic, mainly because the v65C02 removes actual cycles run after each call.
Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
How to fix?
What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
hasn't had any changes in it then we just fill it with the last sample value and are done. Then
we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
How to keep track?
Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
*/
2007-05-29 03:06:33 +00:00
ToggleSpeaker(GetCurrentV65C02Clock());
//should it return something else here???
return 0x00;
}
else if (addr == 0xC050) // Read $C050
2007-05-29 03:06:33 +00:00
{
textMode = false;
}
else if (addr == 0xC051) // Read $C051
2007-05-29 03:06:33 +00:00
{
textMode = true;
}
else if (addr == 0xC052) // Read $C052
2007-05-29 03:06:33 +00:00
{
mixedMode = false;
}
else if (addr == 0xC053) // Read $C053
2007-05-29 03:06:33 +00:00
{
mixedMode = true;
}
else if (addr == 0xC054) // Read $C054
2007-05-29 03:06:33 +00:00
{
displayPage2 = false;
}
else if (addr == 0xC055) // Read $C055
2007-05-29 03:06:33 +00:00
{
displayPage2 = true;
}
else if (addr == 0xC056) // Read $C056
2007-05-29 03:06:33 +00:00
{
hiRes = false;
}
else if (addr == 0xC057) // Read $C057
2007-05-29 03:06:33 +00:00
{
hiRes = true;
}
//Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
//memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
//[SHOULD BE FIXED NOW]
//OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
//visible, two makes it R/W.
/*
301 LDA $E000
304 PHA
305 LDA $C081
308 PLA
309 PHA
30A CMP $E000
30D BNE $332
30F LDA $C083
312 LDA $C083
315 LDA #$A5
317 STA $D000
31A CMP $D000
31D BNE $332
31F LSR A
320 STA $D000
323 CMP $D000
326 BNE $332
328 LDA $C081
32B LDA $C081
32E LDA #$01
330 BNE $334
332 LDA #$00
334 STA $300
337 PLA
338 CMP $E000
33B BEQ $340
33D LDA $C080
340 RTS
A = PEEK($C082)
*/
2007-05-29 03:06:33 +00:00
else if ((addr & 0xFFFB) == 0xC080)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
#endif
2007-05-29 03:06:33 +00:00
//$C080 49280 OECG R Read RAM bank 2; no write
visibleBank = LC_BANK_2;
readRAM = true;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC081)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
#endif
2007-05-29 03:06:33 +00:00
//$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
visibleBank = LC_BANK_2;
readRAM = false;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC082)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
#endif
2007-05-29 03:06:33 +00:00
//$C082 49282 OECG R Read ROM; no write
visibleBank = LC_BANK_2;
readRAM = false;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC083)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
#endif
2007-05-29 03:06:33 +00:00
//$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
visibleBank = LC_BANK_2;
readRAM = true;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC088)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
#endif
2007-05-29 03:06:33 +00:00
//$C088 49288 OECG R Read RAM bank 1; no write
visibleBank = LC_BANK_1;
readRAM = true;
writeRAM = false;
//Hm. Some stuff seems to want this.
//nope, was looking at $C0E8... return 0xFF;
2007-05-29 03:06:33 +00:00
}
else if ((addr & 0xFFFB) == 0xC089)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
#endif
2007-05-29 03:06:33 +00:00
//$C089 49289 OECG RR Read ROM; write RAM bank 1
visibleBank = LC_BANK_1;
readRAM = false;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC08A)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
#endif
2007-05-29 03:06:33 +00:00
//$C08A 49290 OECG R Read ROM; no write
visibleBank = LC_BANK_1;
readRAM = false;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC08B)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
#endif
2007-05-29 03:06:33 +00:00
//$C08B 49291 OECG RR Read/write RAM bank 1
visibleBank = LC_BANK_1;
readRAM = true;
writeRAM = true;
}
else if ((addr & 0xFFF8) == 0xC0E0)
{
floppyDrive.ControlStepper(addr & 0x07);
}
else if ((addr & 0xFFFE) == 0xC0E8)
{
floppyDrive.ControlMotor(addr & 0x01);
}
else if ((addr & 0xFFFE) == 0xC0EA)
{
floppyDrive.DriveEnable(addr & 0x01);
}
else if (addr == 0xC0EC)
{
return floppyDrive.ReadWrite();
//Hm, some stuff is looking at the return value. Dunno what it *should* be...
// OK, it's from the ReadWrite routine...
//return 0xFF;
2007-05-29 03:06:33 +00:00
}
else if (addr == 0xC0ED)
{
return floppyDrive.GetLatchValue();
}
else if (addr == 0xC0EE)
{
floppyDrive.SetReadMode();
}
else if (addr == 0xC0EF)
{
floppyDrive.SetWriteMode();
}
//#define LC_DEBUGGING
#ifdef LC_DEBUGGING
bool showpath = false;
if (addr >= 0xD000 && addr <= 0xD00F)
showpath = true;
#endif
2007-05-29 03:06:33 +00:00
//This sux...
if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
#ifdef LC_DEBUGGING
{
#endif
2007-05-29 03:06:33 +00:00
b = rom[addr];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from $C100-$CFFF block...\n");
}
#endif
2007-05-29 03:06:33 +00:00
else if (addr >= 0xD000)
{
if (readRAM)
{
if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
#ifdef LC_DEBUGGING
{
#endif
2007-05-29 03:06:33 +00:00
b = ram[addr - 0x1000];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
}
#endif
2007-05-29 03:06:33 +00:00
else
#ifdef LC_DEBUGGING
{
#endif
2007-05-29 03:06:33 +00:00
b = ram[addr];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC bank #2 (ram[addr])...\n");
}
#endif
2007-05-29 03:06:33 +00:00
}
else
#ifdef LC_DEBUGGING
{
#endif
2007-05-29 03:06:33 +00:00
b = rom[addr];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC ROM (rom[addr])...\n");
}
#endif
2007-05-29 03:06:33 +00:00
}
else
#ifdef LC_DEBUGGING
{
#endif
2007-05-29 03:06:33 +00:00
b = ram[addr];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from ram[addr]...\n");
}
#endif
#ifdef LC_DEBUGGING
if (addr >= 0xD000 && addr <= 0xD00F)
{
WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
}
#endif
2007-05-29 03:06:33 +00:00
return b;
}
/*
A-9 (Mockingboard)
APPENDIX F Assembly Language Program Listings
1 *PRIMARY ROUTINES
2 *FOR SLOT 4
3 *
4 ORG $9000
5 * ;ADDRESSES FOR FIRST 6522
6 ORB EQU $C400 ;PORT B
7 ORA EQU $C401 ;PORT A
8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
10 * ;ADDRESSES FOR SECOND 6522
11 ORB2 EQU $C480 ;PORT B
12 ORA2 EQU $C481 ;PORT A
13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
2007-05-29 03:06:33 +00:00
*/
void WrMem(uint16 addr, uint8 b)
{
//temp...
//extern V6809REGS regs;
//if (addr >= 0xC800 && addr <= 0xCBFE)
//if (addr == 0xC80F || addr == 0xC80D)
// WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
#if 0
if (addr >= 0xC000 && addr <= 0xC0FF)
WriteLog("\n*** Write at I/O address %04X\n", addr);
#endif
/*
Check the BIKO version on Asimov to see if it's been cracked or not...
7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
[7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
; INX here *ensures* 1 - 6!!! BUG!!!
; Or is it? Could this be part of a braindead copy protection scheme? It's
; awfully close to NOP ($EA)...
; Nothing else touches it once it's been written... Hmm...
7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
; 4E13: 03 00
; 4E15: 25 25 15 15 10 20
; 4E1B: 03 41 99 99 01 00 12
; 4E22: 99 70
7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
; NO checking is done on the raised stat! Aarrrgggghhhhh!
7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
; Print "ALAKAZAM!" and so on...
7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
*/
#if 0
if (addr == 0x7F47)
WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
#endif
/*
I think this is IIc/IIe only...
2007-05-29 03:06:33 +00:00
CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
CLRAUXRD = $C002 ;read from main 48K (WR-only)
SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
CLRAUXWR = $C004 ;write to main 48K (WR-only)
SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
CLRCXROM = $C006 ;use ROM on cards (WR-only)
SETCXROM = $C007 ;use internal ROM (WR-only)
CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
CLR80VID = $C00C ;disable 80-column display mode (WR-only)
SET80VID = $C00D ;enable 80-column display mode (WR-only)
CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
*/
if (addr == 0xC00E)
{
alternateCharset = false;
}
else if (addr == 0xC00F)
{
alternateCharset = true;
}
else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
{
//Actually, according to the A2 ref, this should do nothing since a write
//is immediately preceded by a read leaving it in the same state it was...
2009-01-30 15:17:27 +00:00
//But leaving this out seems to fuck up the key handling of some games...
keyDown = false;
2007-05-29 03:06:33 +00:00
}
else if (addr == 0xC050)
{
textMode = false;
}
else if (addr == 0xC051)
{
textMode = true;
}
else if (addr == 0xC052)
{
mixedMode = false;
}
else if (addr == 0xC053)
{
mixedMode = true;
}
else if (addr == 0xC054)
{
displayPage2 = false;
}
else if (addr == 0xC055)
{
displayPage2 = true;
}
else if (addr == 0xC056)
{
hiRes = false;
}
else if (addr == 0xC057)
{
hiRes = true;
}
else if ((addr & 0xFFFB) == 0xC080)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
#endif
//$C080 49280 OECG R Read RAM bank 2; no write
visibleBank = LC_BANK_2;
readRAM = true;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC081)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
#endif
//$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
visibleBank = LC_BANK_2;
readRAM = false;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC082)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
#endif
//$C082 49282 OECG R Read ROM; no write
visibleBank = LC_BANK_2;
readRAM = false;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC083)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
#endif
//$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
visibleBank = LC_BANK_2;
readRAM = true;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC088)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
#endif
//$C088 49288 OECG R Read RAM bank 1; no write
visibleBank = LC_BANK_1;
readRAM = true;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC089)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
#endif
//$C089 49289 OECG RR Read ROM; write RAM bank 1
visibleBank = LC_BANK_1;
readRAM = false;
writeRAM = true;
}
else if ((addr & 0xFFFB) == 0xC08A)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
#endif
//$C08A 49290 OECG R Read ROM; no write
visibleBank = LC_BANK_1;
readRAM = false;
writeRAM = false;
}
else if ((addr & 0xFFFB) == 0xC08B)
{
#ifdef DEBUG_LC
WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
#endif
//$C08B 49291 OECG RR Read/write RAM bank 1
visibleBank = LC_BANK_1;
readRAM = true;
writeRAM = true;
}
//This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
2007-05-29 03:06:33 +00:00
else if ((addr & 0xFFF8) == 0xC0E0)
{
floppyDrive.ControlStepper(addr & 0x07);
}
else if ((addr & 0xFFFE) == 0xC0E8)
{
floppyDrive.ControlMotor(addr & 0x01);
}
else if ((addr & 0xFFFE) == 0xC0EA)
{
floppyDrive.DriveEnable(addr & 0x01);
}
else if (addr == 0xC0EC)
{
//change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
//or DoIO
2007-05-29 03:06:33 +00:00
floppyDrive.ReadWrite();
}
else if (addr == 0xC0ED)
{
floppyDrive.SetLatchValue(b);
}
else if (addr == 0xC0EE)
{
floppyDrive.SetReadMode();
}
else if (addr == 0xC0EF)
{
floppyDrive.SetWriteMode();
}
//Still need to add missing I/O switches here...
//DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
#if 0
if (addr >= 0xD000 && addr <= 0xD00F)
{
WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
}
#endif
if (addr >= 0xC000 && addr <= 0xCFFF)
return; // Protect LC bank #1 from being written to!
2007-05-29 03:06:33 +00:00
if (addr >= 0xD000)
{
if (writeRAM)
{
if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
ram[addr - 0x1000] = b;
else
ram[addr] = b;
}
return;
}
ram[addr] = b;
}
//
// Load a file into RAM/ROM image space
//
bool LoadImg(char * filename, uint8 * ram, int size)
{
FILE * fp = fopen(filename, "rb");
if (fp == NULL)
return false;
fread(ram, 1, size, fp);
fclose(fp);
return true;
}
static void SaveApple2State(const char * filename)
{
}
static bool LoadApple2State(const char * filename)
{
return false;
}
#ifdef CPU_CLOCK_CHECKING
uint8 counter = 0;
uint32 totalCPU = 0;
uint64 lastClock = 0;
#endif
2007-05-29 03:06:33 +00:00
//
// Main loop
//
int main(int /*argc*/, char * /*argv*/[])
{
InitLog("./apple2.log");
LoadSettings();
srand(time(NULL)); // Initialize RNG
// Zero out memory
//Need to bankify this stuff for the IIe emulation...
memset(ram, 0, 0x10000);
memset(rom, 0, 0x10000);
memset(ram2, 0, 0x10000);
2007-05-29 03:06:33 +00:00
// Set up V65C02 execution context
memset(&mainCPU, 0, sizeof(V65C02REGS));
mainCPU.RdMem = RdMem;
mainCPU.WrMem = WrMem;
mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
{
WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
return -1;
}
//This is now included...
/* if (!LoadImg(settings.diskPath, diskRom, 0x100))
{
WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
// return -1;
}//*/
//Load up disk image from config file (for now)...
floppyDrive.LoadImage(settings.diskImagePath1, 0);
2009-01-30 15:17:27 +00:00
floppyDrive.LoadImage(settings.diskImagePath2, 1);
2007-05-29 03:06:33 +00:00
// floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
//Kill the DOS ROM in slot 6 for now...
//not
memcpy(rom + 0xC600, diskROM, 0x100);
WriteLog("About to initialize video...\n");
if (!InitVideo())
{
std::cout << "Aborting!" << std::endl;
2007-05-29 03:06:33 +00:00
return -1;
}
// Have to do this *after* video init but *before* sound init...!
//Shouldn't be necessary since we're not doing emulation in the ISR...
if (settings.autoStateSaving)
{
// Load last state from file...
if (!LoadApple2State(settings.autoStatePath))
WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
}
#if 0
// State loading!
if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
{
cout << "Couldn't load state file!" << endl;
cout << "Aborting!!" << endl;
return -1;
}
//A P Y X S PC
//-- -- -- -- ----- -----
//00 75 3B 53 FD 01 41 44
mainCPU.cpuFlags = 0;
mainCPU.a = 0x00;
mainCPU.x = 0x53;
mainCPU.y = 0x3B;
mainCPU.cc = 0x75;
mainCPU.sp = 0xFD;
mainCPU.pc = 0x4441;
textMode = false;
mixedMode = false;
displayPage2 = false;
hiRes = true;
//kludge...
readHiRam = true;
//dumpDis=true;
//kludge II...
memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
#endif
WriteLog("About to initialize audio...\n");
SoundInit();
SDL_EnableUNICODE(1); // Needed to do key translation shit
2009-01-30 15:17:27 +00:00
// gui = new GUI(surface); // Set up the GUI system object...
gui = new GUI(mainSurface); // Set up the GUI system object...
2009-01-30 18:20:05 +00:00
#if 0
2007-05-29 03:06:33 +00:00
gui->AddMenuTitle("Apple2");
gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
gui->AddMenuItem("");
gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
gui->CommitItemsToMenu();
2009-01-30 18:20:05 +00:00
#endif
2007-05-29 03:06:33 +00:00
SetupBlurTable(); // Set up the color TV emulation blur table
running = true; // Set running status...
InitializeEventList(); // Clear the event list before we use it...
SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
startTicks = SDL_GetTicks();
#ifdef THREADED_65C02
cpuCond = SDL_CreateCond();
cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
#endif
2007-05-29 03:06:33 +00:00
WriteLog("Entering main loop...\n");
while (running)
{
double timeToNextEvent = GetTimeToNextEvent();
#ifndef THREADED_65C02
2007-05-29 03:06:33 +00:00
Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
#endif
2007-05-29 03:06:33 +00:00
//We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
//(Fix so that this is not a requirement!)
//Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
// mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
#ifdef CPU_CLOCK_CHECKING
#ifndef THREADED_65C02
totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
#endif
#endif
// Handle CPU time delta for sound...
//Don't need this anymore now that we use absolute time...
// AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
2007-05-29 03:06:33 +00:00
HandleNextEvent();
}
#ifdef THREADED_65C02
cpuFinished = true;
SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
SDL_WaitThread(cpuThread, NULL);
//nowok:SDL_WaitThread(CPUThreadFunc, NULL);
SDL_DestroyCond(cpuCond);
#endif
2007-05-29 03:06:33 +00:00
if (settings.autoStateSaving)
{
// Save state here...
SaveApple2State(settings.autoStatePath);
}
floppyDrive.SaveImage();
SoundDone();
VideoDone();
SaveSettings();
LogDone();
return 0;
}
/*
Apple II keycodes
-----------------
Key Aln CTL SHF BTH
-----------------------
space $A0 $A0 $A0 $A0 No xlation
RETURN $8D $8D $8D $8D No xlation
0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1! $B1 $B1 $A1 $A1 No xlation
2" $B2 $B2 $A2 $A2 No xlation
3# $B3 $B3 $A3 $A3 No xlation
4$ $B4 $B4 $A4 $A4 No xlation
5% $B5 $B5 $A5 $A5 No xlation
6& $B6 $B6 $A6 $A6 No xlation
7' $B7 $B7 $A7 $A7 No xlation
8( $B8 $B8 $A8 $A8 No xlation
9) $B9 $B9 $A9 $A9 No xlation
:* $BA $BA $AA $AA No xlation
;+ $BB $BB $AB $AB No xlation
,< $AC $AC $BC $BC No xlation
-= $AD $AD $BD $BD No xlation
.> $AE $AE $BE $BE No xlation
/? $AF $AF $BF $BF No xlation
A $C1 $81 $C1 $81
B $C2 $82 $C2 $82
C $C3 $83 $C3 $83
D $C4 $84 $C4 $84
E $C5 $85 $C5 $85
F $C6 $86 $C6 $86
G $C7 $87 $C7 $87
H $C8 $88 $C8 $88
I $C9 $89 $C9 $89
J $CA $8A $CA $8A
K $CB $8B $CB $8B
L $CC $8C $CC $8C
M $CD $8D $DD $9D -> ODD
N^ $CE $8E $DE $9E -> ODD
O $CF $8F $CF $8F
P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
Q $D1 $91 $D1 $91
R $D2 $92 $D2 $92
S $D3 $93 $D3 $93
T $D4 $94 $D4 $94
U $D5 $95 $D5 $95
V $D6 $96 $D6 $96
W $D7 $97 $D7 $97
X $D8 $98 $D8 $98
Y $D9 $99 $D9 $99
Z $DA $9A $DA $9A
<- $88 $88 $88 $88
-> $95 $95 $95 $95
ESC $9B $9B $9B $9B No xlation
*/
static void FrameCallback(void)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_KEYDOWN:
if (event.key.keysym.unicode != 0)
{
//Need to do some key translation here, and screen out non-apple keys as well...
if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
break;
lastKeyPressed = event.key.keysym.unicode;
keyDown = true;
//kludge: should have a caps lock thingy here...
//or all uppercase for ][+...
if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
lastKeyPressed &= 0xDF; // Convert to upper case...
}
// CTRL+RESET key emulation (mapped to CTRL+`)
// This doesn't work...
// if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
// if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
//NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
//ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
if (event.key.keysym.sym == SDLK_RIGHT)
lastKeyPressed = 0x15, keyDown = true;
else if (event.key.keysym.sym == SDLK_LEFT)
lastKeyPressed = 0x08, keyDown = true;
// Use ALT+Q to exit, as well as the usual window decoration method
if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
running = false;
if (event.key.keysym.sym == SDLK_F12)
dumpDis = !dumpDis; // Toggle the disassembly process
// else if (event.key.keysym.sym == SDLK_F11)
// floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
2007-05-29 03:06:33 +00:00
else if (event.key.keysym.sym == SDLK_F9)
{
floppyDrive.CreateBlankImage();
// SpawnMessage("Image cleared...");
}//*/
else if (event.key.keysym.sym == SDLK_F10)
{
floppyDrive.SwapImages();
// SpawnMessage("Image swapped...");
}//*/
if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
TogglePalette();
else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
CycleScreenTypes();
// if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
if (event.key.keysym.sym == SDLK_F1) // GUI launch key
//NOTE: Should parse the output to determine whether or not the user requested
// to quit completely... !!! FIX !!!
gui->Run();
if (event.key.keysym.sym == SDLK_F5)
{
VolumeDown();
char volStr[10] = "*********";
volStr[GetVolume()] = 0;
SpawnMessage("Volume: %s", volStr);
}
else if (event.key.keysym.sym == SDLK_F6)
{
VolumeUp();
char volStr[10] = "*********";
volStr[GetVolume()] = 0;
SpawnMessage("Volume: %s", volStr);
}
2007-05-29 03:06:33 +00:00
break;
case SDL_QUIT:
running = false;
}
}
RenderVideoFrame();
SetCallbackTime(FrameCallback, 16666.66666667);
#ifdef CPU_CLOCK_CHECKING
//We know it's stopped, so we can get away with this...
uint64 clock = GetCurrentV65C02Clock();
totalCPU += (uint32)(clock - lastClock);
lastClock = clock;
counter++;
if (counter == 60)
{
printf("Executed %u cycles...\n", totalCPU);
totalCPU = 0;
counter = 0;
}
#endif
#ifdef THREADED_65C02
SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
#endif
//Instead of this, we should yield remaining time to other processes... !!! FIX !!!
//lessee...
//nope.
//Actually, slows things down too much...
//SDL_Delay(10);
2007-05-29 03:06:33 +00:00
while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
startTicks = SDL_GetTicks();
}
static void BlinkTimer(void)
{
flash = !flash;
SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
}
/*
Next problem is this: How to have events occur and synchronize with the rest
of the threads?
o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
remainder CPU cycles over...)
One way would be to use a fractional accumulator, then subtract 1 every
time it overflows. Like so:
double overflow = 0;
uint32 time = 20;
while (!done)
{
Execute6808(&soundCPU, time);
overflow += 0.289115646;
if (overflow > 1.0)
{
overflow -= 1.0;
time = 21;
}
else
time = 20;
}
*/