robmcmullen-apple2/src/apple2.cpp

1851 lines
44 KiB
C++
Executable File

//
// 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
// 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 <SDL2/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"
#include "gui/textedit.h"
// Debug and misc. defines
#define THREADED_65C02
#define CPU_THREAD_OVERFLOW_COMPENSATION
#define DEBUG_LC
//#define CPU_CLOCK_CHECKING
//#define THREAD_DEBUGGING
#define SOFT_SWITCH_DEBUGGING
// Global variables
uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
uint8_t ram2[0x10000]; // Auxillary RAM
uint8_t diskRom[0x100]; // Disk ROM space
V65C02REGS mainCPU; // v65C02 execution context
uint8_t appleType = APPLE_TYPE_IIE;
FloppyDrive floppyDrive;
// Local variables
static uint8_t lastKeyPressed = 0;
static bool keyDown = false;
static bool openAppleDown = false;
static bool closedAppleDown = false;
static bool store80Mode = false;
static bool vbl = false;
static bool slotCXROM = false;
static bool slotC3ROM = false;
static bool ramrd = false;
static bool ramwrt = false;
static bool altzp = false;
static bool ioudis = true;
bool dhires = false;
//static FloppyDrive floppyDrive;
enum { LC_BANK_1, LC_BANK_2 };
static uint8_t visibleBank = LC_BANK_1;
static bool readRAM = false;
static bool writeRAM = false;
static bool running = true; // Machine running state flag...
static uint32_t startTicks;
static GUI * gui = NULL;
// Local functions (technically, they're global...)
bool LoadImg(char * filename, uint8_t * ram, int size);
uint8_t RdMem(uint16_t addr);
void WrMem(uint16_t addr, uint8_t 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 SDL_sem * mainSem = NULL;
static bool cpuFinished = false;
static bool cpuSleep = false;
// NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
// 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();
// decrement mainSem...
//SDL_SemWait(mainSem);
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
float overflow = 0.0;
#endif
do
{
if (cpuSleep)
SDL_CondWait(cpuCond, cpuMutex);
// decrement mainSem...
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_SemWait(mainSem);\n");
#endif
SDL_SemWait(mainSem);
// There are exactly 800 slices of 21.333 cycles per frame, so it works out
// evenly.
#if 0
uint32_t 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
#ifdef THREAD_DEBUGGING
WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
#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!
// Adjust the sound routine's last cycle toggled time base
// Also, since we're finished executing, .clock is now valid
#ifdef THREAD_DEBUGGING
WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
#endif
AdjustLastToggleCycles(mainCPU.clock);
#else
#ifdef THREAD_DEBUGGING
WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
#endif
for(int i=0; i<800; i++)
{
uint32_t cycles = 21;
overflow += 0.333333334;
if (overflow > 1.0)
{
cycles++;
overflow -= 1.0;
}
Execute65C02(&mainCPU, cycles);
WriteSampleToBuffer();
}
#endif
//WriteLog("CPUThread: Supposedly end of frame...\n");
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
#endif
SDL_mutexP(cpuMutex);
#if 0
if (SDL_CondWait(cpuCond, cpuMutex) != 0)
{
printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
exit(-1);
}
#else
// increment mainSem...
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_SemPost(mainSem);\n");
#endif
SDL_SemPost(mainSem);
// SDL_CondSignal(mainCond); // In case something is waiting on us...
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
#endif
SDL_CondWait(cpuCond, cpuMutex);
#endif
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
#endif
SDL_mutexV(cpuMutex);
}
while (!cpuFinished);
SDL_DestroyMutex(cpuMutex);
return 0;
}
#endif
// Test GUI function
Element * TestWindow(void)
{
Element * win = new DraggableWindow2(10, 10, 128, 128);
// ((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_t RdMem(uint16_t addr)
{
uint8_t 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
{
return lastKeyPressed | (keyDown ? 0x80 : 0x00);
}
// else if ((addr & 0xFFF8) == 0xC010) // Read $C010-$C01F
else if (addr == 0xC010)
{
//This is bogus: keyDown is set to false, so return val NEVER is set...
//Fixed...
//Also, this is IIe/IIc only...!
uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
keyDown = false;
return retVal;
}
// These are //e locations
else if (addr == 0xC011)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RDBANK2 (read)\n");
#endif
return (visibleBank == LC_BANK_2 ? 0x80 : 0x00);
}
else if (addr == 0xC012)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RDLCRAM (read)\n");
#endif
return (readRAM ? 0x80 : 0x00);
}
else if (addr == 0xC013)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMRD (read)\n");
#endif
return (ramrd ? 0x80 : 0x00);
}
else if (addr == 0xC014)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMWRT (read)\n");
#endif
return (ramwrt ? 0x80 : 0x00);
}
else if (addr == 0xC015)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTCXROM (read)\n");
#endif
return (slotCXROM ? 0x80 : 0x00);
}
else if (addr == 0xC016)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTZP (read)\n");
#endif
return (altzp ? 0x80 : 0x00);
}
else if (addr == 0xC017)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTC3ROM (read)\n");
#endif
return (slotC3ROM ? 0x80 : 0x00);
}
else if (addr == 0xC018)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80STORE (read)\n");
#endif
return (store80Mode ? 0x80 : 0x00);
}
else if (addr == 0xC019)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("VBL (read)\n");
#endif
return (vbl ? 0x80 : 0x00);
}
else if (addr == 0xC01A)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("TEXT (read)\n");
#endif
return (textMode ? 0x80 : 0x00);
}
else if (addr == 0xC01B)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("MIXED (read)\n");
#endif
return (mixedMode ? 0x80 : 0x00);
}
else if (addr == 0xC01C)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("PAGE2 (read)\n");
#endif
return (displayPage2 ? 0x80 : 0x00);
}
else if (addr == 0xC01D)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("HIRES (read)\n");
#endif
return (hiRes ? 0x80 : 0x00);
}
else if (addr == 0xC01E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTCHARSET (read)\n");
#endif
return (alternateCharset ? 0x80 : 0x00);
}
else if (addr == 0xC01F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80COL (read)\n");
#endif
return (col80Mode ? 0x80 : 0x00);
}
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
{
/*
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. (?)
*/
ToggleSpeaker(GetCurrentV65C02Clock());
//should it return something else here???
return 0x00;
}
else if (addr == 0xC050) // Read $C050
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("TEXT off (read)\n");
#endif
textMode = false;
}
else if (addr == 0xC051) // Read $C051
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("TEXT on (read)\n");
#endif
textMode = true;
}
else if (addr == 0xC052) // Read $C052
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("MIXED off (read)\n");
#endif
mixedMode = false;
}
else if (addr == 0xC053) // Read $C053
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("MIXED on (read)\n");
#endif
mixedMode = true;
}
else if (addr == 0xC054) // Read $C054
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("PAGE2 off (read)\n");
#endif
displayPage2 = false;
}
else if (addr == 0xC055) // Read $C055
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("PAGE2 on (read)\n");
#endif
displayPage2 = true;
}
else if (addr == 0xC056) // Read $C056
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("HIRES off (read)\n");
#endif
hiRes = false;
}
else if (addr == 0xC057) // Read $C057
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("HIRES on (read)\n");
#endif
hiRes = true;
}
else if (addr == 0xC05E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("DHIRES on (read)\n");
#endif
if (ioudis)
dhires = true;
}
else if (addr == 0xC05F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("DHIRES off (read)\n");
#endif
if (ioudis)
dhires = false;
}
else if (addr == 0xC061) // Read $C061
{
// Open Apple key (or push button 0)
return (openAppleDown ? 0x80 : 0x00);
}
else if (addr == 0xC062) // Read $C062
{
// Open Apple key (or push button 0)
return (closedAppleDown ? 0x80 : 0x00);
}
// The way the paddles work is that a strobe is written (or read) to $C070,
// then software counts down the time that it takes for the paddle outputs
// to have bit 7 return to 0. If there are no paddles connected, bit 7
// stays at 1.
else if (addr == 0xC064) // Paddles 0-3
{
return 0xFF;
}
else if (addr == 0xC065)
{
return 0xFF;
}
else if (addr == 0xC066)
{
return 0xFF;
}
else if (addr == 0xC067)
{
return 0xFF;
}
else if (addr == 0xC07E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("IOUDIS (read)\n");
#endif
return (ioudis ? 0x80 : 0x00);
}
else if (addr == 0xC07F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("DHIRES (read)\n");
#endif
return (dhires ? 0x80 : 0x00);
}
//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)
*/
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): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
#endif
//$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;
}
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;
}
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();
}
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
//This sux...
if (addr >= 0xC100 && addr <= 0xC7FF) // The $C000-$CFFF block is *never* RAM
{
// Looks like the ][e ref manual got this one wrong: slotCXROM set should mean
// use internal ROM, NOT slot ROM. :-/
// (fixed now, by setting the switch correctly in the write mem section :-P)
if (!slotCXROM)
// if (slotCXROM)
b = rom[addr];
else
{
if (addr >= 0xC100 && addr <= 0xC1FF)
b = parallelROM[addr & 0xFF];
else if (addr >= 0xC600 && addr <= 0xC6FF)
b = diskROM[addr & 0xFF];
else if (addr >= 0xC300 && addr <= 0xC3FF && !slotC3ROM)
b = rom[addr];
else
b = 0xFF;
// b = rom[addr];
}
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from $C100-$CFFF block...\n");
#endif
}
else if (addr >= 0xC800 && addr <= 0xCFFF) // 2K peripheral or OS ROM
{
b = rom[addr];
}
else if (addr >= 0xD000)
{
if (readRAM)
{
if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
#ifdef LC_DEBUGGING
{
#endif
b = ram[addr - 0x1000];
// b = (ramrd ? ram2[addr - 0x1000] : ram[addr - 0x1000]);
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
}
#endif
else
#ifdef LC_DEBUGGING
{
#endif
b = ram[addr];
// b = (ramrd ? ram2[addr] : ram[addr]);
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC bank #2 (ram[addr])...\n");
}
#endif
}
else
#ifdef LC_DEBUGGING
{
#endif
b = rom[addr];
#ifdef LC_DEBUGGING
if (showpath)
WriteLog("b is from LC ROM (rom[addr])...\n");
}
#endif
}
else
{
#if 1
// Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
{
if (displayPage2)
b = ram2[addr];
else
b = ram[addr];
return b;
}
// Finally, check for auxillary/altzp write switches
#endif
if (addr < 0x0200)
b = (altzp ? ram2[addr] : ram[addr]);
else
b = (ramrd ? ram2[addr] : ram[addr]);
// if (ramrd)
// b = ram2[addr];
// else
// {
// if (altzp)
// b = ram2[addr];
// else
// 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
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)
*/
void WrMem(uint16_t addr, uint8_t 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...
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 == 0xC000)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80STORE off (write)\n");
#endif
store80Mode = false;
}
else if (addr == 0xC001)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80STORE on (write)\n");
#endif
store80Mode = true;
}
else if (addr == 0xC002)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMRD off (write)\n");
#endif
ramrd = false;
}
else if (addr == 0xC003)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMRD on (write)\n");
#endif
ramrd = true;
}
else if (addr == 0xC004)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMWRT off (write)\n");
#endif
ramwrt = false;
}
else if (addr == 0xC005)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("RAMWRT on (write)\n");
#endif
ramwrt = true;
}
else if (addr == 0xC006)
{
// This is the only soft switch that breaks the usual convention.
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTCXROM on (write)\n");
#endif
slotCXROM = true;
}
else if (addr == 0xC007)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTCXROM off (write)\n");
#endif
slotCXROM = false;
}
else if (addr == 0xC008)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTZP off (write)\n");
#endif
altzp = false;
}
else if (addr == 0xC009)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTZP on (write)\n");
#endif
altzp = true;
}
else if (addr == 0xC00A)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTC3ROM off (write)\n");
#endif
slotC3ROM = false;
}
else if (addr == 0xC00B)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("SLOTC3ROM on (write)\n");
#endif
slotC3ROM = true;
}
else if (addr == 0xC00C)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80COL off (write)\n");
#endif
col80Mode = false;
}
else if (addr == 0xC00D)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("80COL on (write)\n");
#endif
col80Mode = true;
}
else if (addr == 0xC00E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTCHARSET off (write)\n");
#endif
alternateCharset = false;
}
else if (addr == 0xC00F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("ALTCHARSET on (write)\n");
#endif
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...
//But leaving this out seems to fuck up the key handling of some games...
keyDown = false;
}
else if (addr == 0xC050)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("TEXT off (write)\n");
#endif
textMode = false;
}
else if (addr == 0xC051)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("TEXT on (write)\n");
#endif
textMode = true;
}
else if (addr == 0xC052)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("MIXED off (write)\n");
#endif
mixedMode = false;
}
else if (addr == 0xC053)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("MIXED on (write)\n");
#endif
mixedMode = true;
}
else if (addr == 0xC054)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("PAGE2 off (write)\n");
#endif
displayPage2 = false;
}
else if (addr == 0xC055)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("PAGE2 on (write)\n");
#endif
displayPage2 = true;
}
else if (addr == 0xC056)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("HIRES off (write)\n");
#endif
hiRes = false;
}
else if (addr == 0xC057)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("HIRES on (write)\n");
#endif
hiRes = true;
}
else if (addr == 0xC05E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("DHIRES on (write)\n");
#endif
if (ioudis)
dhires = true;
//static int goDumpDis = 0;
//goDumpDis++;
//if (goDumpDis == 2)
// dumpDis = true;
}
else if (addr == 0xC05F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("DHIRES off (write)\n");
#endif
if (ioudis)
dhires = false;
}
else if (addr == 0xC07E)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("IOUDIS on (write)\n");
#endif
ioudis = true;
}
else if (addr == 0xC07F)
{
#ifdef SOFT_SWITCH_DEBUGGING
WriteLog("IOUDIS off (write)\n");
#endif
ioudis = false;
}
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 !!!
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
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!
if (addr >= 0xD000)
{
if (writeRAM)
{
#if 1
if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
ram[addr - 0x1000] = b;
else
ram[addr] = b;
#else
if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
{
if (ramwrt)
ram2[addr - 0x1000] = b;
else
ram[addr - 0x1000] = b;
}
else
{
if (ramwrt)
ram2[addr] = b;
else
ram[addr] = b;
}
#endif
}
return;
}
// Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
{
if (displayPage2)
ram2[addr] = b;
else
ram[addr] = b;
return;
}
// Finally, check for auxillary/altzp write switches
#if 0
if (ramwrt)
ram2[addr] = b;
else
{
if (altzp)
ram2[addr] = b;
else
ram[addr] = b;
}
#else
if (addr < 0x0200)
{
if (altzp)
ram2[addr] = b;
else
ram[addr] = b;
}
else
{
if (ramwrt)
ram2[addr] = b;
else
ram[addr] = b;
}
#endif
}
//
// Load a file into RAM/ROM image space
//
bool LoadImg(char * filename, uint8_t * 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_t counter = 0;
uint32_t totalCPU = 0;
uint64_t lastClock = 0;
#endif
//
// 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);
// Set up V65C02 execution context
memset(&mainCPU, 0, sizeof(V65C02REGS));
mainCPU.RdMem = RdMem;
mainCPU.WrMem = WrMem;
mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
// alternateCharset = true;
// if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
{
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);
floppyDrive.LoadImage(settings.diskImagePath2, 1);
// 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);
// memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
WriteLog("About to initialize video...\n");
if (!InitVideo())
{
std::cout << "Aborting!" << std::endl;
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();
//nope SDL_EnableUNICODE(1); // Needed to do key translation shit
// gui = new GUI(surface); // Set up the GUI system object...
// gui = new GUI(mainSurface); // Set up the GUI system object...
// SDL 2... this will likely cause Apple 2 to crash
// gui = new GUI(NULL); // Set up the GUI system object...
#if 0
gui->AddMenuTitle("Apple2");
gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
gui->AddMenuItem("");
gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
gui->CommitItemsToMenu();
#endif
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();
mainSem = SDL_CreateSemaphore(1);
cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
//Hmm... CPU does POST (+1), wait, then WAIT (-1)
// SDL_sem * mainMutex = SDL_CreateMutex();
#endif
WriteLog("Entering main loop...\n");
while (running)
{
double timeToNextEvent = GetTimeToNextEvent();
#ifndef THREADED_65C02
Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
#endif
//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));
HandleNextEvent();
}
#ifdef THREADED_65C02
WriteLog("Main: cpuFinished = true;\n");
cpuFinished = true;
//#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
//What to do? How do you know when the CPU is sleeping???
//USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
#if 0
SDL_mutexP(mainMutex);
SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
SDL_mutexV(mainMutex);
#else
//Nope, use a semaphore...
WriteLog("Main: SDL_SemWait(mainSem);\n");
SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
#endif
WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
SDL_WaitThread(cpuThread, NULL);
//nowok:SDL_WaitThread(CPUThreadFunc, NULL);
WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
SDL_DestroyCond(cpuCond);
//SDL_DestroyMutex(mainMutex);
SDL_DestroySemaphore(mainSem);
#endif
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 uint64_t lastCPUCycles = 0;
static uint32_t frameCount = 0;
static void FrameCallback(void)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_TEXTINPUT:
//Need to do some key translation here, and screen out non-apple keys as well...
//(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
// everything else done separately. this is slightly easier. :-P)
// if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
if (event.edit.text[0] == '\t') // Prelim key screening...
break;
lastKeyPressed = event.edit.text[0];
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...
break;
case SDL_KEYDOWN:
// 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;
else if (event.key.keysym.sym == SDLK_UP)
lastKeyPressed = 0x0B, keyDown = true;
else if (event.key.keysym.sym == SDLK_DOWN)
lastKeyPressed = 0x0A, keyDown = true;
else if (event.key.keysym.sym == SDLK_RETURN)
lastKeyPressed = 0x0D, keyDown = true;
else if (event.key.keysym.sym == SDLK_ESCAPE)
lastKeyPressed = 0x1B, keyDown = true;
else if (event.key.keysym.sym == SDLK_BACKSPACE)
lastKeyPressed = 0x7F, keyDown = true;
// Fix CTRL+key combo...
if (event.key.keysym.mod & KMOD_CTRL)
{
if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
{
lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
keyDown = true;
//printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
}
}
// 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;
// Paddle buttons 0 & 1
if (event.key.keysym.sym == SDLK_INSERT)
openAppleDown = true;
if (event.key.keysym.sym == SDLK_PAGEUP)
closedAppleDown = true;
if (event.key.keysym.sym == SDLK_F11)
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...
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[19] = "[****************]";
// volStr[GetVolume()] = 0;
for(int i=GetVolume(); i<16; i++)
volStr[1 + i] = '-';
SpawnMessage("Volume: %s", volStr);
}
else if (event.key.keysym.sym == SDLK_F6)
{
VolumeUp();
char volStr[19] = "[****************]";
// volStr[GetVolume()] = 0;
for(int i=GetVolume(); i<16; i++)
volStr[1 + i] = '-';
SpawnMessage("Volume: %s", volStr);
}
static bool fullscreenDebounce = false;
if (event.key.keysym.sym == SDLK_F12)
{
if (!fullscreenDebounce)
{
ToggleFullScreen();
fullscreenDebounce = true;
}
}
// else
break;
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_F12)
fullscreenDebounce = false;
// Paddle buttons 0 & 1
if (event.key.keysym.sym == SDLK_INSERT)
openAppleDown = false;
if (event.key.keysym.sym == SDLK_PAGEUP)
closedAppleDown = false;
// if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
// keyDown = false;
break;
case SDL_QUIT:
running = false;
}
}
//#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
RenderVideoFrame();
SetCallbackTime(FrameCallback, 16666.66666667);
#ifdef CPU_CLOCK_CHECKING
//We know it's stopped, so we can get away with this...
counter++;
if (counter == 60)
{
uint64_t clock = GetCurrentV65C02Clock();
//totalCPU += (uint32_t)(clock - lastClock);
printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
lastClock = clock;
// totalCPU = 0;
counter = 0;
}
#endif
//Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
//lessee...
//nope.
//Actually, slows things down too much...
//SDL_Delay(10);
// while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
// This is the problem: If you set the interval to 16, it runs faster than
// 1/60s per frame. If you set it to 17, it runs slower. What we need is to
// have it do 16 for one frame, then 17 for two others. Then it should average
// out to 1/60s per frame every 3 frames.
frameCount = (frameCount + 1) % 3;
uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
while (SDL_GetTicks() - startTicks < waitFrameTime)
SDL_Delay(1); // Wait for next frame...
startTicks = SDL_GetTicks();
#if 0
uint64_t cpuCycles = GetCurrentV65C02Clock();
uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
lastCPUCycles = cpuCycles;
#endif
//let's wait, then signal...
//works longer, but then still falls behind...
#ifdef THREADED_65C02
SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
#endif
}
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_t time = 20;
while (!done)
{
Execute6808(&soundCPU, time);
overflow += 0.289115646;
if (overflow > 1.0)
{
overflow -= 1.0;
time = 21;
}
else
time = 20;
}
*/