From 8a6f8af6e820a86b1a44d551f7287907e39c47a6 Mon Sep 17 00:00:00 2001 From: ArthurFerreira2 Date: Thu, 24 Jun 2021 22:55:47 +0200 Subject: [PATCH] stable version --- Makefile | 2 +- README.md | 6 +- puce6502.c | 2442 ++++++++++++++++++++++++++++------- puce6502.h | 35 +- reinetteII+.c | 1578 +++++++++++----------- reinetteII+.rc | 8 +- reinetteII+.res | Bin 0 -> 62766 bytes screenshots/Ms. Pac-Man.bmp | Bin 0 -> 645174 bytes screenshots/no disk.bmp | Bin 645174 -> 645174 bytes 9 files changed, 2807 insertions(+), 1264 deletions(-) create mode 100644 reinetteII+.res create mode 100644 screenshots/Ms. Pac-Man.bmp diff --git a/Makefile b/Makefile index fb43e7d..51ffe42 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -FLAGS = -std=c99 -pedantic -Wpedantic -Wall -O3 +FLAGS = -std=c11 -pedantic -Wpedantic -Wall -O3 LIBS = -lSDL2 # comment these two lines if you are under Linux : diff --git a/README.md b/README.md index baf5634..d63c4fd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ After [reinette](https://github.com/ArthurFerreira2/reinette) (Apple 1 emulator) * easy screenshot -It uses the same MOS 6502 CPU emulator as her sisters (now christened [puce6502](https://github.com/ArthurFerreira2/puce6502)).\ +It uses an optimized and accurate MOS 6502 CPU emulator (now christened [puce6502](https://github.com/ArthurFerreira2/puce6502)).\ You only need SDL2 to compile it. (I'm not using SDL_Mixer, but only the native SDL2 audio functions) This emulator is not accurate in many ways and does not compete with @@ -66,9 +66,7 @@ Use the functions keys to control the emulator itself : * F7 : reset the zoom to 2:1 * shift F7 : increase zoom up to 8:1 max * ctrl F7 : decrease zoom down to 1:1 pixels -* F8 : monochrome / color display (only in HGR mode) -* F9 : pause / un-pause the emulator -* F10 : break +* F10 : pause / un-pause the emulator * F11 : reset * F12 : about, help diff --git a/puce6502.c b/puce6502.c index cbb5417..0ef43b6 100644 --- a/puce6502.c +++ b/puce6502.c @@ -1,6 +1,6 @@ /* puce6502 - MOS 6502 cpu emulator - Last modified 1st of August 2020 + Last modified 21st of June 2021 Copyright (c) 2018 Arthur Ferreira (arthur.ferreira2@gmail.com) This version has been modified for reinette II plus, a french Apple II plus @@ -28,16 +28,13 @@ THE SOFTWARE. */ + +// set to zero for 'normal' use +// or to 1 if you want to run the functionnal tests +#define _FUNCTIONNAL_TESTS 0 + #include "puce6502.h" -// function to be provided by user to handle read and writes to locations not -// in ROM or in RAM : Soft Switches, extension cards ROMs, PIA, VIA, ACIA etc... -extern uint8_t softSwitches(uint16_t address, uint8_t value, bool WRT); - -// these are the Language Card switches used in readMem and writeMem -extern bool LCWR, LCRD, LCBK2; - - #define CARRY 0x01 #define ZERO 0x02 #define INTR 0x04 @@ -47,540 +44,2003 @@ extern bool LCWR, LCRD, LCBK2; #define OFLOW 0x40 #define SIGN 0x80 -struct Operand { - uint8_t code; - bool setAcc; - uint8_t value; - uint16_t address; -} ope; +#if _FUNCTIONNAL_TESTS -struct Register { - uint8_t A,X,Y,SR,SP; - uint16_t PC; -} reg; + // for functionnal tests, see main() + uint8_t RAM[65536]; + inline uint8_t readMem(uint16_t address) { return RAM[address]; } + inline void writeMem(uint16_t address, uint8_t value) { RAM[address] = value; } + +#else + + // user provided functions + extern uint8_t readMem(uint16_t address); + extern void writeMem(uint16_t address, uint8_t value); + +#endif -static int cycles[256] = { // cycles per instruction - 7,6,0,0,0,3,5,0,3,2,2,0,0,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, - 6,6,0,0,3,3,5,0,4,2,2,0,4,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, - 6,6,0,0,0,3,5,0,3,2,2,0,3,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, - 6,6,0,0,0,3,5,0,4,2,2,0,5,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, - 0,6,0,0,3,3,3,0,2,0,2,0,4,4,4,0,3,6,0,0,4,4,4,0,2,5,2,0,0,5,0,0, - 2,6,2,0,3,3,3,0,2,2,2,0,4,4,4,0,3,5,0,0,4,4,4,0,2,4,2,0,4,4,4,0, - 2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, - 2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0 +unsigned long long int ticks = 0; // accumulated number of clock cycles + + +static uint16_t PC; // Program Counter +static uint8_t A, X, Y, SP; // Accumulator, X and y indexes and Stack Pointer +static union { + uint8_t byte; + struct { + uint8_t C : 1; // Carry + uint8_t Z : 1; // Zero + uint8_t I : 1; // Interupt-disable + uint8_t D : 1; // Decimal + uint8_t B : 1; // Break + uint8_t U : 1; // Undefined + uint8_t V : 1; // Overflow + uint8_t S : 1; // Sign + }; +} P; // Processor Status + +void puce6502RST() { // Reset + PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); + SP = 0xFD; + P.I = 1; + P.U = 1; + ticks += 7; +} + + +void puce6502IRQ() { // Interupt Request + if (!P.I) return; + P.I = 1; + PC++; + writeMem(0x100 + SP, (PC >> 8) & 0xFF); + SP--; + writeMem(0x100 + SP, PC & 0xFF); + SP--; + writeMem(0x100 + SP, P.byte & ~BREAK); + SP--; + PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8); + ticks += 7; +} + + +void puce6502NMI() { // Non Maskable Interupt + P.I = 1; + PC++; + writeMem(0x100 + SP, (PC >> 8) & 0xFF); + SP--; + writeMem(0x100 + SP, PC & 0xFF); + SP--; + writeMem(0x100 + SP, P.byte & ~BREAK); + SP--; + PC = readMem(0xFFFA) | (readMem(0xFFFB) << 8); + ticks += 7; +} + + +/* + Addressing modes abreviations used in the comments down below : + + IMP : Implied or Implicit : DEX, RTS, CLC - 25 instructions + ACC : Accumulator : ASL A, ROR A, DEC A - 4 instructions + IMM : Immediate : LDA #$A5 - 11 + ZPG : Zero Page : LDA $81 - 21 instructions + ZPX : Zero Page Indexed with X : LDA $55,X - 16 instructions + ZPY : Zero Page Indexed with Y : LDX $55,Y - 2 instructions + REL : Relative : BEQ LABEL12 - 8 instructions + ABS : Absolute : LDA $2000 - 23 instructions + ABX : Absolute Indexed with X : LDA $2000,X - 15 instructions + ABY : Absolute Indexed with Y : LDA $2000,Y - 9 instructions + IND : Indirect : JMP ($1020) - 1 instruction + IZX : ZP Indexed Indirect with X (Preindexed) : LDA ($55,X) - 8 instructions + IZY : ZP Indirect Indexed with Y (Postindexed) : LDA ($55),Y - 8 instructions + */ + +uint16_t puce6502Exec(unsigned long long int cycleCount) { + register uint16_t address; + register uint8_t value8; + register uint16_t value16; + + cycleCount += ticks; // cycleCount becomes the targeted ticks value + while (ticks < cycleCount) { + + switch (readMem(PC++)) { // fetch instruction and increment Program Counter + + case 0x00 : // IMP BRK + PC++; + writeMem(0x100 + SP, ((PC) >> 8) & 0xFF); + SP--; + writeMem(0x100 + SP, PC & 0xFF); + SP--; + writeMem(0x100 + SP, P.byte | BREAK); + SP--; + P.I = 1; + P.D = 0; + PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8); + ticks += 7; + break; + + case 0x01 : // IZX ORA + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + A |= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0x05 : // ZPG ORA + A |= readMem(readMem(PC)); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0x06 : // ZPG ASL + address = readMem(PC); + PC++; + value16 = readMem(address) << 1; + P.C = value16 > 0xFF; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 5; + break; + + case 0x08 : // IMP PHP + writeMem(0x100 + SP, P.byte | BREAK); + SP--; + ticks += 3; + break; + + case 0x09 : // IMM ORA + A |= readMem(PC); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x0A : // ACC ASL + value16 = A << 1; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x0D : // ABS ORA + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + A |= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x0E : // ABS ASL + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value16 = readMem(address) << 1; + P.C = value16 > 0xFF; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 6; + break; + + case 0x10 : // REL BPL + address = readMem(PC); + PC++; + if (!P.S) { // jump taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0x11 : // IZY ORA + value8 = readMem(PC); + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + ticks += (((address & 0xFF) + Y) & 0xFF00) ? 6 : 5; // page crossing + address += Y; + A |= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x15 : // ZPX ORA + A |= readMem(readMem(PC) + X); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x16 : // ZPX ASL + address = readMem(PC) + X; + PC++; + value16 = readMem(address) << 1; + writeMem(address, value16 & 0xFF); + P.C = value16 > 0xFF; + P.Z = value16 == 0; + P.S = (value16 & 0xFF) > 0x7F; + ticks += 6; + break; + + case 0x18 : // IMP CLC + P.C = 0; + ticks += 2; + break; + + case 0x19 : // ABY ORA + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + A |= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x1D : // ABX ORA + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + A |= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x1E : // ABX ASL + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value16 = readMem(address) << 1; + P.C = value16 > 0xFF; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 7; + break; + + case 0x20 : // ABS JSR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + writeMem(0x100 + SP, (PC >> 8) & 0xFF); + SP--; + writeMem(0x100 + SP, PC & 0xFF); + SP--; + PC = address; + ticks += 6; + break; + + case 0x21 : // IZX AND + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0x24 : // ZPG BIT + address = readMem(PC); + PC++; + value8 = readMem(address); + P.Z = (A & value8) == 0; + P.byte = (P.byte & 0x3F) | (value8 & 0xC0); + ticks += 3; + break; + + case 0x25 : // ZPG AND + A &= readMem(readMem(PC)); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0x26 : // ZPG ROL + address = readMem(PC); + PC++; + value16 = (readMem(address) << 1) | P.C; + P.C = (value16 & 0x100) != 0; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 5; + break; + + case 0x28 : // IMP PLP + SP++; + P.byte = readMem(0x100 + SP) | UNDEF; + ticks += 4; + break; + + case 0x29 : // IMM AND + A &= readMem(PC); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x2A : // ACC ROL + value16 = (A << 1) | P.C; + P.C = (value16 & 0x100) != 0; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x2C : // ABS BIT + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + P.Z = (A & value8) == 0; + P.byte = (P.byte & 0x3F) | (value8 & 0xC0); + ticks += 4; + break; + + case 0x2D : // ABS AND + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x2E : // ABS ROL + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value16 = (readMem(address) << 1) | P.C; + P.C = (value16 & 0x100) != 0; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 6; + break; + + case 0x30 : // REL BMI + address = readMem(PC); + PC++; + if (P.S) { // branch taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0x31 : // IZY AND + value8 = readMem(PC); + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + ticks += (((address & 0xFF) + Y) & 0xFF00) ? 6 : 5; // page crossing + address += Y; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x35 : // ZPX AND + address = (readMem(PC) + X) & 0xFF; + PC++; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x36 : // ZPX ROL + address = (readMem(PC) + X) & 0xFF; + PC++; + value16 = (readMem(address) << 1) | P.C; + P.C = value16 > 0xFF; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 6; + break; + + case 0x38 : // IMP SEC + P.C = 1; + ticks += 2; + break; + + case 0x39 : // ABY AND + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x3D : // ABX AND + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + A &= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x3E : // ABX ROL + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value16 = (readMem(address) << 1) | P.C; + P.C = value16 > 0xFF; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 7; + break; + + case 0x40 : // IMP RTI + SP++; + P.byte = readMem(0x100 + SP); + SP++; + PC = readMem(0x100 + SP); + SP++; + PC |= readMem(0x100 + SP) << 8; + ticks += 6; + break; + + case 0x41 : // IZX EOR + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0x45 : // ZPG EOR + address = readMem(PC); + PC++; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0x46 : // ZPG LSR + address = readMem(PC); + PC++; + value8 = readMem(address); + P.C = (value8 & 1) != 0; + value8 = value8 >> 1; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 5; + break; + + case 0x48 : // IMP PHA + writeMem(0x100 + SP, A); + SP--; + ticks += 3; + break; + + case 0x49 : // IMM EOR + A ^= readMem(PC); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x4A : // ACC LSR + P.C = (A & 1) != 0; + A = A >> 1; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x4C : // ABS JMP + PC = readMem(PC) | (readMem(PC + 1) << 8); + ticks += 3; + break; + + case 0x4D : // ABS EOR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x4E : // ABS LSR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + P.C = (value8 & 1) != 0; + value8 = value8 >> 1; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 6; + break; + + case 0x50 : // REL BVC + address = readMem(PC); + PC++; + if (!P.V) { // branch taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0x51 : // IZY EOR + value8 = readMem(PC); + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + ticks += (((address & 0xFF) + Y) & 0xFF00) ? 6 : 5; // page crossing + A ^= readMem(address + Y); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x55 : // ZPX EOR + address = (readMem(PC) + X) & 0xFF; + PC++; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x56 : // ZPX LSR + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + P.C = (value8 & 1) != 0; + value8 = value8 >> 1; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 6; + break; + + case 0x58 : // IMP CLI + P.I = 0; + ticks += 2; + break; + + case 0x59 : // ABY EOR + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x5D : // ABX EOR + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + A ^= readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0x5E : // ABX LSR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + P.C = (value8 & 1) != 0; + value8 = value8 >> 1; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 7; + break; + + case 0x60 : // IMP RTS + SP++; + PC = readMem(0x100 + SP); + SP++; + PC |= readMem(0x100 + SP) << 8; + PC++; + ticks += 6; + break; + + case 0x61 : // IZX ADC + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0x65 : // ZPG ADC + address = readMem(PC); + PC++; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0x66 : // ZPG ROR + address = readMem(PC); + PC++; + value8 = readMem(address); + value16 = (value8 >> 1) | (P.C << 7); + P.C = (value8 & 0x1) != 0; + value16 &= 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 5; + break; + + case 0x68 : // IMP PLA + SP++; + A = readMem(0x100 + SP); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x69 : // IMM ADC + value8 = readMem(PC); + PC++; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x6A : // ACC ROR + value16 = (A >> 1) | (P.C << 7); + P.C = (A & 0x1) != 0; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x6C : // IND JMP + address = readMem(PC) | readMem(PC + 1) << 8; + PC = readMem(address) | (readMem(address + 1) << 8); + ticks += 5; + break; + + case 0x6D : // ABS ADC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x6E : // ABS ROR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + value16 = (value8 >> 1) | (P.C << 7); + P.C = (value8 & 0x1) != 0; + value16 = value16 & 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 6; + break; + + case 0x70 : // REL BVS + address = readMem(PC); + PC++; + if (P.V) { // branch taken + ticks++; + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + PC += address; + } + ticks += 2; + break; + + case 0x71 : // IZY ADC + value8 = readMem(PC); + PC++; + address = readMem(value8); + if ((address + Y) & 0xFF00) // page crossing + ticks++; + value8++; + address |= readMem(value8) << 8; + address += Y; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 5; + break; + + case 0x75 : // ZPX ADC + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x76 : // ZPX ROR + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + value16 = (value8 >> 1) | (P.C << 7); + P.C = (value8 & 0x1) != 0; + value16 = value16 & 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 6; + break; + + case 0x78 : // IMP SEI + P.I = 1; + ticks += 2; + break; + + case 0x79 : // ABY ADC + if ((readMem(PC) + Y) & 0xFF00) + ticks++; + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += Y; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x7D : // ABX ADC + if ((readMem(PC) + X) & 0xFF00) + ticks++; + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0x7E : // ABX ROR + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + value16 = (value8 >> 1) | (P.C << 7); + P.C = (value8 & 0x1) != 0; // TBR + value16 = value16 & 0xFF; + writeMem(address, value16); + P.Z = value16 == 0; + P.S = value16 > 0x7F; + ticks += 7; + break; + + case 0x81 : // IZX STA + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + writeMem(address, A); + ticks += 6; + break; + + case 0x84 : // ZPG STY + writeMem(readMem(PC), Y); + PC++; + ticks += 3; + break; + + case 0x85 : // ZPG STA + writeMem(readMem(PC), A); + PC++; + ticks += 3; + break; + + case 0x86 : // ZPG STX + writeMem(readMem(PC), X); + PC++; + ticks += 3; + break; + + case 0x88 : // IMP DEY + Y--; + P.Z = (Y & 0xFF) == 0; + P.S = (Y & SIGN) != 0; + ticks += 2; + break; + + case 0x8A : // IMP TXA + A = X; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x8C : // ABS STY + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + writeMem(address, Y); + ticks += 4; + break; + + case 0x8D : // ABS STA + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + writeMem(address, A); + ticks += 4; + break; + + case 0x8E : // ABS STX + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + writeMem(address, X); + ticks += 4; + break; + + case 0x90 : // REL BCC + address = readMem(PC); + PC++; + if (!P.C) { // branch taken + ticks++; + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + PC += address; + } + ticks += 2; + break; + + case 0x91 : // IZY STA + value8 = readMem(PC); + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + address += Y; + writeMem(address, A); + ticks += 6; + break; + + case 0x94 : // ZPX STY + address = (readMem(PC) + X) & 0xFF; + PC++; + writeMem(address, Y); + ticks += 4; + break; + + case 0x95 : // ZPX STA + writeMem((readMem(PC) + X) & 0xFF, A); + PC++; + ticks += 4; + break; + + case 0x96 : // ZPY STX + writeMem((readMem(PC) + Y) & 0xFF, X); + PC++; + ticks += 4; + break; + + case 0x98 : // IMP TYA + A = Y; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0x99 : // ABY STA + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += Y; + writeMem(address, A); + ticks += 5; + break; + + case 0x9A : // IMP TXS + SP = X; + ticks += 2; + break; + + case 0x9D : // ABX STA + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + writeMem(address, A); + ticks += 5; + break; + + case 0xA0 : // IMM LDY + Y = readMem(PC); + PC++; + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 2; + break; + + case 0xA1 : // IZX LDA + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + A = readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0xA2 : // IMM LDX + address = PC; + PC++; + X = readMem(address); + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 2; + break; + + case 0xA4 : // ZPG LDY + Y = readMem(readMem(PC)); + PC++; + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 3; + break; + + case 0xA5 : // ZPG LDA + A = readMem(readMem(PC)); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0xA6 : // ZPG LDX + X = readMem(readMem(PC)); + PC++; + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 3; + break; + + case 0xA8 : // IMP TAY + Y = A; + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 2; + break; + + case 0xA9 : // IMM LDA + A = readMem(PC); + PC++; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0xAA : // IMP TAX + X = A; + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 2; + break; + + case 0xAC : // ABS LDY + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + Y = readMem(address); + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 4; + break; + + case 0xAD : // ABS LDA + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + A = readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xAE : // ABS LDX + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + X = readMem(address); + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 4; + break; + + case 0xB0 : // REL BCS + address = readMem(PC); + PC++; + if (P.C) { // branch taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0xB1 : // IZY LDA + value8 = readMem(PC); + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + A = readMem(address + Y); + ticks += (((address & 0xFF) + Y) & 0xFF00) ? 6 : 5; // page crossing + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0xB4 : // ZPX LDY + address = (readMem(PC) + X) & 0xFF; + PC++; + Y = readMem(address); + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 4; + break; + + case 0xB5 : // ZPX LDA + address = (readMem(PC) + X) & 0xFF; + PC++; + A = readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xB6 : // ZPY LDX + address = (readMem(PC) + Y) & 0xFF; + PC++; + X = readMem(address); + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 4; + break; + + case 0xB8 : // IMP CLV + P.V = 0; + ticks += 2; + break; + + case 0xB9 : // ABY LDA + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + A = readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0xBA : // IMP TSX + X = SP; + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 2; + break; + + case 0xBC : // ABX LDY + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + Y = readMem(address); + P.Z = Y == 0; + P.S = Y > 0x7F; + break; + + case 0xBD : // ABX LDA + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + A = readMem(address); + P.Z = A == 0; + P.S = A > 0x7F; + break; + + case 0xBE : // ABY LDX + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + X = readMem(address); + P.Z = X == 0; + P.S = X > 0x7F; + break; + + case 0xC0 : // IMM CPY + value8 = readMem(PC); + PC++; + P.Z = ((Y - value8) & 0xFF) == 0; + P.S = ((Y - value8) & SIGN) != 0; + P.C = (Y >= value8) != 0; + ticks += 2; + break; + + case 0xC1 : // IZX CMP + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + ticks += 6; + break; + + case 0xC4 : // ZPG CPY + value8 = readMem(readMem(PC)); + PC++; + P.Z = ((Y - value8) & 0xFF) == 0; + P.S = ((Y - value8) & SIGN) != 0; + P.C = (Y >= value8) != 0; + ticks += 3; + break; + + case 0xC5 : // ZPG CMP + value8 = readMem(readMem(PC)); + PC++; + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + ticks += 3; + break; + + case 0xC6 : // ZPG DEC + address = readMem(PC); + PC++; + value8 = readMem(address); + --value8; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 5; + break; + + case 0xC8 : // IMP INY + Y++; + P.Z = Y == 0; + P.S = Y > 0x7F; + ticks += 2; + break; + + case 0xC9 : // IMM CMP + value8 = readMem(PC); + PC++; + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + ticks += 2; + break; + + case 0xCA : // IMP DEX + X--; + P.Z = (X & 0xFF) == 0; + P.S = X > 0x7F; + ticks += 2; + break; + + case 0xCC : // ABS CPY + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + P.Z = ((Y - value8) & 0xFF) == 0; + P.S = ((Y - value8) & SIGN) != 0; + P.C = (Y >= value8) != 0; + ticks += 4; + break; + + case 0xCD : // ABS CMP + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + ticks += 4; + break; + + case 0xCE : // ABS DEC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + value8--; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 3; + break; + + case 0xD0 : // REL BNE + address = readMem(PC); + PC++; + if (!P.Z) { // branch taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0xD1 : // IZY CMP + value8 = readMem(PC); + PC++; + address = readMem(value8); + ticks += ((address + Y) & 0xFF00) ? 6 : 5; // page crossing + value8++; + address |= readMem(value8) << 8; + address += Y; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + break; + + case 0xD5 : // ZPX CMP + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + ticks += 4; + break; + + case 0xD6 : // ZPX DEC + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + value8--; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 6; + break; + + case 0xD8 : // IMP CLD + P.D = 0; + ticks += 2; + break; + + case 0xD9 : // ABY CMP + address = readMem(PC); + PC++; + ticks += ((address + Y) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += Y; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + break; + + case 0xDD : // ABX CMP + address = readMem(PC); + PC++; + ticks += ((address + X) & 0xFF00) ? 5 : 4; // page crossing + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + P.Z = ((A - value8) & 0xFF) == 0; + P.S = ((A - value8) & SIGN) != 0; + P.C = (A >= value8) != 0; + break; + + case 0xDE : // ABX DEC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + value8--; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = (value8 & SIGN) != 0; + ticks += 7; + break; + + case 0xE0 : // IMM CPX + value8 = readMem(PC); + PC++; + P.Z = ((X - value8) & 0xFF) == 0; + P.S = ((X - value8) & SIGN) != 0; + P.C = (X >= value8) != 0; + ticks += 2; + break; + + case 0xE1 : // IZX SBC + value8 = readMem(PC) + X; + PC++; + address = readMem(value8); + value8++; + address |= readMem(value8) << 8; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 6; + break; + + case 0xE4 : // ZPG CPX + value8 = readMem(readMem(PC)); + PC++; + P.Z = ((X - value8) & 0xFF) == 0; + P.S = ((X - value8) & SIGN) != 0; + P.C = (X >= value8) != 0; + ticks += 3; + break; + + case 0xE5 : // ZPG SBC + value8 = readMem(readMem(PC)); + PC++; + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 3; + break; + + case 0xE6 : // ZPG INC + address = readMem(PC); + PC++; + value8 = readMem(address); + value8++; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 5; + break; + + case 0xE8 : // IMP INX + X++; + P.Z = X == 0; + P.S = X > 0x7F; + ticks += 2; + break; + + case 0xE9 : // IMM SBC + value8 = readMem(PC); + PC++; + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + (P.C); + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 2; + break; + + case 0xEA: // IMP NOP + ticks += 2; + break; + + case 0xEC : // ABS CPX + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + P.Z = ((X - value8) & 0xFF) == 0; + P.S = ((X - value8) & SIGN) != 0; + P.C = (X >= value8) != 0; + ticks += 4; + break; + + case 0xED : // ABS SBC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xEE : // ABS INC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + value8 = readMem(address); + value8++; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 6; + break; + + case 0xF0 : // REL BEQ + address = readMem(PC); + PC++; + if (P.Z) { // branch taken + ticks++; + if (address & SIGN) + address |= 0xFF00; // jump backward + if (((PC & 0xFF) + address) & 0xFF00) // page crossing + ticks++; + PC += address; + } + ticks += 2; + break; + + case 0xF1 : // IZY SBC + value8 = readMem(PC); + PC++; + address = readMem(value8); + if ((address + Y) & 0xFF00) // page crossing + ticks++; + value8++; + address |= readMem(value8) << 8; + address += Y; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 5; + break; + + case 0xF5 : // ZPX SBC + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xF6 : // ZPX INC + address = (readMem(PC) + X) & 0xFF; + PC++; + value8 = readMem(address); + value8++; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 6; + break; + + case 0xF8 : // IMP SED + P.D = 1; + ticks += 2; + break; + + case 0xF9 : // ABY SBC + address = readMem(PC); + PC++; + if ((address + Y) & 0xFF00) // page crossing + ticks++; + address |= readMem(PC) << 8; + PC++; + address += Y; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = value16 > 0xFF; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xFD : // ABX SBC + address = readMem(PC); + PC++; + if ((address + X) & 0xFF00) // page crossing + ticks++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + value8 ^= 0xFF; + if (P.D) + value8 -= 0x0066; + value16 = A + value8 + P.C; + P.V = ((value16 ^ A) & (value16 ^ value8) & 0x0080) != 0; + if (P.D) + value16 += ((((value16 + 0x66) ^ A ^ value8) >> 3) & 0x22) * 3; + P.C = (value16 & 0xFF00) != 0; + A = value16 & 0xFF; + P.Z = A == 0; + P.S = A > 0x7F; + ticks += 4; + break; + + case 0xFE : // ABX INC + address = readMem(PC); + PC++; + address |= readMem(PC) << 8; + PC++; + address += X; + value8 = readMem(address); + value8++; + writeMem(address, value8); + P.Z = value8 == 0; + P.S = value8 > 0x7F; + ticks += 7; + break; + + default: // invalid / undocumented opcode + ticks += 2; // as NOP + break; + } // end of switch + } + return PC; +} + + + +// the code below was used during developpment for test and debug +// and is not required for normal operation + +#include + +static const char* mn[256] = { + "BRK","ORA","UND","UND","UND","ORA","ASL","UND","PHP","ORA","ASL","UND","UND","ORA","ASL","UND", + "BPL","ORA","UND","UND","UND","ORA","ASL","UND","CLC","ORA","UND","UND","UND","ORA","ASL","UND", + "JSR","AND","UND","UND","BIT","AND","ROL","UND","PLP","AND","ROL","UND","BIT","AND","ROL","UND", + "BMI","AND","UND","UND","UND","AND","ROL","UND","SEC","AND","UND","UND","UND","AND","ROL","UND", + "RTI","EOR","UND","UND","UND","EOR","LSR","UND","PHA","EOR","LSR","UND","JMP","EOR","LSR","UND", + "BVC","EOR","UND","UND","UND","EOR","LSR","UND","CLI","EOR","UND","UND","UND","EOR","LSR","UND", + "RTS","ADC","UND","UND","UND","ADC","ROR","UND","PLA","ADC","ROR","UND","JMP","ADC","ROR","UND", + "BVS","ADC","UND","UND","UND","ADC","ROR","UND","SEI","ADC","UND","UND","UND","ADC","ROR","UND", + "UND","STA","UND","UND","STY","STA","STX","UND","DEY","UND","TXA","UND","STY","STA","STX","UND", + "BCC","STA","UND","UND","STY","STA","STX","UND","TYA","STA","TXS","UND","UND","STA","UND","UND", + "LDY","LDA","LDX","UND","LDY","LDA","LDX","UND","TAY","LDA","TAX","UND","LDY","LDA","LDX","UND", + "BCS","LDA","UND","UND","LDY","LDA","LDX","UND","CLV","LDA","TSX","UND","LDY","LDA","LDX","UND", + "CPY","CMP","UND","UND","CPY","CMP","DEC","UND","INY","CMP","DEX","UND","CPY","CMP","DEC","UND", + "BNE","CMP","UND","UND","UND","CMP","DEC","UND","CLD","CMP","UND","UND","UND","CMP","DEC","UND", + "CPX","SBC","UND","UND","CPX","SBC","INC","UND","INX","SBC","NOP","UND","CPX","SBC","INC","UND", + "BEQ","SBC","UND","UND","UND","SBC","INC","UND","SED","SBC","UND","UND","UND","SBC","INC","UND" }; +static const int am[256] = { + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x0 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x7 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0xA , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x4 , 0x4 , 0x5 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x0 , 0x0 , + 0x2 , 0xC , 0x2 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x4 , 0x4 , 0x5 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x8 , 0x8 , 0x9 , 0x0 , + 0x2 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x2 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 + }; -//=============================================================== MEMORY AND I/O +void dasm(uint16_t address) { -inline static uint8_t readMem(uint16_t address){ - if (address < RAMSIZE) - return(ram[address]); // RAM + uint8_t op = readMem(address); + uint8_t b1 = readMem((address + 1) & 0xFFFF); + uint8_t b2 = readMem((address + 2) & 0xFFFF); - if (address >= ROMSTART){ - if (!LCRD) - return(rom[address - ROMSTART]); // ROM + printf("%04X %02X ", address, op); - if (LCBK2 && (address < 0xE000)) - return(bk2[address - BK2START]); // BK2 - - return(lgc[address - LGCSTART]); // LC - } - - if ((address & 0xFF00) == SL6START) - return(sl6[address - SL6START]); // disk][ - - if (address == 0xCFFF || ((address & 0xFF00) == 0xC000)) - return(softSwitches(address, 0, false)); - - return(ticks % 256); // catch all, give a 'floating' value -} - - -inline static void writeMem(uint16_t address, uint8_t value){ - if (address < RAMSIZE) { - ram[address] = value; // RAM - return; - } - - if (LCWR && (address >= ROMSTART)){ - if (LCBK2 && (address < 0xE000)){ - bk2[address - BK2START] = value; // BK2 - return; - } - lgc[address - LGCSTART] = value; // LC - return; - } - - if (address == 0xCFFF || ((address & 0xFF00) == 0xC000)){ - softSwitches(address, value, true); // Soft Switches - return; + switch(am[op]) { + case 0x0: printf(" %s ", mn[op] ); break; // implied + case 0x1: printf(" %s A ", mn[op] ); break; // accumulator + case 0x2: printf("%02X %s #$%02X ", b1, mn[op],b1 ); break; // immediate + case 0x3: printf("%02X %s $%02X ", b1, mn[op],b1 ); break; // zero page + case 0x4: printf("%02X %s $%02X,X ", b1, mn[op],b1 ); break; // zero page, X indexed + case 0x5: printf("%02X %s $%02X,Y ", b1, mn[op],b1 ); break; // zero page, Y indexed + case 0x6: printf("%02X %s $%02X ", b1, mn[op],b1 ); break; // relative + case 0xC: printf("%02X %s ($%02X,X) ", b1, mn[op],b1 ); break; // X indexed, indirect + case 0xD: printf("%02X %s ($%02X),Y ", b1, mn[op],b1 ); break; // indirect, Y indexed + case 0x7: printf("%02X%02X %s $%02X%02X ",b1,b2,mn[op],b2,b1); break; // absolute + case 0x8: printf("%02X%02X %s $%02X%02X,X ",b1,b2,mn[op],b2,b1); break; // absolute, X indexed + case 0x9: printf("%02X%02X %s $%02X%02X,Y ",b1,b2,mn[op],b2,b1); break; // absolute, Y indexed + case 0xA: printf("%02X%02X %s ($%02X%02X) ",b1,b2,mn[op],b2,b1); break; // indirect } } - -//=============================================== STACK, SIGN AND OTHER ROUTINES - -inline static void push(uint8_t value){ - writeMem(0x100 + reg.SP--, value); +void printRegs() { + printf("A=%02X X=%02X Y=%02X S=%02X *S=%02X %c%c%c%c%c%c%c%c", \ + A, X, Y, SP, readMem(0x100 + SP), \ + P.S?'N':'-', P.V?'V':'-', P.U?'U':'.', P.B?'B':'-', \ + P.D?'D':'-', P.I?'I':'-', P.Z?'Z':'-', P.C?'C':'-'); } -inline static uint8_t pull(){ - return(readMem(0x100 + ++reg.SP)); +void setPC(uint16_t address) { + PC = address; } -inline static void setSZ(uint8_t value){ // update both the Sign & Zero FLAGS - if (value & 0x00FF) reg.SR &= ~ZERO; - else reg.SR |= ZERO; - if (value & 0x80) reg.SR |= SIGN; - else reg.SR &= ~SIGN; +uint16_t getPC(){ + return PC; } -inline static void branch(){ // used by the 8 branch instructions - ticks++; - if (((reg.PC & 0xFF) + ope.address) & 0xFF00) ticks++; - reg.PC += ope.address; -} -inline static void makeUpdates(uint8_t val){ // used by ASL, LSR, ROL and ROR - if (ope.setAcc){ - reg.A = val; - ope.setAcc = false; - } - else writeMem(ope.address, val); - setSZ(val); -} +#if _FUNCTIONNAL_TESTS -//============================================================= ADDRESSING MODES + // 6502 functonnal tests + // using Klaus Dormann's functonnal tests published at : + // https://github.com/Klaus2m5/6502_65C02_functional_tests -static void IMP(){ // IMPlicit -} + int main(int argc, char* argv[]){ -static void ACC(){ // ACCumulator - ope.value = reg.A; - ope.setAcc = true; -} + char *filename = "6502_functional_test.bin"; + FILE *f = fopen(filename, "rb"); + if (!f || fread(RAM, 1, 65536, f) != 65536) { + printf("ERROR : can't load %s\n", filename); + return(0); + } + fclose(f); -static void IMM(){ // IMMediate - ope.address = reg.PC++; - ope.value = readMem(ope.address); -} + puce6502RST(); // reset the CPU + PC = 0x400; // set Program Counter to start of code -static void ZPG(){ // Zero PaGe - ope.address = readMem(reg.PC++); - ope.value = readMem(ope.address); -} + unsigned long long int oldticks = 0; + uint16_t oldPC = PC, newPC = PC; // to detect the BNE $FE when an error occurs -static void ZPX(){ // Zero Page,X - ope.address = (readMem(reg.PC++) + reg.X) & 0xFF; - ope.value = readMem(ope.address); -} + // while(1) { + // dasm(newPC); + // printf(" "); + // newPC = puce6502Exec(1); + // printRegs(); + // printf(" Cycles: %llu Total: %llu\n", ticks - oldticks, ticks); + // oldticks = ticks; + // + // if (newPC == 0x3469){ // 6502_functional_test SUCCESS + // printf("\nReached end of 6502_functional_test @ %04X : SUCCESS !\n", newPC); + // break; + // } + // + // if (newPC == oldPC ) { + // printf("\n\nLoop detected @ %04X - Press ENTER to proceed with next test or CTRL to stop\n\n", newPC); + // return(-1); + // getchar(); + // PC = newPC + 2; + // } + // oldPC = newPC; + // } -static void ZPY(){ // Zero Page,Y - if (readMem(reg.PC) + reg.Y > 0xFF) ticks++; - ope.address = (readMem(reg.PC++) + reg.Y) & 0xFF; - ope.value = readMem(ope.address); -} + // Benchmark : replace the above while loop by this one + while(puce6502Exec(100) != 0x3469); + printf("%llu\n", ticks); + // and use the time utility to avaluate the speed the emulated 65C02 -static void REL(){ // RELative (for branch instructions) - ope.address = readMem(reg.PC++); - if (ope.address & 0x80) ope.address |= 0xFF00; // branch backward -} + return(0); + } -static void ABS(){ // ABSolute - ope.address = readMem(reg.PC) | (readMem(reg.PC + 1) << 8); - ope.value = readMem(ope.address); - reg.PC += 2; -} +#endif -static void ABX(){ // ABsolute,X - if (readMem(reg.PC) + reg.X > 0xFF) ticks++; - ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.X; - ope.value = readMem(ope.address); - reg.PC += 2; -} -static void ABY(){ // ABsolute,Y - if (readMem(reg.PC) + reg.Y > 0xFF) ticks++; - ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.Y; - ope.value = readMem(ope.address); - reg.PC += 2; -} +/* test results : -static void IND(){ // INDirect - JMP ($ABCD) with page-boundary wraparound bug - uint16_t vector1 = readMem(reg.PC) | (readMem(reg.PC + 1) << 8); - uint16_t vector2 = (vector1 & 0xFF00) | ((vector1 + 1) & 0x00FF); - ope.address = readMem(vector1) | (readMem(vector2) << 8); - ope.value = readMem(ope.address); - reg.PC += 2; -} -static void IDX(){ // InDexed indirect X - uint16_t vector1 = ((readMem(reg.PC++) + reg.X) & 0xFF); - ope.address = readMem(vector1 & 0x00FF)|(readMem((vector1+1) & 0x00FF) << 8); - ope.value = readMem(ope.address); -} +## Using 6502_functional_test.bin : -static void IDY(){ // InDirect Indexed Y - uint16_t vector1 = readMem(reg.PC++); - uint16_t vector2 = (vector1 & 0xFF00) | ((vector1 + 1) & 0x00FF); - ope.address = (readMem(vector1) | (readMem(vector2) << 8)) + reg.Y; - ope.value = readMem(ope.address); -} +<...> +3457 69 55 ADC #$55 A=AA X=0E Y=FF S=FF *S=34 NVUB---- Cycles: 2 Total: 96240555 +3459 C9 AA CMP #$AA A=AA X=0E Y=FF S=FF *S=34 -VUB--ZC Cycles: 2 Total: 96240557 +345B D0 FE BNE $FE A=AA X=0E Y=FF S=FF *S=34 -VUB--ZC Cycles: 2 Total: 96240559 +345D AD 0002 LDA $0200 A=2B X=0E Y=FF S=FF *S=34 -VUB---C Cycles: 4 Total: 96240563 +3460 C9 2B CMP #$2B A=2B X=0E Y=FF S=FF *S=34 -VUB--ZC Cycles: 2 Total: 96240565 +3462 D0 FE BNE $FE A=2B X=0E Y=FF S=FF *S=34 -VUB--ZC Cycles: 2 Total: 96240567 +3464 A9 F0 LDA #$F0 A=F0 X=0E Y=FF S=FF *S=34 NVUB---C Cycles: 2 Total: 96240569 +3466 8D 0002 STA $0200 A=F0 X=0E Y=FF S=FF *S=34 NVUB---C Cycles: 4 Total: 96240573 +Reached end of 6502_functional_test @ 3469 : SUCCESS ! -//================================================================= INSTRUCTIONS -static void NOP(){ // NO Operation -} +=> which means puce65c02 passes the 6502 functionnal tests : -void BRK(){ // BReaK - push(((++reg.PC) >> 8) & 0xFF); - push(reg.PC & 0xFF); - push(reg.SR | BREAK); - reg.SR |= INTR; - reg.PC = readMem(0xFFFE) | ((readMem(0xFFFF) << 8)); // IRQ/BRK FFFE FFFF -} + ; S U C C E S S ************************************************ + ; ------------- + success ;if you get here everything went well +3469 : 4c6934 > jmp * ;test passed, no errors -static void CLD(){ // CLear Decimal - reg.SR &= ~DECIM; -} + ; ------------- + ; S U C C E S S ************************************************ -static void SED(){ // SEt Decimal - reg.SR |= DECIM; -} -static void CLC(){ // CLear Carry - reg.SR &= ~CARRY; -} -static void SEC(){ // SEt Carry - reg.SR |= CARRY; -} -static void CLI(){ // CLear Interrupt - reg.SR &= ~INTR; -} +## Benchmarks -static void SEI(){ // SEt Interrupt - reg.SR |= INTR; -} + No info printed during execution + Using gcc -O3 option -static void CLV(){ // CLear oVerflow - reg.SR &= ~OFLOW; -} + $ gcc -O3 puce6502.c -o puce6502 + $ time ./puce6502 + 96240630 -static void LDA(){ // LoaD Accumulator - reg.A = ope.value; - setSZ(reg.A); -} + real 0m0,093s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void LDX(){ // LoaD X - reg.X = ope.value; - setSZ(reg.X); -} + real 0m0,107s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void LDY(){ // LoaD Y - reg.Y = ope.value; - setSZ(reg.Y); -} + real 0m0,096s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void STA(){ // STore Accumulator - writeMem(ope.address, reg.A); -} + real 0m0,097s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void STX(){ // STore X - writeMem(ope.address, reg.X); -} + real 0m0,110s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void STY(){ // STore Y - writeMem(ope.address, reg.Y); -} - -static void DEC(){ // DECrement - writeMem(ope.address, --ope.value); - setSZ(ope.value); -} - -static void DEX(){ // DEcrement X - setSZ(--reg.X); -} - -static void DEY(){ // DEcrement Y - setSZ(--reg.Y); -} - -static void INC(){ // INCrement - writeMem(ope.address, ++ope.value); - setSZ(ope.value); -} - -static void INX(){ // INcrement X - setSZ(++reg.X); -} - -static void INY(){ // INcrement Y - setSZ(++reg.Y); -} - -static void TAX(){ // Transfer Accumulator to X - reg.X = reg.A; - setSZ(reg.X); -} + real 0m0,110s + user 0m0,000s + sys 0m0,000s + $ time ./puce6502 + 96240630 -static void TAY(){ // Transfer Accumulator to Y - reg.Y = reg.A; - setSZ(reg.Y); -} - -static void TXA(){ // Transfer X to Accumulator - reg.A = reg.X; - setSZ(reg.A); -} - -static void TYA(){ // Transfer Y to Accumulator - reg.A = reg.Y; - setSZ(reg.A); -} - -static void TSX(){ // Transfer Sp to X - reg.X = reg.SP; - setSZ(reg.X); -} + real 0m0,095s + user 0m0,000s + sys 0m0,000s -static void TXS(){ // Transfer X to Sp - reg.SP = reg.X; -} -static void BEQ(){ // Branch on EQual (zero set) - if (reg.SR & ZERO) branch(); -} - -static void BNE(){ // Branch on Not Equal (zero clear) - if (!(reg.SR & ZERO)) branch(); -} - -static void BMI(){ // Branch if MInus : when negative, when SIGN is set - if (reg.SR & SIGN) branch(); -} - -static void BPL(){ // Branch if PLus : when positive, when SIGN is clear - if (!(reg.SR & SIGN)) branch(); -} +--> emulated CPU running at around 1 GHz !!!?!??? -static void BVS(){ // Branch on oVerflow Set - if (reg.SR & OFLOW) branch(); -} - -static void BVC(){ // Branch on oVerflow Clear - if (!(reg.SR & OFLOW)) branch(); -} - -static void BCS(){ // Branch on Carry Set - if (reg.SR & CARRY) branch(); -} - -static void BCC(){ // Branch on Carry Clear - if (!(reg.SR & CARRY)) branch(); -} - -static void PHA(){ // PusH A to the stack - push(reg.A); -} - -static void PLA(){ // PulL stack into A - reg.A = pull(); - setSZ(reg.A); -} - -static void PHP(){ // PusH Programm (Status) register to the stack - push(reg.SR | BREAK); -} - -static void PLP(){ // PulL stack into Programm (SR) register - reg.SR = pull() | UNDEF; -} - -static void JMP(){ // JuMP - reg.PC = ope.address; -} - -static void JSR(){ // Jump Sub-Routine - push((--reg.PC >> 8) & 0xFF); - push(reg.PC & 0xFF); - reg.PC = ope.address; -} - -static void RTS(){ // ReTurn from Sub-routine - reg.PC = (pull() | (pull() << 8)) + 1; -} - -static void RTI(){ // ReTurn from Interrupt - reg.SR = pull(); - reg.PC = pull() | (pull() << 8); -} - -static void CMP(){ // Compare with A - setSZ(reg.A - ope.value); - if (reg.A >= ope.value) reg.SR |= CARRY; - else reg.SR &= ~CARRY; -} - -static void CPX(){ // Compare with X - setSZ(reg.X - ope.value); - if (reg.X >= ope.value) reg.SR |= CARRY; - else reg.SR &= ~CARRY; -} - -static void CPY(){ // Compare with Y - setSZ(reg.Y - ope.value); - if (reg.Y >= ope.value) reg.SR |= CARRY; - else reg.SR &= ~CARRY; -} - -static void AND(){ // AND with A - reg.A &= ope.value; - setSZ(reg.A); -} - -static void ORA(){ // OR with A - reg.A |= ope.value; - setSZ(reg.A); -} - -static void EOR(){ // Exclusive Or with A - reg.A ^= ope.value; - setSZ(reg.A); -} - -static void BIT(){ // BIT with A - http://www.6502.org/tutorials/vflag.html - if (reg.A & ope.value) reg.SR &= ~ZERO; - else reg.SR |= ZERO; - reg.SR = (reg.SR & 0x3F) | (ope.value & 0xC0); // update SIGN & OFLOW -} - -static void ASL(){ // Arithmetic Shift Left - uint16_t result = (ope.value << 1); - if (result & 0xFF00) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - makeUpdates((uint8_t)(result & 0xFF)); -} - -static void LSR(){ // Logical Shift Right - if (ope.value & 1) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - makeUpdates((uint8_t)((ope.value >> 1) & 0xFF)); -} - -static void ROL(){ // ROtate Left - uint16_t result = ((ope.value << 1) | (reg.SR & CARRY)); - if (result & 0x100) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - makeUpdates((uint8_t)(result & 0xFF)); -} - -static void ROR(){ // ROtate Right - uint16_t result = (ope.value >> 1) | ((reg.SR & CARRY) << 7); - if (ope.value & 0x1) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - makeUpdates((uint8_t)(result & 0xFF)); -} - -static void ADC(){ // ADd with Carry - uint16_t result = reg.A + ope.value + (reg.SR & CARRY); - setSZ(result); - if (((result)^(reg.A))&((result)^(ope.value))&0x0080) reg.SR |= OFLOW; - else reg.SR &= ~OFLOW; - if (reg.SR&DECIM) result += ((((result+0x66)^reg.A^ope.value)>>3)&0x22)*3; - if (result & 0xFF00) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - reg.A = (result & 0xFF); -} - -static void SBC(){ // SuBtract with Carry - ope.value ^= 0xFF; - if (reg.SR & DECIM) ope.value -= 0x0066; - uint16_t result = reg.A + ope.value + (reg.SR & CARRY); - setSZ(result); - if (((result)^(reg.A))&((result)^(ope.value))&0x0080) reg.SR |= OFLOW; - else reg.SR &= ~OFLOW; - if (reg.SR&DECIM) result += ((((result+0x66)^reg.A^ope.value)>>3)&0x22)*3; - if (result & 0xFF00) reg.SR |= CARRY; - else reg.SR &= ~CARRY; - reg.A = (result & 0xFF); -} - -static void UND(){ // UNDefined (not a valid or supported 6502 opcode) - BRK(); -} - - -//================================================================== JUMP TABLES - -static void (*instruction[])(void) = { - BRK, ORA, UND, UND, UND, ORA, ASL, UND, PHP, ORA, ASL, UND, UND, ORA, ASL, UND, - BPL, ORA, UND, UND, UND, ORA, ASL, UND, CLC, ORA, UND, UND, UND, ORA, ASL, UND, - JSR, AND, UND, UND, BIT, AND, ROL, UND, PLP, AND, ROL, UND, BIT, AND, ROL, UND, - BMI, AND, UND, UND, UND, AND, ROL, UND, SEC, AND, UND, UND, UND, AND, ROL, UND, - RTI, EOR, UND, UND, UND, EOR, LSR, UND, PHA, EOR, LSR, UND, JMP, EOR, LSR, UND, - BVC, EOR, UND, UND, UND, EOR, LSR, UND, CLI, EOR, UND, UND, UND, EOR, LSR, UND, - RTS, ADC, UND, UND, UND, ADC, ROR, UND, PLA, ADC, ROR, UND, JMP, ADC, ROR, UND, - BVS, ADC, UND, UND, UND, ADC, ROR, UND, SEI, ADC, UND, UND, UND, ADC, ROR, UND, - UND, STA, UND, UND, STY, STA, STX, UND, DEY, UND, TXA, UND, STY, STA, STX, UND, - BCC, STA, UND, UND, STY, STA, STX, UND, TYA, STA, TXS, UND, UND, STA, UND, UND, - LDY, LDA, LDX, UND, LDY, LDA, LDX, UND, TAY, LDA, TAX, UND, LDY, LDA, LDX, UND, - BCS, LDA, UND, UND, LDY, LDA, LDX, UND, CLV, LDA, TSX, UND, LDY, LDA, LDX, UND, - CPY, CMP, UND, UND, CPY, CMP, DEC, UND, INY, CMP, DEX, UND, CPY, CMP, DEC, UND, - BNE, CMP, UND, UND, UND, CMP, DEC, UND, CLD, CMP, UND, UND, UND, CMP, DEC, UND, - CPX, SBC, UND, UND, CPX, SBC, INC, UND, INX, SBC, NOP, UND, CPX, SBC, INC, UND, - BEQ, SBC, UND, UND, UND, SBC, INC, UND, SED, SBC, UND, UND, UND, SBC, INC, UND -}; - -static void (*addressing[])(void) = { - IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, IMP, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, - ABS, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, - IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, - IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, IND, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, - IMP, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMP, IMP, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, ZPX, ZPX, ZPY, IMP, IMP, ABY, IMP, IMP, IMP, ABX, IMP, IMP, - IMM, IDX, IMM, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, ZPX, ZPX, ZPY, IMP, IMP, ABY, IMP, IMP, ABX, ABX, ABY, IMP, - IMM, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, - IMM, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, - REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP -}; - - -//========================================================= USER INTERFACE (API) - -void puce6502Reset(){ - reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); // RESET FFFC FFFD - reg.SP = 0xFF; - reg.SR = (reg.SR | INTR) & ~DECIM; - ope.setAcc = false; - ticks += 7; -} - -void puce6502Exec(long long int cycleCount){ - cycleCount += ticks; // cycleCount becomes the target ticks value - while (ticks < cycleCount) { - ope.code = readMem(reg.PC++); // FETCH and increment the Program Counter - addressing[ope.code](); // DECODE against the addressing mode - instruction[ope.code](); // EXECUTE the instruction - ticks += cycles[ope.code]; // update ticks count - } -} - - -//=================================================== ADDED FOR REINETTE II PLUS - -void puce6502Break(){ - BRK(); -} +*/ diff --git a/puce6502.h b/puce6502.h index a1f3b7f..40893be 100644 --- a/puce6502.h +++ b/puce6502.h @@ -1,10 +1,9 @@ /* - Puce6502 - MOS 6502 cpu emulator Last modified 1st of August 2020 Copyright (c) 2018 Arthur Ferreira (arthur.ferreira2@gmail.com) - This version has been modified for reinette II plus, a french Apple II plus + This version has been modified for Reinette II plus, a french Apple II plus emulator using SDL2 (https://github.com/ArthurFerreira2/reinette-II-plus). Please download the latest version from @@ -27,36 +26,26 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + #ifndef _PUCE6502_H #define _PUCE6502_H typedef unsigned char uint8_t; typedef unsigned short uint16_t; -typedef enum {false, true} bool; +typedef enum { false, true } bool; -#define RAMSIZE 0xC000 -#define ROMSTART 0xD000 -#define ROMSIZE 0x3000 -#define LGCSTART 0xD000 -#define LGCSIZE 0x3000 -#define BK2START 0xD000 -#define BK2SIZE 0x1000 -#define SL6START 0xC600 -#define SL6SIZE 0x0100 +extern unsigned long long int ticks; -uint8_t ram[RAMSIZE]; // 48K of ram in $000-$BFFF -uint8_t rom[ROMSIZE]; // 12K of rom in $D000-$FFFF -uint8_t lgc[LGCSIZE]; // Language Card 12K in $D000-$FFFF -uint8_t bk2[BK2SIZE]; // bank 2 of Language Card 4K in $D000-$DFFF -uint8_t sl6[SL6SIZE]; // P5A disk ][ PROM in slot 6 +uint16_t puce6502Exec(unsigned long long int cycleCount); +void puce6502RST(); +void puce6502IRQ(); +void puce6502NMI(); -long long int ticks; - -void puce6502Exec(long long int cycleCount); -void puce6502Reset(); -void puce6502Break(); +// void printRegs(); +// void dasm(uint16_t address); +// void setPC(uint16_t address); +// uint16_t getPC(); #endif diff --git a/reinetteII+.c b/reinetteII+.c index 3858a27..dccc7dd 100644 --- a/reinetteII+.c +++ b/reinetteII+.c @@ -1,814 +1,910 @@ /* - reinette II plus, a french Apple II emulator, using SDL2 - and powered by puce6502 - a MOS 6502 cpu emulator by the same author - Last modified 5th of September 2020 - Copyright (c) 2020 Arthur Ferreira (arthur.ferreira2@gmail.com) + * Reinette II plus, a french Apple II emulator, using SDL2 + * and powered by puce6502 - a MOS 6502 cpu emulator by the same author + * Last modified 21st of June 2021 + * Copyright (c) 2020 Arthur Ferreira (arthur.ferreira2@gmail.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ #include #include #include + #include "puce6502.h" +// memory layout +#define RAMSIZE 0xC000 +#define ROMSTART 0xD000 +#define ROMSIZE 0x3000 +uint8_t ram[RAMSIZE]; // 48K of ram in $000-$BFFF +uint8_t rom[ROMSIZE]; // 12K of rom in $D000-$FFFF + +// language card +#define LGCSTART 0xD000 +#define LGCSIZE 0x3000 +#define BK2START 0xD000 +#define BK2SIZE 0x1000 +uint8_t lgc[LGCSIZE]; // Language Card 12K in $D000-$FFFF +uint8_t bk2[BK2SIZE]; // bank 2 of Language Card 4K in $D000-$DFFF + +// disk ][ prom +#define SL6START 0xC600 +#define SL6SIZE 0x0100 +uint8_t sl6[SL6SIZE]; // P5A disk ][ prom in slot 6 + + //================================================================ SOFT SWITCHES uint8_t KBD = 0; // $C000, $C010 ascii value of keyboard input -bool TEXT = true; // $C050 CLRTEXT / $C051 SETTEXT -bool MIXED = false; // $C052 CLRMIXED / $C053 SETMIXED -uint8_t PAGE = 1; // $C054 PAGE1 / $C055 PAGE2 -bool HIRES = false; // $C056 GR / $C057 HGR -bool LCWR = true; // Language Card writable -bool LCRD = false; // Language Card readable -bool LCBK2 = true; // Language Card bank 2 enabled -bool LCWFF = false; // Language Card pre-write flip flop +bool TEXT = true; // $C050 CLRTEXT / $C051 SETTEXT +bool MIXED = false; // $C052 CLRMIXED / $C053 SETMIXED +bool PAGE2 = false; // $C054 PAGE2 off / $C055 PAGE2 on +bool HIRES = false; // $C056 GR / $C057 HGR +bool LCWR = true; // Language Card writable +bool LCRD = false; // Language Card readable +bool LCBK2 = true; // Language Card bank 2 enabled +bool LCWFF = false; // Language Card pre-write flip flop + //====================================================================== PADDLES -uint8_t PB0 = 0; // $C061 Push Button 0 (bit 7) / Open Apple -uint8_t PB1 = 0; // $C062 Push Button 1 (bit 7) / Solid Apple -uint8_t PB2 = 0; // $C063 Push Button 2 (bit 7) / shift mod !!! -float GCP[2] = {127, 127}; // GC Position ranging from 0 (left) to 255 right -float GCC[2] = {0}; // $C064 (GC0) and $C065 (GC1) Countdowns -int GCD[2] = {0}; // GC0 and GC1 Directions (left/down or right/up) -int GCA[2] = {0}; // GC0 and GC1 Action (push or release) +uint8_t PB0 = 0; // $C061 Push Button 0 (bit 7) / Open Apple +uint8_t PB1 = 0; // $C062 Push Button 1 (bit 7) / Solid Apple +uint8_t PB2 = 0; // $C063 Push Button 2 (bit 7) / shift mod !!! +float GCP[2] = { 127.0f, 127.0f }; // GC Position ranging from 0 (left) to 255 right +float GCC[2] = { 0.0f }; // $C064 (GC0) and $C065 (GC1) Countdowns +int GCD[2] = { 0 }; // GC0 and GC1 Directions (left/down or right/up) +int GCA[2] = { 0 }; // GC0 and GC1 Action (push or release) uint8_t GCActionSpeed = 8; // Game Controller speed at which it goes to the edges uint8_t GCReleaseSpeed = 8; // Game Controller speed at which it returns to center long long int GCCrigger; // $C070 the tick at which the GCs were reseted -inline static void resetPaddles(){ - GCC[0] = GCP[0] * GCP[0]; // initialize the countdown for both paddles - GCC[1] = GCP[1] * GCP[1]; // to the square of their actuall values (positions) - GCCrigger = ticks; // records the time this was done +inline static void resetPaddles() { + GCC[0] = GCP[0] * GCP[0]; // initialize the countdown for both paddles + GCC[1] = GCP[1] * GCP[1]; // to the square of their actuall values (positions) + GCCrigger = ticks; // records the time this was done } -inline static uint8_t readPaddle(int pdl){ - const float GCFreq = 6.6; // the speed at which the GC values decrease - GCC[pdl] -= (ticks - GCCrigger) / GCFreq; // decreases the countdown - if (GCC[pdl] <= 0) // timeout - return(GCC[pdl] = 0); // returns 0 - return(0x80); // not timeout, return something with the MSB set +inline static uint8_t readPaddle(int pdl) { + const float GCFreq = 6.6; // the speed at which the GC values decrease + + GCC[pdl] -= (ticks - GCCrigger) / GCFreq; // decreases the countdown + if (GCC[pdl] <= 0) // timeout + return GCC[pdl] = 0; // returns 0 + return 0x80; // not timeout, return something with the MSB set } -//======================================================================== AUDIO + +//====================================================================== SPEAKER #define audioBufferSize 4096 // found to be large enought -Sint8 audioBuffer[2][audioBufferSize] = {0}; // see main() for more details +Sint8 audioBuffer[2][audioBufferSize] = { 0 }; // see in main() for more details SDL_AudioDeviceID audioDevice; -bool muted = false; // mute/unmute +bool muted = false; // mute/unmute switch -static void playSound(){ - static long long int lastTick = 0LL; - static bool SPKR = false; // $C030 Speaker toggle +static void playSound() { + static long long int lastTick = 0LL; + static bool SPKR = false; // $C030 Speaker toggle - if (!muted){ - SPKR = !SPKR; // toggle speaker state - Uint32 length = (ticks - lastTick) / 10.65625; // 1023000Hz / 96000Hz = 10.65625 - lastTick = ticks; - if (length > audioBufferSize) length = audioBufferSize; - SDL_QueueAudio(audioDevice, audioBuffer[SPKR], length | 1); // | 1 TO HEAR HIGH FREQ SOUNDS - } + if (!muted) { + SPKR = !SPKR; // toggle speaker state + Uint32 length = (int)((double)(ticks - lastTick) / 10.65625f); // 1023000Hz / 96000Hz = 10.65625 + lastTick = ticks; + if (length > audioBufferSize) length = audioBufferSize; + SDL_QueueAudio(audioDevice, audioBuffer[SPKR], length | 1); // | 1 TO HEAR HIGH FREQ SOUNDS + } } + //====================================================================== DISK ][ int curDrv = 0; // Current Drive - only one can be enabled at a time -struct drive{ - char filename[400]; // the full disk image pathname - bool readOnly; // based on the image file attributes - uint8_t data[232960]; // nibblelized disk image - bool motorOn; // motor status - bool writeMode; // writes to file are not implemented - uint8_t track; // current track position - uint16_t nibble; // ptr to nibble under head position -} disk[2] = {0}; // two disk ][ drive units +struct drive { + char filename[400]; // the full disk image pathname + bool readOnly; // based on the image file attributes + uint8_t data[232960]; // nibblelized disk image + bool motorOn; // motor status + bool writeMode; // writes to file are not implemented + uint8_t track; // current track position + uint16_t nibble; // ptr to nibble under head position +} disk[2] = { 0 }; // two disk ][ drive units -int insertFloppy(SDL_Window *wdo, char *filename, int drv){ - FILE *f = fopen(filename, "rb"); // open file in read binary mode - if (!f || fread(disk[drv].data, 1, 232960, f) != 232960) // load it into memory and check size - return(0); - fclose(f); +int insertFloppy(SDL_Window *wdo, char *filename, int drv) { - sprintf(disk[drv].filename,"%s", filename); // update disk filename record + FILE *f = fopen(filename, "rb"); // open file in read binary mode + if (!f || fread(disk[drv].data, 1, 232960, f) != 232960) // load it into memory and check size + return 0; + fclose(f); - f = fopen(filename, "ab"); // try to open the file in append binary mode - if (!f){ // success, file is writable - disk[drv].readOnly = true; // update the readOnly flag - fclose(f); // and close it untouched - } - else disk[drv].readOnly = false; // f is NULL, no writable, no need to close it + sprintf(disk[drv].filename, "%s", filename); // update disk filename record - char title[1000]; // UPDATE WINDOW TITLE - int i, a, b; - i = a = 0; - while (disk[0].filename[i] != 0) // find start of filename for disk0 - if (disk[0].filename[i++] == '\\') a = i; - i = b = 0; - while (disk[1].filename[i] != 0) // find start of filename for disk1 - if (disk[1].filename[i++] == '\\') b = i; + f = fopen(filename, "ab"); // try to open the file in append binary mode + if (f) { // success, file is writable + disk[drv].readOnly = false; // update the readOnly flag + fclose(f); // and close it untouched + } else { + disk[drv].readOnly = true; // f is NULL, no writable, no need to close it + } + char title[1000]; // UPDATE WINDOW TITLE + int i, a, b; - sprintf(title, "reinette II+ D1: %s D2: %s", \ - disk[0].filename + a, disk[1].filename + b); - SDL_SetWindowTitle(wdo, title); // updates window title + i = a = 0; + while (disk[0].filename[i] != 0) // find start of filename for disk0 + if (disk[0].filename[i++] == '\\') a = i; + i = b = 0; + while (disk[1].filename[i] != 0) // find start of filename for disk1 + if (disk[1].filename[i++] == '\\') b = i; - return(1); + sprintf(title, "Reinette ][+ D1: %s D2: %s", disk[0].filename + a, disk[1].filename + b); + SDL_SetWindowTitle(wdo, title); // updates window title + + return 1; } -int saveFloppy(int drive){ - if (!disk[drive].filename[0]) return 0; // no file loaded into drive - if (disk[drive].readOnly) return 0; // file is read only write no aptempted +int saveFloppy(int drive) { + if (!disk[drive].filename[0]) return 0; // no file loaded into drive + if (disk[drive].readOnly) return 0; // file is read only write no aptempted - FILE *f = fopen(disk[drive].filename, "wb"); - if (!f) return(0); // could not open the file in write overide binary - if (fwrite(disk[drive].data, 1, 232960, f) != 232960){ // failed to write the full file (disk full ?) - fclose(f); // release the ressource - return(0); - } - fclose(f); // success, release the ressource - return (1); + FILE *f = fopen(disk[drive].filename, "wb"); + if (!f) return 0; // could not open the file in write overide binary + + if (fwrite(disk[drive].data, 1, 232960, f) != 232960) { // failed to write the full file (disk full ?) + fclose(f); // release the ressource + return 0; + } + fclose(f); // success, release the ressource + return 1; } -void stepMotor(uint16_t address){ - static bool phases[2][4] = {0}; // phases states (for both drives) - static bool phasesB[2][4] = {0}; // phases states Before - static bool phasesBB[2][4] = {0}; // phases states Before Before - static int pIdx[2] = {0}; // phase index (for both drives) - static int pIdxB[2] = {0}; // phase index Before - static int halfTrackPos[2] = {0}; +void stepMotor(uint16_t address) { + static bool phases[2][4] = { 0 }; // phases states (for both drives) + static bool phasesB[2][4] = { 0 }; // phases states Before + static bool phasesBB[2][4] = { 0 }; // phases states Before Before + static int pIdx[2] = { 0 }; // phase index (for both drives) + static int pIdxB[2] = { 0 }; // phase index Before + static int halfTrackPos[2] = { 0 }; - address &= 7; - int phase = address >> 1; + address &= 7; + int phase = address >> 1; - phasesBB[curDrv][pIdxB[curDrv]] = phasesB[curDrv][pIdxB[curDrv]]; - phasesB[curDrv][pIdx[curDrv]] = phases[curDrv][pIdx[curDrv]]; - pIdxB[curDrv] = pIdx[curDrv]; - pIdx[curDrv] = phase; + phasesBB[curDrv][pIdxB[curDrv]] = phasesB[curDrv][pIdxB[curDrv]]; + phasesB[curDrv][pIdx[curDrv]] = phases[curDrv][pIdx[curDrv]]; + pIdxB[curDrv] = pIdx[curDrv]; + pIdx[curDrv] = phase; - if (!(address & 1)){ // head not moving (PHASE x OFF) - phases[curDrv][phase] = false; - return; - } + if (!(address & 1)) { // head not moving (PHASE x OFF) + phases[curDrv][phase] = false; + return; + } - if ((phasesBB[curDrv][(phase + 1) & 3]) && (--halfTrackPos[curDrv] < 0)) // head is moving in - halfTrackPos[curDrv] = 0; + if ((phasesBB[curDrv][(phase + 1) & 3]) && (--halfTrackPos[curDrv] < 0)) // head is moving in + halfTrackPos[curDrv] = 0; - if ((phasesBB[curDrv][(phase - 1) & 3]) && (++halfTrackPos[curDrv] > 140)) // head is moving out - halfTrackPos[curDrv] = 140; + if ((phasesBB[curDrv][(phase - 1) & 3]) && (++halfTrackPos[curDrv] > 140)) // head is moving out + halfTrackPos[curDrv] = 140; - phases[curDrv][phase] = true; // update track# - disk[curDrv].track = (halfTrackPos[curDrv] + 1) / 2; - disk[curDrv].nibble = 0; // not sure this is necessary ? + phases[curDrv][phase] = true; // update track# + disk[curDrv].track = (halfTrackPos[curDrv] + 1) / 2; } -void setDrv(bool drv){ - disk[drv].motorOn = disk[!drv].motorOn || disk[drv].motorOn; // if any of the motors were ON - disk[!drv].motorOn = false; // motor of the other drive is set to OFF - curDrv = drv; // set the current drive +inline void setDrv(int drv) { + disk[drv].motorOn = disk[!drv].motorOn || disk[drv].motorOn; // if any of the motors were ON + disk[!drv].motorOn = false; // motor of the other drive is set to OFF + curDrv = drv; // set the current drive } + //========================================== MEMORY MAPPED SOFT SWITCHES HANDLER -// this function is called from readMem and writeMem in puce6502 -// it complements both functions when address 1is between 0xC000 and 0xCFFF -uint8_t softSwitches(uint16_t address, uint8_t value, bool WRT){ - static uint8_t dLatch = 0; // disk ][ I/O register +// this function is called from readMem and writeMem +// it complements both functions when address is in page $C0 +uint8_t softSwitches(uint16_t address, uint8_t value, bool WRT) { + static uint8_t dLatch = 0; // disk ][ I/O register - switch (address){ - case 0xC000: return(KBD); // KEYBOARD - case 0xC010: KBD &= 0x7F; return(KBD); // KBDSTROBE + switch (address) { + case 0xC000: return KBD; // KEYBOARD + case 0xC010: KBD &= 0x7F; return KBD; // KBDSTROBE - case 0xC020: // TAPEOUT (shall we listen it ? - try SAVE from applesoft) - case 0xC030: // SPEAKER - case 0xC033: playSound(); break; // apple invader uses $C033 to output sound ! + case 0xC020: // TAPEOUT (shall we listen it ? - try SAVE from applesoft) + case 0xC030: // SPEAKER + case 0xC033: playSound(); break; // apple invader uses $C033 to output sound ! - case 0xC050: TEXT = false; break; // Graphics - case 0xC051: TEXT = true; break; // Text - case 0xC052: MIXED = false; break; // Mixed off - case 0xC053: MIXED = true; break; // Mixed on - case 0xC054: PAGE = 1; break; // Page 1 - case 0xC055: PAGE = 2; break; // Page 2 - case 0xC056: HIRES = false; break; // HiRes off - case 0xC057: HIRES = true; break; // HiRes on + case 0xC050: TEXT = false; break; // Graphics + case 0xC051: TEXT = true; break; // Text + case 0xC052: MIXED = false; break; // Mixed off + case 0xC053: MIXED = true; break; // Mixed on + case 0xC054: PAGE2 = false; break; // PAGE2 off + case 0xC055: PAGE2 = true; break; // PAGE2 on + case 0xC056: HIRES = false; break; // HiRes off + case 0xC057: HIRES = true; break; // HiRes on - case 0xC061: return(PB0); // Push Button 0 - case 0xC062: return(PB1); // Push Button 1 - case 0xC063: return(PB2); // Push Button 2 - case 0xC064: return(readPaddle(0)); // Paddle 0 - case 0xC065: return(readPaddle(1)); // Paddle 1 - case 0xC066: return(readPaddle(0)); // Paddle 2 -- not implemented - case 0xC067: return(readPaddle(1)); // Paddle 3 -- not implemented + case 0xC061: return PB0; // Push Button 0 + case 0xC062: return PB1; // Push Button 1 + case 0xC063: return PB2; // Push Button 2 + case 0xC064: return readPaddle(0); // Paddle 0 + case 0xC065: return readPaddle(1); // Paddle 1 - case 0xC070: resetPaddles(); break; // paddle timer RST - - case 0xC0E0 ... 0xC0E7: stepMotor(address); break; // MOVE DRIVE HEAD - - case 0xCFFF: - case 0xC0E8: disk[curDrv].motorOn = false; break; // MOTOROFF - case 0xC0E9: disk[curDrv].motorOn = true; break; // MOTORON - - case 0xC0EA: setDrv(0); break; // DRIVE0EN - case 0xC0EB: setDrv(1); break; // DRIVE1EN - - case 0xC0EC: // Shift Data Latch - if (disk[curDrv].writeMode) // writting - disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble]=dLatch; - else // reading - dLatch=disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble]; - disk[curDrv].nibble = (disk[curDrv].nibble + 1) % 0x1A00; // turn floppy of 1 nibble - return(dLatch); - - case 0xC0ED: dLatch = value; break; // Load Data Latch - - case 0xC0EE: // latch for READ - disk[curDrv].writeMode = false; - return(disk[curDrv].readOnly ? 0x80 : 0); // check protection - - case 0xC0EF: disk[curDrv].writeMode = true; break; // latch for WRITE + case 0xC070: resetPaddles(); break; // paddle timer RST case 0xC080: // LANGUAGE CARD : - case 0xC084: LCBK2 = 1; LCRD = 1; LCWR = 0; LCWFF = 0; break; // LC2RD - case 0xC081: - case 0xC085: LCBK2 = 1; LCRD = 0; LCWR|=LCWFF; LCWFF = !WRT; break; // LC2WR - case 0xC082: - case 0xC086: LCBK2 = 1; LCRD = 0; LCWR = 0; LCWFF = 0; break; // ROMONLY2 - case 0xC083: - case 0xC087: LCBK2 = 1; LCRD = 1; LCWR|=LCWFF; LCWFF = !WRT; break; // LC2RW - case 0xC088: - case 0xC08C: LCBK2 = 0; LCRD = 1; LCWR = 0; LCWFF = 0; break; // LC1RD - case 0xC089: - case 0xC08D: LCBK2 = 0; LCRD = 0; LCWR|=LCWFF; LCWFF = !WRT; break; // LC1WR - case 0xC08A: - case 0xC08E: LCBK2 = 0; LCRD = 0; LCWR = 0; LCWFF = 0; break; // ROMONLY1 - case 0xC08B: - case 0xC08F: LCBK2 = 0; LCRD = 1; LCWR|=LCWFF; LCWFF = !WRT; break; // LC1RW - } - return(ticks%256); // catch all + case 0xC084: LCBK2 = 1; LCRD = 1; LCWR = 0; LCWFF = 0; break; // LC2RD + case 0xC081: + case 0xC085: LCBK2 = 1; LCRD = 0; LCWR |= LCWFF; LCWFF = !WRT; break; // LC2WR + case 0xC082: + case 0xC086: LCBK2 = 1; LCRD = 0; LCWR = 0; LCWFF = 0; break; // ROMONLY2 + case 0xC083: + case 0xC087: LCBK2 = 1; LCRD = 1; LCWR |= LCWFF; LCWFF = !WRT; break; // LC2RW + case 0xC088: + case 0xC08C: LCBK2 = 0; LCRD = 1; LCWR = 0; LCWFF = 0; break; // LC1RD + case 0xC089: + case 0xC08D: LCBK2 = 0; LCRD = 0; LCWR |= LCWFF; LCWFF = !WRT; break; // LC1WR + case 0xC08A: + case 0xC08E: LCBK2 = 0; LCRD = 0; LCWR = 0; LCWFF = 0; break; // ROMONLY1 + case 0xC08B: + case 0xC08F: LCBK2 = 0; LCRD = 1; LCWR |= LCWFF; LCWFF = !WRT; break; // LC1RW + + case 0xC0E0: + case 0xC0E1: + case 0xC0E2: + case 0xC0E3: + case 0xC0E4: + case 0xC0E5: + case 0xC0E6: + case 0xC0E7: stepMotor(address); break; // MOVE DRIVE HEAD + + case 0xCFFF: + case 0xC0E8: disk[curDrv].motorOn = false; break; // MOTOROFF + case 0xC0E9: disk[curDrv].motorOn = true; break; // MOTORON + + case 0xC0EA: setDrv(0); break; // DRIVE0EN + case 0xC0EB: setDrv(1); break; // DRIVE1EN + + case 0xC0EC: // Shift Data Latch + if (disk[curDrv].writeMode) // writting + disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble]=dLatch;// good luck gcc + else // reading + dLatch=disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble];// easy peasy + disk[curDrv].nibble = (disk[curDrv].nibble + 1) % 0x1A00; // turn floppy of 1 nibble + return dLatch; + + case 0xC0ED: dLatch = value; break; // Load Data Latch + + case 0xC0EE: // latch for READ + disk[curDrv].writeMode = false; + return disk[curDrv].readOnly ? 0x80 : 0; // check protection + + case 0xC0EF: disk[curDrv].writeMode = true; break; // latch for WRITE + } + return ticks % 0xFF; // catch all, gives a 'floating' value +} + + +//======================================================================= MEMORY +// these two functions are imported into puce6502.c + +uint8_t readMem(uint16_t address) { + if (address < RAMSIZE) + return ram[address]; // RAM + + if (address >= ROMSTART) { + if (!LCRD) + return rom[address - ROMSTART]; // ROM + + if (LCBK2 && (address < 0xE000)) + return bk2[address - BK2START]; // BK2 + + return lgc[address - LGCSTART]; // LC + } + + if ((address & 0xFF00) == SL6START) + return sl6[address - SL6START]; // disk][ + + if ((address & 0xF000) == 0xC000) + return softSwitches(address, 0, false); // Soft Switches + + return ticks & 0xFF; // catch all, gives a 'floating' value +} + + +void writeMem(uint16_t address, uint8_t value) { + if (address < RAMSIZE) { + ram[address] = value; // RAM + return; + } + + if (LCWR && (address >= ROMSTART)) { + if (LCBK2 && (address < 0xE000)) { + bk2[address - BK2START] = value; // BK2 + return; + } + lgc[address - LGCSTART] = value; // LC + return; + } + + if ((address & 0xF000) == 0xC000) { + softSwitches(address, value, true); // Soft Switches + return; + } } //========================================================== PROGRAM ENTRY POINT -int main(int argc, char *argv[]){ - - char workDir[1000]; // find the working directory - int workDirSize = 0, i = 0; - while (argv[0][i] != '\0'){ - workDir[i] = argv[0][i]; - if (argv[0][++i] == '\\') workDirSize = i + 1; // find the last '/' if any - } - - // SDL INITIALIZATION - - double fps = 60, frameTime = 0, frameDelay = 1000.0 / 60.0; // targeting 60 FPS - Uint32 frameStart = 0, frame = 0; - int zoom = 2; - uint8_t tries = 0; // disk ][ speed-up - SDL_Event event; - SDL_bool paused = false, running = true, ctrl, shift, alt; - - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0){ - printf("failed to initialize SDL2 : %s", SDL_GetError()); - return(-1); - } - - SDL_EventState(SDL_DROPFILE, SDL_ENABLE); // ask SDL2 to read dropfile events - - SDL_Window *wdo = SDL_CreateWindow("reinette II+", SDL_WINDOWPOS_CENTERED, \ - SDL_WINDOWPOS_CENTERED, 280*zoom, 192*zoom, SDL_WINDOW_OPENGL); - SDL_Surface *sshot; // used later for the screenshots - SDL_Renderer *rdr = SDL_CreateRenderer(wdo, -1, SDL_RENDERER_ACCELERATED); // | SDL_RENDERER_PRESENTVSYNC); - SDL_SetRenderDrawBlendMode(rdr, SDL_BLENDMODE_NONE); // SDL_BLENDMODE_BLEND); - SDL_RenderSetScale(rdr, zoom, zoom); - - // SDL AUDIO INITIALIZATION - - SDL_AudioSpec desired = {96000, AUDIO_S8, 1, 0, 4096, 0, 0, NULL, NULL}; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, SDL_FALSE); // get the audio device ID - SDL_PauseAudioDevice(audioDevice, muted); // unmute it (muted is false) - uint8_t volume = 2; - - for (int i=1; i 1) insertFloppy(wdo, argv[1], 0); // load .nib in parameter into drive 0 - - for (int i=0; ipixels, sshot->pitch); - workDir[workDirSize]=0; - int i = -1, a = 0, b = 0; - while (disk[0].filename[++i] != '\0'){ - if (disk[0].filename[i] == '\\') a = i; - if (disk[0].filename[i] == '.') b = i; - } - strncat(workDir, "screenshots", 13); - if (a != b) strncat(workDir, disk[0].filename + a, b - a); - else strncat(workDir,"\\no disk", 10); - strncat(workDir, ".bmp", 5); - SDL_SaveBMP(sshot, workDir); - SDL_FreeSurface(sshot); - break; - } - - case SDLK_F3: // PASTE text from clipboard - if (SDL_HasClipboardText()){ - char *clipboardText = SDL_GetClipboardText(); - int c = 0; - while (clipboardText[c]){ // all chars until ascii NUL - KBD = clipboardText[c++] | 0x80; // set bit7 - if (KBD == 0x8A) KBD = 0x8D; // translate Line Feed to Carriage Ret - puce6502Exec(400000); // give cpu (and applesoft) some cycles to process each char - } - SDL_free(clipboardText); // release the ressource - } - break; - - case SDLK_F4: // VOLUME - if (shift && (volume < 120)) volume++; // increase volume - if (ctrl && (volume > 0)) volume--; // decrease volume - if (!ctrl && !shift) muted = !muted; // toggle mute / unmute - for (int i=1; i 1 )) GCReleaseSpeed -= 2; // decrease Release Speed - if (!ctrl && !shift) GCReleaseSpeed = 8; // reset Release Speed to 8 - break; - - case SDLK_F6: // JOYSTICK Action Speed - if (shift && (GCActionSpeed < 127)) GCActionSpeed += 2; // increase Action Speed - if (ctrl && (GCActionSpeed > 1 )) GCActionSpeed -= 2; // decrease Action Speed - if (!ctrl && !shift) GCActionSpeed = 8; // reset Action Speed to 8 - break; - - case SDLK_F7: // ZOOM - if (shift && (zoom < 8)) zoom++; // zoom in - if (ctrl && (zoom > 1)) zoom--; // zoom out - if (!ctrl && !shift) zoom = 2; // reset zoom to 2 - SDL_SetWindowSize(wdo, 280 * zoom, 192 * zoom); // update window size - SDL_RenderSetScale(rdr, zoom, zoom); // update renderer size - break; - - case SDLK_F8: monochrome = !monochrome; break; // toggle monochrome for HGR mode - case SDLK_F9: paused = !paused; break; // toggle pause - case SDLK_F10: puce6502Break(); break; // simulate a break - case SDLK_F11: puce6502Reset(); break; // reset - - case SDLK_F12: // help box - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", - "~ reinette II plus v0.4b ~ \ - \n\nctrl F1\twrites the changes of the floppy in drive 0 \ - \nalt F1\twrites the changes of the floppy in drive 1 \ - \n\nF2\tsave a screenshot into the screenshots directory \ - \nF3\tpaste text from clipboard \ - \n\nF4\tmute / un-mute sound \ - \nshift F4\tincrease volume \ - \nctrl F4\tdecrease volume \ - \n\nF5\treset joystick release speed \ - \nshift F5\tincrease joystick release speed \ - \ncrtl F5\tdecrease joystick release speed \ - \n\nF6\treset joystick action speed \ - \nshift F6\tincrease joystick action speed \ - \ncrtl F6\tdecrease joystick action speed \ - \n\nF7\treset the zoom to 2:1 \ - \nshift F7\tincrease zoom up to 8:1 max \ - \nctrl F7\tdecrease zoom down to 1:1 pixels \ - \nF8\tmonochrome / color display (only in HGR) \ - \nF9\tpause / un-pause the emulator \ - \n\nF10\tload binary file into $8000 \ - \nF11\treset \ - \nctrl F11\tbreak \ - \n\nF12\tthis help \ - \n\nmore information at github.com/ArthurFerreira2", NULL); - break; - - // EMULATED KEYS : - - case SDLK_a: KBD = ctrl ? 0x81: 0xC1; break; // a - case SDLK_b: KBD = ctrl ? 0x82: 0xC2; break; // b STX - case SDLK_c: KBD = ctrl ? 0x83: 0xC3; break; // c ETX - case SDLK_d: KBD = ctrl ? 0x84: 0xC4; break; // d EOT - case SDLK_e: KBD = ctrl ? 0x85: 0xC5; break; // e - case SDLK_f: KBD = ctrl ? 0x86: 0xC6; break; // f ACK - case SDLK_g: KBD = ctrl ? 0x87: 0xC7; break; // g BELL - case SDLK_h: KBD = ctrl ? 0x88: 0xC8; break; // h BS - case SDLK_i: KBD = ctrl ? 0x89: 0xC9; break; // i HTAB - case SDLK_j: KBD = ctrl ? 0x8A: 0xCA; break; // j LF - case SDLK_k: KBD = ctrl ? 0x8B: 0xCB; break; // k VTAB - case SDLK_l: KBD = ctrl ? 0x8C: 0xCC; break; // l FF - case SDLK_m: KBD = ctrl ? shift ? 0x9D: 0x8D: 0xCD; break; // m CR ] - case SDLK_n: KBD = ctrl ? shift ? 0x9E: 0x8E: 0xCE; break; // n ^ - case SDLK_o: KBD = ctrl ? 0x8F: 0xCF; break; // o - case SDLK_p: KBD = ctrl ? shift ? 0x80: 0x90: 0xD0; break; // p @ - case SDLK_q: KBD = ctrl ? 0x91: 0xD1; break; // q - case SDLK_r: KBD = ctrl ? 0x92: 0xD2; break; // r - case SDLK_s: KBD = ctrl ? 0x93: 0xD3; break; // s ESC - case SDLK_t: KBD = ctrl ? 0x94: 0xD4; break; // t - case SDLK_u: KBD = ctrl ? 0x95: 0xD5; break; // u NAK - case SDLK_v: KBD = ctrl ? 0x96: 0xD6; break; // v - case SDLK_w: KBD = ctrl ? 0x97: 0xD7; break; // w - case SDLK_x: KBD = ctrl ? 0x98: 0xD8; break; // x CANCEL - case SDLK_y: KBD = ctrl ? 0x99: 0xD9; break; // y - case SDLK_z: KBD = ctrl ? 0x9A: 0xDA; break; // z - case SDLK_LEFTBRACKET: KBD = ctrl ? 0x9B: 0xDB; break; // [ { - case SDLK_BACKSLASH: KBD = ctrl ? 0x9C: 0xDC; break; // \ | - case SDLK_RIGHTBRACKET: KBD = ctrl ? 0x9D: 0xDD; break; // ] } - case SDLK_BACKSPACE: KBD = ctrl ? 0xDF: 0x88; break; // BS - case SDLK_0: KBD = shift? 0xA9: 0xB0; break; // 0 ) - case SDLK_1: KBD = shift? 0xA1: 0xB1; break; // 1 ! - case SDLK_2: KBD = shift? 0xC0: 0xB2; break; // 2 - case SDLK_3: KBD = shift? 0xA3: 0xB3; break; // 3 # - case SDLK_4: KBD = shift? 0xA4: 0xB4; break; // 4 $ - case SDLK_5: KBD = shift? 0xA5: 0xB5; break; // 5 % - case SDLK_6: KBD = shift? 0xDE: 0xB6; break; // 6 ^ - case SDLK_7: KBD = shift? 0xA6: 0xB7; break; // 7 & - case SDLK_8: KBD = shift? 0xAA: 0xB8; break; // 8 * - case SDLK_9: KBD = shift? 0xA8: 0xB9; break; // 9 ( - case SDLK_QUOTE: KBD = shift? 0xA2: 0xA7; break; // ' " - case SDLK_EQUALS: KBD = shift? 0xAB: 0xBD; break; // = + - case SDLK_SEMICOLON: KBD = shift? 0xBA: 0xBB; break; // ; : - case SDLK_COMMA: KBD = shift? 0xBC: 0xAC; break; // , < - case SDLK_PERIOD: KBD = shift? 0xBE: 0xAE; break; // . > - case SDLK_SLASH: KBD = shift? 0xBF: 0xAF; break; // / ? - case SDLK_MINUS: KBD = shift? 0xDF: 0xAD; break; // - _ - case SDLK_BACKQUOTE: KBD = shift? 0xFE: 0xE0; break; // ` ~ - case SDLK_LEFT: KBD = 0x88; break; // BS - case SDLK_RIGHT: KBD = 0x95; break; // NAK - case SDLK_SPACE: KBD = 0xA0; break; - case SDLK_ESCAPE: KBD = 0x9B; break; // ESC - case SDLK_RETURN: KBD = 0x8D; break; // CR - - // EMULATED JOYSTICK : - - case SDLK_KP_1: GCD[0] = -1; GCA[0] = 1; break; // pdl0 <- - case SDLK_KP_3: GCD[0] = 1; GCA[0] = 1; break; // pdl0 -> - case SDLK_KP_5: GCD[1] = -1; GCA[1] = 1; break; // pdl1 <- - case SDLK_KP_2: GCD[1] = 1; GCA[1] = 1; break; // pdl1 -> - } - - if (event.type == SDL_KEYUP) - switch (event.key.keysym.sym){ - case SDLK_KP_1: GCD[0] = 1; GCA[0] = 0; break; // pdl0 -> - case SDLK_KP_3: GCD[0] = -1; GCA[0] = 0; break; // pdl0 <- - case SDLK_KP_5: GCD[1] = 1; GCA[1] = 0; break; // pdl1 -> - case SDLK_KP_2: GCD[1] = -1; GCA[1] = 0; break; // pdl1 <- - } - } - - for (int pdl=0; pdl<2; pdl++){ // update the two paddles positions - if (GCA[pdl]){ // actively pushing the stick - GCP[pdl] += GCD[pdl] * GCActionSpeed; - if (GCP[pdl] > 255) GCP[pdl] = 255; - if (GCP[pdl] < 0 ) GCP[pdl] = 0; - } - else { // the stick is return back to center - GCP[pdl] += GCD[pdl] * GCReleaseSpeed; - if (GCD[pdl] == 1 && GCP[pdl] > 127) GCP[pdl] = 127; - if (GCD[pdl] == -1 && GCP[pdl] < 127) GCP[pdl] = 127; - } - } - - //============================================================= VIDEO OUTPUT - - // HIGH RES GRAPHICS - - if (!TEXT && HIRES){ - uint16_t word; - uint8_t bits[16], bit, pbit, colorSet, even; - vRamBase = PAGE * 0x2000; // PAGE is 1 or 2 - lineLimit = MIXED ? 160 : 192; - - for (int line=0; line> bit) & 1; - colorSet = bits[7] * 4; // select the right color set - pbit = previousBit[line][col]; // the bit value of the left dot - bit = 0; // starting at 1st bit of 1st byte - - while (bit < 15){ // until we reach bit7 of 2nd byte - if (bit == 7){ // moving into the second byte - colorSet = bits[15] * 4; // update the color set - bit++; // skip bit 7 - } - if (monochrome) - colorIdx = bits[bit] * 3; // black if bit==0, white if bit==1 - else - colorIdx = even + colorSet + (bits[bit] << 1) + (pbit); - SDL_SetRenderDrawColor(rdr, hcolor[colorIdx][0], \ - hcolor[colorIdx][1], hcolor[colorIdx][2], SDL_ALPHA_OPAQUE); - SDL_RenderDrawPoint(rdr, x++, line); - pbit = bits[bit++]; // proceed to the next pixel - even = even ? 0 : 8; // one pixel every two is darker - } - - previousDots[line][col] = word; // update the video cache - if ((col < 37) && (previousBit[line][col + 2] != pbit)){ // check color franging effect on the dot after - previousBit[line][col + 2] = pbit; // set pbit and clear the - previousDots[line][col + 2] = -1; // video cache for next dot - } - } // if (previousDots[line][col] ... - } - } - } - - // lOW RES GRAPHICS - - else if (!TEXT){ // and not in HIRES - vRamBase = PAGE * 0x0400; - lineLimit = MIXED ? 20 : 24; - - for (int col=0; col<40; col++){ // for each column - pixelGR.x = col * 7; - for (int line=0; line> 4; // second nibble - SDL_SetRenderDrawColor(rdr, color[colorIdx][0], \ - color[colorIdx][1], color[colorIdx][2], SDL_ALPHA_OPAQUE); - SDL_RenderFillRect(rdr, &pixelGR); - } - } - } - - // TEXT 40 COLUMNS - - if (TEXT || MIXED){ - vRamBase = PAGE * 0x0400; - lineLimit = TEXT ? 0 : 20; - - for (int col=0; col<40; col++){ // for each column - dstRect.x = col * 7; - for (int line=lineLimit; line<24; line++){ // for each row - dstRect.y = line * 8; - - glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory - - if (glyph > 0x7F) glyphAttr = A_NORMAL; // is NORMAL ? - else if (glyph < 0x40) glyphAttr = A_INVERSE; // is INVERSE ? - else glyphAttr = A_FLASH; // it's FLASH ! - - glyph &= 0x7F; // unset bit 7 - - if (glyph > 0x5F) glyph &= 0x3F; // shifts to match - if (glyph < 0x20) glyph |= 0x40; // the ASCII codes - - if (glyphAttr == A_NORMAL || (glyphAttr == A_FLASH && frame < 15)) - SDL_RenderCopy(rdr, normCharTexture, &charRects[glyph], &dstRect); - else - SDL_RenderCopy(rdr, revCharTexture, &charRects[glyph], &dstRect); - } - } - } - - //====================================================== DISPLAY DISK STATUS - - if (disk[curDrv].motorOn){ // drive is active - if (disk[curDrv].writeMode) - SDL_SetRenderDrawColor(rdr, 255, 0, 0, 85); // red for writes - else - SDL_SetRenderDrawColor(rdr, 0, 255, 0, 85); // green for reads - - SDL_RenderFillRect(rdr, &drvRect[curDrv]); // square actually - } - - //========================================================= SDL RENDER FRAME - - if (++frame > 30) frame = 0; - - frameTime = SDL_GetTicks() - frameStart; // frame duration - if (frameTime < frameDelay){ // do we have time ? - SDL_Delay(frameDelay - frameTime); // wait 'vsync' - SDL_RenderPresent(rdr); // swap buffers - frameTime = SDL_GetTicks() - frameStart; // update frameTime - } // else skip the frame - fps = 1000.0 / frameTime; // calculate the actual frame rate - - } // while (running) - - //================================================ RELEASE RESSOURSES AND EXIT - - SDL_AudioQuit(); - SDL_Quit(); - return(0); +int main(int argc, char *argv[]) { + + + //========================================================= SDL INITIALIZATION + + int zoom = 2; + uint8_t tries = 0; // for disk ][ speed-up access + SDL_Event event; + SDL_bool running = true, paused = false, ctrl = false, shift = false, alt = false; + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { + printf("failed to initialize SDL2 : %s", SDL_GetError()); + return -1; + } + + SDL_Window *wdo = SDL_CreateWindow("Reinette ][+", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 280 * zoom, 192 * zoom, SDL_WINDOW_OPENGL); + SDL_Renderer *rdr = SDL_CreateRenderer(wdo, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + SDL_SetRenderDrawBlendMode(rdr, SDL_BLENDMODE_NONE); // SDL_BLENDMODE_BLEND); + SDL_EventState(SDL_DROPFILE, SDL_ENABLE); // ask SDL2 to read dropfile events + SDL_RenderSetScale(rdr, zoom, zoom); + SDL_Surface *sshot; // used later for the screenshots + + + //=================================================== SDL AUDIO INITIALIZATION + + SDL_AudioSpec desired = { 96000, AUDIO_S8, 1, 0, 4096, 0, 0, NULL, NULL }; + audioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, SDL_FALSE); // get the audio device ID + SDL_PauseAudioDevice(audioDevice, muted); // unmute it (muted is false) + uint8_t volume = 4; + + for (int i = 0; i < audioBufferSize; i++) { // two audio buffers, + audioBuffer[true][i] = volume; // one used when SPKR is true + audioBuffer[false][i] = -volume; // the other when SPKR is false + } + + + //===================================== VARIABLES USED IN THE VIDEO PRODUCTION + + int TextCache[24][40] = { 0 }; + int LoResCache[24][40] = { 0 }; + int HiResCache[192][40] = { 0 }; // check which Hi-Res 7 dots needs redraw + uint8_t previousBit[192][40] = { 0 }; // the last bit value of the byte before. + + enum characterAttribute { A_NORMAL, A_INVERSE, A_FLASH } glyphAttr; // character attribute in TEXT + uint8_t flashCycle = 0; // TEXT cursor flashes at 2Hz + + SDL_Rect drvRect[2] = { { 272, 188, 4, 4 }, { 276, 188, 4, 4 } }; // disk drive status squares + SDL_Rect pixelGR = { 0, 0, 7, 4 }; // a block in LoRes + SDL_Rect dstRect = { 0, 0, 7, 8 }; // the dst character in rdr + SDL_Rect charRects[128]; // the src from the norm and rev textures + for (int c = 0; c < 128; c++) { // index of the array = ascii code + charRects[c].x = 7 * c; + charRects[c].y = 0; + charRects[c].w = 7; + charRects[c].h = 8; + } + + const int color[16][3] = { // the 16 low res colors + { 0, 0, 0 }, { 226, 57, 86 }, { 28, 116, 205 }, { 126, 110, 173 }, + { 31, 129, 128 }, { 137, 130, 122 }, { 86, 168, 228 }, { 144, 178, 223 }, + { 151, 88, 34 }, { 234, 108, 21 }, { 158, 151, 143 }, { 255, 206, 240 }, + { 144, 192, 49 }, { 255, 253, 166 }, { 159, 210, 213 }, { 255, 255, 255 } + }; + + const int hcolor[16][3] = { // the high res colors (2 light levels) + { 0, 0, 0 }, { 144, 192, 49 }, { 126, 110, 173 }, { 255, 255, 255 }, + { 0, 0, 0 }, { 234, 108, 21 }, { 86, 168, 228 }, { 255, 255, 255 }, + { 0, 0, 0 }, { 63, 55, 86 }, { 72, 96, 25 }, { 255, 255, 255 }, + { 0, 0, 0 }, { 43, 84, 114 }, { 117, 54, 10 }, { 255, 255, 255 } + }; + + const int offsetGR[24] = { // helper for TEXT and GR video generation + 0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380, // lines 0-7 + 0x0028, 0x00A8, 0x0128, 0x01A8, 0x0228, 0x02A8, 0x0328, 0x03A8, // lines 8-15 + 0x0050, 0x00D0, 0x0150, 0x01D0, 0x0250, 0x02D0, 0x0350, 0x03D0 // lines 16-23 + }; + + const int offsetHGR[192] = { // helper for HGR video generation + 0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, // lines 0-7 + 0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80, // lines 8-15 + 0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00, // lines 16-23 + 0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80, + 0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00, + 0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80, + 0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00, + 0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80, + 0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28, + 0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8, + 0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28, + 0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8, + 0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28, + 0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8, + 0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28, + 0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8, + 0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50, + 0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0, + 0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50, + 0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0, + 0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50, + 0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0, // lines 168-183 + 0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50, // lines 176-183 + 0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0 // lines 184-191 + }; + + + //================================= LOAD NORMAL AND REVERSE CHARACTERS BITMAPS + + char workDir[1000]; // find the working directory + int workDirSize = 0, i = 0; + while (argv[0][i] != '\0') { + workDir[i] = argv[0][i]; + if (argv[0][++i] == '\\') workDirSize = i + 1; // find the last '/' if any + } + + SDL_Surface *tmpSurface; + workDir[workDirSize] = 0; + tmpSurface = SDL_LoadBMP(strncat(workDir, "assets/font-normal.bmp", 23)); // load the normal font + SDL_Texture *normCharTexture = SDL_CreateTextureFromSurface(rdr, tmpSurface); + SDL_FreeSurface(tmpSurface); + + workDir[workDirSize] = 0; + tmpSurface = SDL_LoadBMP(strncat(workDir, "assets/font-reverse.bmp", 24)); // load the reverse font + SDL_Texture *revCharTexture = SDL_CreateTextureFromSurface(rdr, tmpSurface); + SDL_FreeSurface(tmpSurface); + + + //================================================================== LOAD ROMS + + workDir[workDirSize] = 0; + FILE *f = fopen(strncat(workDir, "rom/appleII+.rom", 17), "rb"); // load the Apple II+ ROM + if (!f) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", "Could not locate appleII+.rom in the rom folder", NULL); + return 1; // exit + } + if (fread(rom, 1, ROMSIZE, f) != ROMSIZE) { // the file is too small + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", "appleII+.rom should be exactly 12 KB", NULL); + return 1; // exit + } + fclose(f); + + workDir[workDirSize] = 0; + f = fopen(strncat(workDir, "rom/diskII.rom", 15), "rb"); // load the P5A disk ][ PROM + if (!f) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", "Could not locate diskII.rom in the rom folder", NULL); + return 1; // exit + } + if (fread(sl6, 1, 256, f) != 256) { // file too small + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", "diskII.rom should be exactly 256 bytes", NULL); + return 1; // exit + } + fclose(f); + + + //========================================================== VM INITIALIZATION + + if (argc > 1) insertFloppy(wdo, argv[1], 0); // load floppy if provided at command line + + // reset the CPU + puce6502RST(); // reset the 6502 + + // dirty hack, fix soon... if I understand why + ram[0x4D] = 0xAA; // Joust crashes if this memory location equals zero + ram[0xD0] = 0xAA; // Planetoids won't work if this memory location equals zero + + + //================================================================== MAIN LOOP + + while (running) { + + if (!paused) { // the apple II is clocked at 1023000.0 Hhz + puce6502Exec(17050); // execute instructions for 1/60 of a second + while (disk[curDrv].motorOn && ++tries) // until motor is off or i reaches 255+1=0 + puce6502Exec(5000); // speed up drive access artificially + } + + + //=============================================================== USER INPUT + + while (SDL_PollEvent(&event)) { + alt = SDL_GetModState() & KMOD_ALT ? true : false; + ctrl = SDL_GetModState() & KMOD_CTRL ? true : false; + shift = SDL_GetModState() & KMOD_SHIFT ? true : false; + PB0 = alt ? 0xFF : 0x00; // update push button 0 + PB1 = ctrl ? 0xFF : 0x00; // update push button 1 + PB2 = shift ? 0xFF : 0x00; // update push button 2 + + if (event.type == SDL_QUIT) running = false; // WM sent TERM signal + + // if (event.type == SDL_WINDOWEVENT) { // pause if the window loses focus + // if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) + // paused = true; + // else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) + // paused = false; + // } + + if (event.type == SDL_DROPFILE) { // user dropped a file + char *filename = event.drop.file; // get full pathname + if (!insertFloppy(wdo, filename, alt)) // if ALT is pressed : drv 1 else drv 0 + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Load", "Not a valid nib file", NULL); + SDL_free(filename); // free filename memory + paused = false; // might already be the case + + if (!(alt || ctrl)) { // if ALT or CTRL were not pressed + ram[0x3F4] = 0; // unset the Power-UP byte + puce6502RST(); // do a cold reset + memset(ram, 0, sizeof(ram)); + } + } + + if (event.type == SDL_KEYDOWN) { // a key has been pressed + switch (event.key.keysym.sym) { + + // EMULATOR CONTROLS : + + case SDLK_F1: // SAVES + if (ctrl) { + if (saveFloppy(0)) + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Save", "\nDisk 1 saved back to file\n", NULL); + else + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Save", "\nTError while saving Disk 1\n", NULL); + } else if (alt) { + if (saveFloppy(1)) + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Save", "\nDisk 2 saved back to file\n", NULL); + else + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Save", "\nError while saving Disk 2\n", NULL); + } else { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Save", "CTRL-F1 to save D1\nALT-F1 to save D2\n", NULL); + } + break; + + + case SDLK_F2: { // SCREENSHOTS + sshot = SDL_GetWindowSurface(wdo); + SDL_RenderReadPixels(rdr, NULL, SDL_GetWindowPixelFormat(wdo), sshot->pixels, sshot->pitch); + workDir[workDirSize] = 0; + int i = -1, a = 0, b = 0; + while (disk[0].filename[++i] != '\0') { + if (disk[0].filename[i] == '\\') a = i; + if (disk[0].filename[i] == '.') b = i; + } + strncat(workDir, "screenshots\\", 14); + if (a != b) + strncat(workDir, disk[0].filename + a, b - a); + else + strncat(workDir, "no disk", 10); + strncat(workDir, ".bmp", 5); + SDL_SaveBMP(sshot, workDir); + SDL_FreeSurface(sshot); + } + break; + + case SDLK_F3: // PASTE text from clipboard + if (SDL_HasClipboardText()) { + char *clipboardText = SDL_GetClipboardText(); + int c = 0; + while (clipboardText[c]) { // all chars until ascii NUL + KBD = clipboardText[c++] | 0x80; // set bit7 + if (KBD == 0x8A) KBD = 0x8D; // translate Line Feed to Carriage Ret + puce6502Exec(400000); // give cpu (and applesoft) some cycles to process each char + } + SDL_free(clipboardText); // release the ressource + } + break; + + case SDLK_F4: // VOLUME + if (shift && (volume < 120)) volume++; // increase volume + if (ctrl && (volume > 0)) volume--; // decrease volume + if (!ctrl && !shift) muted = !muted; // toggle mute / unmute + for (int i = 0; i < audioBufferSize; i++) { // update the audio buffers, + audioBuffer[true][i] = volume; // one used when SPKR is true + audioBuffer[false][i] = -volume; // the other when SPKR is false + } + break; + + case SDLK_F5: // JOYSTICK Release Speed + if (shift && (GCReleaseSpeed < 127)) GCReleaseSpeed += 2; // increase Release Speed + if (ctrl && (GCReleaseSpeed > 1)) GCReleaseSpeed -= 2; // decrease Release Speed + if (!ctrl && !shift) GCReleaseSpeed = 8; // reset Release Speed to 8 + break; + + case SDLK_F6: // JOYSTICK Action Speed + if (shift && (GCActionSpeed < 127)) GCActionSpeed += 2; // increase Action Speed + if (ctrl && (GCActionSpeed > 1)) GCActionSpeed -= 2; // decrease Action Speed + if (!ctrl && !shift) GCActionSpeed = 8; // reset Action Speed to 8 + break; + + case SDLK_F7: // ZOOM + if (shift && (zoom < 8)) zoom++; // zoom in + if (ctrl && (zoom > 1)) zoom--; // zoom out + if (!ctrl && !shift) zoom = 2; // reset zoom to 2 + SDL_SetWindowSize(wdo, 280 * zoom, 192 * zoom); // update window size + SDL_RenderSetScale(rdr, zoom, zoom); // update renderer size + break; + + case SDLK_F10: paused = !paused; break; // toggle pause + + case SDLK_F11: puce6502RST(); break; // simulate a reset + + case SDLK_F12: // help box + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", + "\tReinette ][ plus v0.4.8\n" + "\n" + "ctrl F1\twrites the changes of the floppy in drive 0\n" + "alt F1\twrites the changes of the floppy in drive 1\n" + "\n" + "F2\tsave a screenshot into the screenshots directory\n" + "F3\tpaste text from clipboard\n" + "\n" + "F4\tmute / un-mute sound\n" + "shift F4\tincrease volume\n" + "ctrl F4\tdecrease volume\n" + "\n" + "F5\treset joystick release speed\n" + "shift F5\tincrease joystick release speed\n" + "crtl F5\tdecrease joystick release speed\n" + "\n" + "F6\treset joystick action speed\n" + "shift F6\tincrease joystick action speed\n" + "crtl F6\tdecrease joystick action speed\n" + "\n" + "F7\treset zoom to 2:1\n" + "shift F7\tincrease zoom up to 8:1\n" + "ctrl F7\tdecrease zoom down to 1:1\n" + "\n" + "F10\tpause / un-pause the emulator\n" + "F11\treset\n" + "\n" + "F12\tthis help\n" + "\n" + "More information at github.com/ArthurFerreira2\n", NULL); + break; + + // EMULATED KEYS : + + case SDLK_a: KBD = ctrl ? 0x81: 0xC1; break; // a + case SDLK_b: KBD = ctrl ? 0x82: 0xC2; break; // b STX + case SDLK_c: KBD = ctrl ? 0x83: 0xC3; break; // c ETX + case SDLK_d: KBD = ctrl ? 0x84: 0xC4; break; // d EOT + case SDLK_e: KBD = ctrl ? 0x85: 0xC5; break; // e + case SDLK_f: KBD = ctrl ? 0x86: 0xC6; break; // f ACK + case SDLK_g: KBD = ctrl ? 0x87: 0xC7; break; // g BELL + case SDLK_h: KBD = ctrl ? 0x88: 0xC8; break; // h BS + case SDLK_i: KBD = ctrl ? 0x89: 0xC9; break; // i HTAB + case SDLK_j: KBD = ctrl ? 0x8A: 0xCA; break; // j LF + case SDLK_k: KBD = ctrl ? 0x8B: 0xCB; break; // k VTAB + case SDLK_l: KBD = ctrl ? 0x8C: 0xCC; break; // l FF + case SDLK_m: KBD = ctrl ? shift ? 0x9D: 0x8D: 0xCD; break; // m CR ] + case SDLK_n: KBD = ctrl ? shift ? 0x9E: 0x8E: 0xCE; break; // n ^ + case SDLK_o: KBD = ctrl ? 0x8F: 0xCF; break; // o + case SDLK_p: KBD = ctrl ? shift ? 0x80: 0x90: 0xD0; break; // p @ + case SDLK_q: KBD = ctrl ? 0x91: 0xD1; break; // q + case SDLK_r: KBD = ctrl ? 0x92: 0xD2; break; // r + case SDLK_s: KBD = ctrl ? 0x93: 0xD3; break; // s ESC + case SDLK_t: KBD = ctrl ? 0x94: 0xD4; break; // t + case SDLK_u: KBD = ctrl ? 0x95: 0xD5; break; // u NAK + case SDLK_v: KBD = ctrl ? 0x96: 0xD6; break; // v + case SDLK_w: KBD = ctrl ? 0x97: 0xD7; break; // w + case SDLK_x: KBD = ctrl ? 0x98: 0xD8; break; // x CANCEL + case SDLK_y: KBD = ctrl ? 0x99: 0xD9; break; // y + case SDLK_z: KBD = ctrl ? 0x9A: 0xDA; break; // z + case SDLK_LEFTBRACKET: KBD = ctrl ? 0x9B: 0xDB; break; // [ { + case SDLK_BACKSLASH: KBD = ctrl ? 0x9C: 0xDC; break; // \ | + case SDLK_RIGHTBRACKET: KBD = ctrl ? 0x9D: 0xDD; break; // ] } + case SDLK_BACKSPACE: KBD = ctrl ? 0xDF: 0x88; break; // BS + case SDLK_0: KBD = shift? 0xA9: 0xB0; break; // 0 ) + case SDLK_1: KBD = shift? 0xA1: 0xB1; break; // 1 ! + case SDLK_2: KBD = shift? 0xC0: 0xB2; break; // 2 + case SDLK_3: KBD = shift? 0xA3: 0xB3; break; // 3 # + case SDLK_4: KBD = shift? 0xA4: 0xB4; break; // 4 $ + case SDLK_5: KBD = shift? 0xA5: 0xB5; break; // 5 % + case SDLK_6: KBD = shift? 0xDE: 0xB6; break; // 6 ^ + case SDLK_7: KBD = shift? 0xA6: 0xB7; break; // 7 & + case SDLK_8: KBD = shift? 0xAA: 0xB8; break; // 8 * + case SDLK_9: KBD = shift? 0xA8: 0xB9; break; // 9 ( + case SDLK_QUOTE: KBD = shift? 0xA2: 0xA7; break; // ' " + case SDLK_EQUALS: KBD = shift? 0xAB: 0xBD; break; // = + + case SDLK_SEMICOLON: KBD = shift? 0xBA: 0xBB; break; // ; : + case SDLK_COMMA: KBD = shift? 0xBC: 0xAC; break; // , < + case SDLK_PERIOD: KBD = shift? 0xBE: 0xAE; break; // . > + case SDLK_SLASH: KBD = shift? 0xBF: 0xAF; break; // / ? + case SDLK_MINUS: KBD = shift? 0xDF: 0xAD; break; // - _ + case SDLK_BACKQUOTE: KBD = shift? 0xFE: 0xE0; break; // ` ~ + case SDLK_LEFT: KBD = 0x88; break; // BS + case SDLK_RIGHT: KBD = 0x95; break; // NAK + case SDLK_SPACE: KBD = 0xA0; break; + case SDLK_ESCAPE: KBD = 0x9B; break; // ESC + case SDLK_RETURN: KBD = 0x8D; break; // CR + + // EMULATED JOYSTICK : + + case SDLK_KP_1: GCD[0] = -1; GCA[0] = 1; break; // pdl0 <- + case SDLK_KP_3: GCD[0] = 1; GCA[0] = 1; break; // pdl0 -> + case SDLK_KP_5: GCD[1] = -1; GCA[1] = 1; break; // pdl1 <- + case SDLK_KP_2: GCD[1] = 1; GCA[1] = 1; break; // pdl1 -> + } + } + + if (event.type == SDL_KEYUP) { + switch (event.key.keysym.sym) { + case SDLK_KP_1: GCD[0] = 1; GCA[0] = 0; break; // pdl0 -> + case SDLK_KP_3: GCD[0] = -1; GCA[0] = 0; break; // pdl0 <- + case SDLK_KP_5: GCD[1] = 1; GCA[1] = 0; break; // pdl1 -> + case SDLK_KP_2: GCD[1] = -1; GCA[1] = 0; break; // pdl1 <- + } + } + } + + for (int pdl = 0; pdl < 2; pdl++) { // update the two paddles positions + if (GCA[pdl]) { // actively pushing the stick + GCP[pdl] += GCD[pdl] * GCActionSpeed; + if (GCP[pdl] > 255) GCP[pdl] = 255; + if (GCP[pdl] < 0) GCP[pdl] = 0; + } else { // the stick is return back to center + GCP[pdl] += GCD[pdl] * GCReleaseSpeed; + if (GCD[pdl] == 1 && GCP[pdl] > 127) GCP[pdl] = 127; + if (GCD[pdl] == -1 && GCP[pdl] < 127) GCP[pdl] = 127; + } + } + + + //============================================================= VIDEO OUTPUT + + // HIGH RES GRAPHICS + if (!TEXT && HIRES) { + uint16_t word; + uint8_t bits[16], bit, pbit, colorSet, even; + uint16_t vRamBase = 0x2000 + PAGE2 * 0x2000; + uint8_t lastLine = MIXED ? 160 : 192; + uint8_t colorIdx = 0; // to index the color arrays + + for (int line = 0; line < lastLine; line++) { // for every line + for (int col = 0; col < 40; col += 2) { // for every 7 horizontal dots + int x = col * 7; + even = 0; + + word = (uint16_t)(ram[vRamBase + offsetHGR[line] + col + 1]) << 8; // store the two next bytes into 'word' + word += ram[vRamBase + offsetHGR[line] + col]; // in reverse order + + if (HiResCache[line][col] != word || !flashCycle) { // check if this group of 7 dots need a redraw + + for (bit=0; bit < 16; bit++) // store all bits 'word' into 'bits' + bits[bit] = (word >> bit) & 1; + colorSet = bits[7] * 4; // select the right color set + pbit = previousBit[line][col]; // the bit value of the left dot + bit = 0; // starting at 1st bit of 1st byte + + while (bit < 15) { // until we reach bit7 of 2nd byte + if (bit == 7) { // moving into the second byte + colorSet = bits[15] * 4; // update the color set + bit++; // skip bit 7 + } + colorIdx = even + colorSet + (bits[bit] << 1) + (pbit); + SDL_SetRenderDrawColor(rdr, hcolor[colorIdx][0], hcolor[colorIdx][1], hcolor[colorIdx][2], SDL_ALPHA_OPAQUE); + SDL_RenderDrawPoint(rdr, x++, line); + pbit = bits[bit++]; // proceed to the next pixel + even = even ? 0 : 8; // one pixel every two is darker + } + + HiResCache[line][col] = word; // update the video cache + if ((col < 37) && (previousBit[line][col + 2] != pbit)) { // check color franging effect on the dot after + previousBit[line][col + 2] = pbit; // set pbit and clear the + HiResCache[line][col + 2] = -1; // video cache for next dot + } + } // if (HiResCache[line][col] ... + } + } + } + + // lOW RES GRAPHICS + else if (!TEXT) { // and not in HIRES + uint16_t vRamBase = 0x400 + PAGE2 * 0x0400; + uint8_t lastLine = MIXED ? 20 : 24; + uint8_t glyph; // 2 blocks in GR + uint8_t colorIdx = 0; // to index the color arrays + + for (int col = 0; col < 40; col++) { // for each column + pixelGR.x = col * 7; + for (int line = 0; line < lastLine; line++) { // for each row + pixelGR.y = line * 8; // first block + + glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory + if (LoResCache[line][col] != glyph || !flashCycle) { + LoResCache[line][col] = glyph; + + colorIdx = glyph & 0x0F; // first nibble + SDL_SetRenderDrawColor(rdr, color[colorIdx][0], color[colorIdx][1], color[colorIdx][2], SDL_ALPHA_OPAQUE); + SDL_RenderFillRect(rdr, &pixelGR); + + pixelGR.y += 4; // second block + colorIdx = (glyph & 0xF0) >> 4; // second nibble + SDL_SetRenderDrawColor(rdr, color[colorIdx][0], color[colorIdx][1], color[colorIdx][2], SDL_ALPHA_OPAQUE); + SDL_RenderFillRect(rdr, &pixelGR); + } + } + } + } + + // TEXT 40 COLUMNS + if (TEXT || MIXED) { // not Full Graphics + uint16_t vRamBase = 0x400 +PAGE2 * 0x0400; + uint8_t firstLine = TEXT ? 0 : 20; + uint8_t glyph; // a TEXT character + + for (int col = 0; col < 40; col++) { // for each column + dstRect.x = col * 7; + for (int line = firstLine; line < 24; line++) { // for each row + dstRect.y = line * 8; + + glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory + if (glyph > 0x7F) glyphAttr = A_NORMAL; // is NORMAL ? + else if (glyph < 0x40) glyphAttr = A_INVERSE; // is INVERSE ? + else glyphAttr = A_FLASH; // it's FLASH ! + + if (glyphAttr==A_FLASH || TextCache[line][col]!=glyph || !flashCycle){ + TextCache[line][col] = glyph; + + glyph &= 0x7F; // unset bit 7 + if (glyph > 0x5F) glyph &= 0x3F; // shifts to match + if (glyph < 0x20) glyph |= 0x40; // the ASCII codes + + if (glyphAttr==A_NORMAL || (glyphAttr==A_FLASH && flashCycle<15)) + SDL_RenderCopy(rdr, normCharTexture, &charRects[glyph], &dstRect); + else + SDL_RenderCopy(rdr, revCharTexture, &charRects[glyph], &dstRect); + } + } + } + } + + + //====================================================== DISPLAY DISK STATUS + + if (disk[curDrv].motorOn) { // drive is active + if (disk[curDrv].writeMode) + SDL_SetRenderDrawColor(rdr, 255, 0, 0, 85); // red for writes + else + SDL_SetRenderDrawColor(rdr, 0, 255, 0, 85); // green for reads + SDL_RenderFillRect(rdr, &drvRect[curDrv]); // square actually + } + + + //========================================================= SDL RENDER FRAME + + if (++flashCycle == 30) // increase cursor flash cycle + flashCycle = 0; // reset to zero every half second + SDL_RenderPresent(rdr); // swap buffers + } // while (running) + + + //================================================ RELEASE RESSOURSES AND EXIT + + SDL_AudioQuit(); + SDL_Quit(); + return 0; } diff --git a/reinetteII+.rc b/reinetteII+.rc index fcfc903..ba87788 100644 --- a/reinetteII+.rc +++ b/reinetteII+.rc @@ -1,7 +1,7 @@ 1 ICON "assets/reinetteII+.ico" 1 VERSIONINFO -FILEVERSION 0,4,1,0 -PRODUCTVERSION 0,4,0,0 +FILEVERSION 0,4,8,0 +PRODUCTVERSION 0,4,8,0 BEGIN BLOCK "StringFileInfo" BEGIN @@ -9,12 +9,12 @@ BEGIN BEGIN VALUE "CompanyName", "Arthur Ferreira" VALUE "FileDescription", "Apple II+ emulator" - VALUE "FileVersion", "0.4.1.0" + VALUE "FileVersion", "0.4.8.0" VALUE "InternalName", "reinette II plus" VALUE "LegalCopyright", "Arthur Ferreira" VALUE "OriginalFilename", "reinetteII+.exe" VALUE "ProductName", "reinette II plus" - VALUE "ProductVersion", "0.4" + VALUE "ProductVersion", "0.4.8.0" END END BLOCK "VarFileInfo" diff --git a/reinetteII+.res b/reinetteII+.res new file mode 100644 index 0000000000000000000000000000000000000000..d480d1c75a6e4bffed1b9813cd270884300fd026 GIT binary patch literal 62766 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

7FgO$?pS_&r1kW+* zE#3thj(DWEew}j+L$}71fS_tv#{C)s83LM*MW-bGlu5Aj2D8=Vuz^wbijaaHQkW=D@6%?WCiN*F40<-1eSxalf z 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 p^q9>iH~f;oZz9nO#aKbHAU9n=gxLV@^AC