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 0000000..d480d1c Binary files /dev/null and b/reinetteII+.res differ diff --git a/screenshots/Ms. Pac-Man.bmp b/screenshots/Ms. Pac-Man.bmp new file mode 100644 index 0000000..038895b Binary files /dev/null and b/screenshots/Ms. Pac-Man.bmp differ diff --git a/screenshots/no disk.bmp b/screenshots/no disk.bmp index f73c9ac..162fe24 100644 Binary files a/screenshots/no disk.bmp and b/screenshots/no disk.bmp differ