initial commit
This commit is contained in:
parent
12d8d6d9fb
commit
f40f0b36e7
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
|||
LIBS = -lSDL2
|
||||
FLAGS = -std=c99 -pedantic -Wpedantic -Wall -Werror -O3
|
||||
# comment this line if you are under Linux
|
||||
LIBS-WIN32 = -lmingw32 -lSDL2main -Wl,-subsystem,windows
|
||||
CC = gcc
|
||||
|
||||
reinetteII+: reinetteII+.c puce6502.c
|
||||
$(CC) $^ $(FLAGS) $(LIBS-WIN32) $(LIBS) -o $@
|
||||
|
||||
all: reinetteII+
|
|
@ -0,0 +1,94 @@
|
|||
# reinette II plus
|
||||
|
||||
### reinette goes graphical !
|
||||
|
||||
![screenshots](screenshots.png)
|
||||
|
||||
After [reinette](https://github.com/ArthurFerreira2/reinette) (Apple 1 emulator) and [reinette II](https://github.com/ArthurFerreira2/reinette-II) (the text only Apple II emulator), I am proud to release reinette II plus, a french\* Apple II plus emulator using SDL2.
|
||||
|
||||
\* reinette has two meanings in french : it's a little frog but also a delicious kind of apple
|
||||
|
||||
### Featuring :
|
||||
|
||||
* all video modes in color
|
||||
* mono sound with mute/unmute
|
||||
* 48KB (no language card support)
|
||||
* paddles/joystic with trim ajustment
|
||||
* paste text from clipboard
|
||||
* disk ][ adapter with two drives (.nib files only)
|
||||
* drag and drop nib files to inset a floppy
|
||||
* save floppy changes back to host
|
||||
* zoom the window by integer increments
|
||||
|
||||
|
||||
It uses the same MOS 6502 CPU emulator as her sisters (now christened 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
|
||||
[AppleWin](https://github.com/AppleWin/AppleWin), [Epple](https://github.com/cmosher01/Epple-II) or [LinApple](https://github.com/linappleii/linapple). Better use one of them if you want a better Apple ][ experience.
|
||||
|
||||
I wrote it in the goal to better understand the Apple ][ internals, and I'm publishing the sources in the hope they will be of any help.
|
||||
|
||||
It's compact, less 800 SLOC, with two source files only, one for the CPU emulation, the other for the computer itself.
|
||||
|
||||
I did my best to comment everything, and if you have an idea of how an Apple ][ works, it should be easy for you to understand the code, modify and enhance it for your needs (see TODO section).
|
||||
|
||||
|
||||
### Startup
|
||||
|
||||
You can specify a .nib file on the command line to start the emulator with a floppy engaged in drive 1. Otherwhise, the emulator will start with no floppy (and thus wait one until you press the reset key or drag and drop a nib file)
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
Drag and drop a disk image file (.nib format only) to inset it into drive 1\
|
||||
Drop the file while pressing the ALT key to insert it into drive 2
|
||||
|
||||
Use the functions keys to control the emulator itself
|
||||
|
||||
* F1 : writes the changes of the floppy in current drive back to host
|
||||
* F5 : zoom out down to 1:1
|
||||
* F6 : zoom in, no magnification limit
|
||||
* F7/F8 : adjust joystic trim
|
||||
* F9 : mute/unmute soud
|
||||
* F10 : reset
|
||||
* F11 : break
|
||||
* F12 : quit (you can also just close the window)
|
||||
|
||||
Paddles / Joystic :
|
||||
|
||||
* numpad 1 : left
|
||||
* numpad 3 : right
|
||||
* numpad 2 : down
|
||||
* numpad 3 : up
|
||||
* CTRL : button 0
|
||||
* ALT : button 1
|
||||
|
||||
|
||||
### limitations
|
||||
|
||||
* High pitch noise at high volume (might be related with my environment)
|
||||
* Sound cracks when playing for long period (intro music for example)
|
||||
* CPU is not 100% cycle accurate - see source file for more details
|
||||
* Colors are approximate (taken from a scan of an old Beagle bros. poster)
|
||||
* HGR is inhacurate, and does not implement color clashing
|
||||
* disk ][ access is artificially accelerated
|
||||
* Only support .nib floppy images. (you can use [CiderPress](https://github.com/fadden/ciderpress) to convert your images to this format)
|
||||
* only has 48KB of RAM (can't run some software requiring the language card)
|
||||
* and many others ...
|
||||
|
||||
|
||||
### To do :
|
||||
|
||||
* warn if you quit the application with unsaved floppy changes
|
||||
* give the user the option to switch to the original Apple II rom
|
||||
* colors where taken from an old Beagle Bros poster, find more accurate RGB values.
|
||||
* implement color clashing in HGR
|
||||
* optimize sound generation
|
||||
* optimize disk access (actually a bit artificial)
|
||||
* re-implement Paddles and Joystic support for a better analog simulation
|
||||
* implement a language card to extend the RAM of reinette II plus to 64K and support more sofware.
|
||||
|
||||
|
||||
*simplicity is the ultimate sophistication*
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 958 B |
Binary file not shown.
After Width: | Height: | Size: 958 B |
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
|
||||
puce6502 - MOS 6502 cpu emulator
|
||||
Last modified 1st of August 2020
|
||||
Copyright (c) 2017 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.
|
||||
|
||||
*/
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
#define CARRY 0x01
|
||||
#define ZERO 0x02
|
||||
#define INTR 0x04
|
||||
#define DECIM 0x08
|
||||
#define BREAK 0x10
|
||||
#define UNDEF 0x20
|
||||
#define OFLOW 0x40
|
||||
#define SIGN 0x80
|
||||
|
||||
struct Operand {
|
||||
uint8_t code;
|
||||
bool setAcc;
|
||||
uint16_t value, address;
|
||||
} ope;
|
||||
|
||||
struct Register {
|
||||
uint8_t A,X,Y,SR,SP;
|
||||
uint16_t PC;
|
||||
} reg;
|
||||
|
||||
|
||||
// instruction timing :
|
||||
// http://nparker.llx.com/a2/opcodes.html
|
||||
// http://wouter.bbcmicro.net/general/6502/6502_opcodes.html
|
||||
|
||||
// IMPLEMENTED :
|
||||
// The conditional branch instructions require a second extra cycle if the
|
||||
// branch happens and crosses a page boundary.
|
||||
|
||||
// NOT IMPLEMENTED :
|
||||
// Absolute-X, absolute-Y, and Zpage-Y addressing modes need an extra cycle
|
||||
// if indexing crosses a page boundary, or if the instruction writes to memory.
|
||||
|
||||
static int cycles[256] = { // cycle count 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
|
||||
};
|
||||
|
||||
|
||||
//=============================================================== MEMORY AND I/O
|
||||
|
||||
inline static uint8_t readMem(uint16_t address){
|
||||
if (address < RAMSIZE) return(ram[address]);
|
||||
if (address >= ROMSTART) return(rom[address - ROMSTART]);
|
||||
return softSwitches(address, 0); // MEMORY MAPPED I/O
|
||||
}
|
||||
|
||||
inline static void writeMem(uint16_t address, uint8_t value){
|
||||
if (address < RAMSIZE) ram[address] = value;
|
||||
else if (address < ROMSTART) softSwitches(address, value);
|
||||
}
|
||||
|
||||
|
||||
//=============================================== STACK, SIGN AND OTHER ROUTINES
|
||||
|
||||
inline static void push(uint8_t value){
|
||||
writeMem(0x100 + reg.SP--, value);
|
||||
}
|
||||
|
||||
inline static uint8_t pull(){
|
||||
return(readMem(0x100 + ++reg.SP));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
//============================================================= ADDRESSING MODES
|
||||
|
||||
static void IMP(){ // IMPlicit
|
||||
}
|
||||
|
||||
static void ACC(){ // ACCumulator
|
||||
ope.value = reg.A;
|
||||
ope.setAcc = true;
|
||||
}
|
||||
|
||||
static void IMM(){ // IMMediate
|
||||
ope.address = reg.PC++;
|
||||
ope.value = readMem(ope.address);
|
||||
}
|
||||
|
||||
static void ZPG(){ // Zero PaGe
|
||||
ope.address = readMem(reg.PC++);
|
||||
ope.value = readMem(ope.address);
|
||||
}
|
||||
|
||||
static void ZPX(){ // Zero Page,X
|
||||
ope.address = (readMem(reg.PC++) + reg.X) & 0xFF;
|
||||
ope.value = readMem(ope.address);
|
||||
}
|
||||
|
||||
static void ZPY(){ // Zero Page,Y
|
||||
ope.address = (readMem(reg.PC++) + reg.Y) & 0xFF;
|
||||
ope.value = readMem(ope.address);
|
||||
}
|
||||
|
||||
static void REL(){ // RELative (for branch instructions)
|
||||
ope.address = readMem(reg.PC++);
|
||||
if (ope.address & 0x80) ope.address |= 0xFF00; // branch backward
|
||||
}
|
||||
|
||||
static void ABS(){ // ABSolute
|
||||
ope.address = readMem(reg.PC) | (readMem(reg.PC + 1) << 8);
|
||||
ope.value = readMem(ope.address);
|
||||
reg.PC += 2;
|
||||
}
|
||||
|
||||
static void ABX(){ // ABsolute,X
|
||||
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
|
||||
ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.Y;
|
||||
ope.value = readMem(ope.address);
|
||||
reg.PC += 2;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
//================================================================= INSTRUCTIONS
|
||||
|
||||
static void NOP(){ // NO Operation
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void CLD(){ // CLear Decimal
|
||||
reg.SR &= ~DECIM;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void SEI(){ // SEt Interrupt
|
||||
reg.SR |= INTR;
|
||||
}
|
||||
|
||||
static void CLV(){ // CLear oVerflow
|
||||
reg.SR &= ~OFLOW;
|
||||
}
|
||||
|
||||
static void LDA(){ // LoaD Accumulator
|
||||
reg.A = ope.value;
|
||||
setSZ(reg.A);
|
||||
}
|
||||
|
||||
static void LDX(){ // LoaD X
|
||||
reg.X = ope.value;
|
||||
setSZ(reg.X);
|
||||
}
|
||||
|
||||
static void LDY(){ // LoaD Y
|
||||
reg.Y = ope.value;
|
||||
setSZ(reg.Y);
|
||||
}
|
||||
|
||||
static void STA(){ // STore Accumulator
|
||||
writeMem(ope.address, reg.A);
|
||||
}
|
||||
|
||||
static void STX(){ // STore X
|
||||
writeMem(ope.address, reg.X);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
void puce6502Break() {
|
||||
BRK();
|
||||
}
|
||||
|
||||
void puce6502Goto(uint16_t address) {
|
||||
reg.PC = address;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Puce6502 - MOS 6502 cpu emulator
|
||||
Last modified 1st of August 2020
|
||||
Copyright (c) 2017 Arthur Ferreira
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef _CPU_H
|
||||
#define _CPU_H
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef enum {false, true} bool;
|
||||
|
||||
#define ROMSTART 0xD000
|
||||
#define ROMSIZE 0x3000
|
||||
#define RAMSIZE 0xC000
|
||||
|
||||
uint8_t rom[ROMSIZE];
|
||||
uint8_t ram[RAMSIZE];
|
||||
|
||||
long long int ticks;
|
||||
|
||||
void puce6502Exec(long long int cycleCount);
|
||||
void puce6502Reset();
|
||||
void puce6502Break();
|
||||
void puce6502Goto(uint16_t address);
|
||||
#endif
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
|
||||
reinette II plus, a french Apple II emulator, using SDL2
|
||||
and powered by puce6502 - a MOS 6502 cpu emulator by the same autor
|
||||
Last modified 1st of August 2020
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include "puce6502.h"
|
||||
|
||||
|
||||
//================================================================ SOFT SWITCHES
|
||||
|
||||
uint8_t TEXT = 0; // start of text (0=full text, 20=mixed or 24=full GR)
|
||||
uint8_t PAGE = 1; // 0xC054 for page1 or 0xC055 for page2
|
||||
bool HIRES = false; // 0xC056 (off: GR) or 0xC057 (on: HGR)
|
||||
|
||||
bool SPKR = false; // 0xC030 Speaker toggle
|
||||
bool MUTED = false; // not an Apple II Soft Switch, press F9 to mute/unmute
|
||||
|
||||
uint8_t KBD = 0; // 0xC000, 0xC010 ascii value of keyboard input
|
||||
|
||||
uint8_t PB0 = 0; // 0xC061 Push Button 0 (bit 7) / Open Apple
|
||||
uint8_t PB1 = 0; // 0xC062 Push Button 1 (bit 7) / Solid Apple
|
||||
|
||||
uint8_t GC0 = 0; // 0xC064 Game Controller 0 (bit 7)
|
||||
uint8_t GC1 = 0; // 0xC065 Game Controller 1 (bit 7)
|
||||
float TGC0, TGC1; // Timers for GC0 and GC1
|
||||
float trimGC = .24; // Game Controller trim use F5 and F6 to adjust it
|
||||
|
||||
|
||||
//======================================================================== AUDIO
|
||||
|
||||
#define audioBufferSize 512 // found to be large enought
|
||||
#define rate 50 // 1 Mhz / 20000 Hz (the wavSpec.freq)
|
||||
SDL_AudioDeviceID audioDevice;
|
||||
uint8_t audioBuffer[2][audioBufferSize]; // see main() for more details
|
||||
long long int lastTick = 0LL;
|
||||
|
||||
|
||||
//====================================================================== DISK ][
|
||||
|
||||
uint8_t slot6[256] = {0}; // disk ][ PROM in slot 6
|
||||
|
||||
struct drive{
|
||||
char filename[512]; // the full disk image path
|
||||
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; // current nibble under head position
|
||||
} disk[2] = {0}; // two disk ][ drive units
|
||||
|
||||
int curDrv = 0; // only one can be enabled at a time
|
||||
|
||||
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;
|
||||
|
||||
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) == 0){ // head not moving (PHASE X OFF)
|
||||
phases[curDrv][phase] = false;
|
||||
return;
|
||||
}
|
||||
// head is moving in
|
||||
if ((phasesBB[curDrv][(phase + 1) & 3]) && (--halfTrackPos[curDrv] < 0))
|
||||
halfTrackPos[curDrv] = 0;
|
||||
// head is moving out
|
||||
if ((phasesBB[curDrv][(phase - 1) & 3]) && (++halfTrackPos[curDrv] > 140))
|
||||
halfTrackPos[curDrv] = 140;
|
||||
// update track
|
||||
phases[curDrv][phase] = true;
|
||||
disk[curDrv].track = (halfTrackPos[curDrv] + 1) / 2;
|
||||
}
|
||||
|
||||
|
||||
//========================================== MEMORY MAPPED SOFT SWITCHES HANDLER
|
||||
// this function is called from readMem and writeMem in puce6502
|
||||
// it complements both functions when address is between 0xC000 and 0xCFFF
|
||||
|
||||
uint8_t softSwitches(uint16_t address, uint8_t value){
|
||||
static uint8_t dLatch = 0; // disk ][ I/O reg
|
||||
|
||||
if (address>>8 == 0xC6) return(slot6[address - 0xC600]); // disk ][ PROM
|
||||
|
||||
switch (address){
|
||||
case 0xC000: return(KBD); // keyboard
|
||||
case 0xC010: KBD &= 0x7F; return(KBD); // key strobe
|
||||
|
||||
case 0xC030: // Sound
|
||||
SPKR = !SPKR; // toggle speaker
|
||||
if (!MUTED){
|
||||
uint16_t length = (ticks - lastTick) / rate;
|
||||
if (length > audioBufferSize) length = audioBufferSize;
|
||||
ticks -= length << 2; // speed up
|
||||
lastTick = ticks;
|
||||
SDL_QueueAudio(audioDevice, audioBuffer[SPKR], length);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xC050: if (!TEXT) TEXT = 20; break; // Graphics
|
||||
case 0xC051: TEXT = 0; break; // Text
|
||||
case 0xC052: TEXT = 24; break; // Full Screen
|
||||
case 0xC053: TEXT = 20; break; // Mixed Screen
|
||||
case 0xC054: PAGE = 1; break; // Page 1
|
||||
case 0xC055: PAGE = 2; if (HIRES) TEXT = 24; break; // Page 2
|
||||
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 0xC064: return((TGC0-=trimGC) > 192? 0x80: 0x00); // Paddle 0
|
||||
case 0xC065: return((TGC1-=trimGC) > 192? 0x80: 0x00); // Paddle 1
|
||||
case 0xC070: TGC0 = GC0; TGC1 = GC1; break; // paddle timer RST
|
||||
|
||||
case 0xC0E0: // PHASE0OFF
|
||||
case 0xC0E1: // PHASE0ON
|
||||
case 0xC0E2: // PHASE1OFF
|
||||
case 0xC0E3: // PHASE1ON
|
||||
case 0xC0E4: // PHASE2OFF
|
||||
case 0xC0E5: // PHASE2ON
|
||||
case 0xC0E6: // PHASE3OFF
|
||||
case 0xC0E7: stepMotor(address); break; // PHASE3ON
|
||||
|
||||
case 0xC0E8: disk[curDrv].motorOn = false; break; // MOTOROFF
|
||||
case 0xC0E9: disk[curDrv].motorOn = true; break; // MOTORON
|
||||
|
||||
case 0xC0EA: // DRIVE0EN
|
||||
disk[0].motorOn = disk[1].motorOn || disk[0].motorOn;
|
||||
disk[1].motorOn = false;
|
||||
curDrv = 0;
|
||||
break;
|
||||
case 0xC0EB: // DRIVE1EN
|
||||
disk[1].motorOn = disk[0].motorOn || disk[1].motorOn;
|
||||
disk[0].motorOn = false;
|
||||
curDrv = 1;
|
||||
break;
|
||||
|
||||
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
|
||||
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(0); // catch all
|
||||
}
|
||||
|
||||
|
||||
//========================================================== PROGRAM ENTRY POINT
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
// VM INITIALIZATION
|
||||
FILE *f = fopen("appleII+.rom", "rb"); // load the Apple II+ ROM
|
||||
fread(rom, 1, ROMSIZE, f);
|
||||
fclose(f);
|
||||
|
||||
f=fopen("diskII.rom", "rb"); // load the disk ][ PROM
|
||||
fread(slot6, 1, 256, f);
|
||||
fclose(f);
|
||||
|
||||
if (argc == 2){ // load .nib in drive 0
|
||||
f = fopen(argv[1], "rb"); // open it in read binary
|
||||
fread(disk[0].data, 1, 232960, f);
|
||||
fclose(f);
|
||||
f = fopen(argv[1], "ab"); // check if file is writeable
|
||||
disk[0].readOnly = f ? false : true; // f will be NULL if open failed
|
||||
fclose(f);
|
||||
sprintf(disk[0].filename,"%s", argv[1]); // update disk filename
|
||||
}
|
||||
|
||||
puce6502Reset(); // reset the 6502
|
||||
|
||||
// SDL INITIALIZATION
|
||||
int zoom = 2;
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
|
||||
SDL_Window *wdo = SDL_CreateWindow("reinette II+", SDL_WINDOWPOS_CENTERED, \
|
||||
SDL_WINDOWPOS_CENTERED, 280*zoom, 192*zoom, SDL_WINDOW_OPENGL);
|
||||
|
||||
SDL_Renderer *rdr = SDL_CreateRenderer(wdo, -1, SDL_RENDERER_ACCELERATED);
|
||||
SDL_SetRenderDrawBlendMode(rdr, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderSetScale(rdr, zoom, zoom);
|
||||
|
||||
const int frameDelay = 1000/60; // targeting 60 FPS
|
||||
Uint32 frameStart = 0, frameTime = 0, frame = 0;
|
||||
SDL_Event event;
|
||||
bool running = true, ctrl, shift, alt;
|
||||
|
||||
// SDL AUDIO INITIALIZATION
|
||||
SDL_AudioSpec wavSpec = {20000, AUDIO_U8, 1, 0, 8, 0, 0, NULL, NULL};
|
||||
audioDevice = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, 0);
|
||||
SDL_PauseAudioDevice(audioDevice, MUTED);
|
||||
|
||||
// two audio buffers, one when the speaker is 'on', the other when it's 'off'
|
||||
for (int i=0; i<audioBufferSize; i++){
|
||||
audioBuffer[true][i] = 32; // when SPKR==true : 1/8 of max amplitude (255)
|
||||
audioBuffer[false][i] = 0; // when SPKR==false : silence
|
||||
}
|
||||
|
||||
// LOAD NORMAL AND REVERSE CHARACTERS BITMAPS
|
||||
SDL_Surface *tmpSurface = SDL_LoadBMP("font-normal.bmp");
|
||||
SDL_Texture *normCharTexture = SDL_CreateTextureFromSurface(rdr, tmpSurface);
|
||||
SDL_FreeSurface(tmpSurface);
|
||||
|
||||
tmpSurface = SDL_LoadBMP("font-reverse.bmp");
|
||||
SDL_Texture *revCharTexture = SDL_CreateTextureFromSurface(rdr, tmpSurface);
|
||||
SDL_FreeSurface(tmpSurface);
|
||||
|
||||
// VARIABLES USED IN THE VIDEO PRODUCTION
|
||||
uint16_t previousDots[192][40]={0}; // check which Hi-Res 7 dots needs redraw
|
||||
uint8_t glyph; // a TEXT character, or 2 blocks in GR
|
||||
uint16_t vRamBase = 0x0400; // can be 0x0400, 0x0800, 0x2000 or 0x4000
|
||||
uint8_t colorIdx = 0; // to index the color arrays
|
||||
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;
|
||||
}
|
||||
SDL_Rect drvRect[2] = { // disk drive 1 status square
|
||||
{272, 188, 4, 4}, {276, 188, 4, 4}};
|
||||
|
||||
enum characterAttribute {A_NORMAL, A_INVERSE, A_FLASH} glyphAttribute;
|
||||
|
||||
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[8][3] = { // the high res colors (2 color sets)
|
||||
{ 0, 0, 0}, {126, 110, 173}, {144, 192, 49}, {255, 255, 255},
|
||||
{ 0, 0, 0}, { 86, 168, 228}, {234, 108, 21}, {255, 255, 255}};
|
||||
|
||||
const int hhcolor[8][3] = { // the high res 2nd half pixel, darker
|
||||
{ 10, 10, 10}, {116, 100, 163}, {134, 182, 39}, {245, 245, 245},
|
||||
{ 10, 10, 10}, { 76, 158, 218}, {224, 98, 11}, {245, 245, 245}};
|
||||
|
||||
const int offsetGR[24] = { // helper for TEXT and GR video generation
|
||||
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, // lines 0-7
|
||||
0x028, 0x0A8, 0x128, 0x1A8, 0x228, 0x2A8, 0x328, 0x3A8, // lines 8-15
|
||||
0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0}; // lines 16-23
|
||||
|
||||
const int offsetHGR[192] = { // helper for HGR video generation
|
||||
0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, // 0-7
|
||||
0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80, // 8-15
|
||||
0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00, // 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, // 168-183
|
||||
0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50, // 176-183
|
||||
0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0}; // 184-191
|
||||
|
||||
|
||||
|
||||
//================================================================== MAIN LOOP
|
||||
|
||||
while (running){
|
||||
frameStart = SDL_GetTicks();
|
||||
|
||||
//================================================================== RUN CPU
|
||||
puce6502Exec(16666); // execute 1000000/60 cycles at each frame
|
||||
|
||||
//=========================================================== KEYBOARD INPUT
|
||||
while (SDL_PollEvent(&event)){
|
||||
|
||||
ctrl = SDL_GetModState() & KMOD_CTRL ? true : false;
|
||||
alt = SDL_GetModState() & KMOD_ALT ? 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
|
||||
|
||||
if (event.type == SDL_QUIT) running = false; // WM sent TERM signal
|
||||
|
||||
if (event.type == SDL_DROPFILE){ // user dropped a file
|
||||
char* filename = event.drop.file; // get full pathname
|
||||
f = fopen(filename, "rb"); // open it in read binary
|
||||
fread(disk[alt].data, 1, 232960, f); // if alt : drv 1 else drv 0
|
||||
fclose(f);
|
||||
f = fopen(filename, "ab"); // check file is writeable
|
||||
disk[alt].readOnly = f ? false : true; // f is NULL if open failed
|
||||
fclose(f);
|
||||
sprintf(disk[alt].filename,"%s", filename); // update disk filename
|
||||
SDL_free(filename); // free filename memory
|
||||
if (!alt) puce6502Goto(0xC600);
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYDOWN) // a key has been pressed
|
||||
switch (event.key.keysym.sym){
|
||||
|
||||
// EMULATOR CONTROL :
|
||||
case SDLK_INSERT: // Shift+Insert => paste txt
|
||||
if (shift && 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; // Line Feed to Carriage Ret
|
||||
puce6502Exec(400000); // to process each char
|
||||
}
|
||||
SDL_free(clipboardText);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F1: // save curDrv back to host
|
||||
if (disk[curDrv].filename[0] && !disk[curDrv].readOnly)
|
||||
if((f = fopen(disk[curDrv].filename, "wb"))){
|
||||
fwrite(disk[curDrv].data, 1, 232960, f);
|
||||
fclose(f);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F5: if ((zoom-=2) < 0) zoom = 0; // zoom out
|
||||
case SDLK_F6: if (++zoom > 8) zoom = 8; // zoom in
|
||||
SDL_SetWindowSize(wdo, 280*zoom, 192*zoom);
|
||||
SDL_RenderSetScale(rdr, zoom, zoom); break;
|
||||
|
||||
case SDLK_F7: trimGC -= .01; break; // PDL Trim
|
||||
case SDLK_F8: trimGC += .01; break; // PDL Trim
|
||||
case SDLK_F9: MUTED = !MUTED; break; // mute
|
||||
|
||||
case SDLK_F10: // reset
|
||||
puce6502Reset();
|
||||
softSwitches(0xC0E8,0); // motorOff
|
||||
softSwitches(0xC0E9,0); // drive0
|
||||
break;
|
||||
|
||||
case SDLK_F11: puce6502Break(); break; // break
|
||||
case SDLK_F12: running = false; break; // exit ...
|
||||
|
||||
// EMULATED KEYS :
|
||||
case SDLK_ESCAPE: KBD = 0x9B; break; // ESC
|
||||
case SDLK_RETURN: KBD = 0x8D; break; // CR
|
||||
case SDLK_DELETE: KBD = 0x80; break; // DEL->NUL
|
||||
case SDLK_LEFT: KBD = 0x88; break; // BS
|
||||
case SDLK_RIGHT: KBD = 0x95; break; // NAK
|
||||
case SDLK_BACKSPACE: KBD = 0x88; break; // BS
|
||||
case SDLK_SPACE: KBD = 0xA0; break;
|
||||
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 ? 0x8D: 0xCD; break; // m CR
|
||||
case SDLK_n: KBD = ctrl ? 0x8E: 0xCE; break; // n
|
||||
case SDLK_o: KBD = ctrl ? 0x8F: 0xCF; break; // o
|
||||
case SDLK_p: KBD = ctrl ? 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_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_LEFTBRACKET: KBD = shift? 0xFB: 0xDB; break; // [ {
|
||||
case SDLK_BACKSLASH: KBD = shift? 0xFC: 0xDC; break; // \ |
|
||||
case SDLK_RIGHTBRACKET: KBD = shift? 0xFD: 0xDD; break; // ] }
|
||||
case SDLK_BACKQUOTE: KBD = shift? 0xFE: 0xE0; break; // ` ~
|
||||
|
||||
case SDLK_KP_1: GC0 = 192; break; // pdl0 <-
|
||||
case SDLK_KP_3: GC0 = 255; break; // pdl0 ->
|
||||
case SDLK_KP_5: GC1 = 192; break; // pdl1 <-
|
||||
case SDLK_KP_2: GC1 = 255; break; // pdl1 ->
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYUP)
|
||||
switch (event.key.keysym.sym){
|
||||
case SDLK_KP_1: GC0 = 224; break; // reset
|
||||
case SDLK_KP_3: GC0 = 224; break; // the
|
||||
case SDLK_KP_5: GC1 = 224; break; // paddles
|
||||
case SDLK_KP_2: GC1 = 224; break; // to center
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================= VIDEO OUTPUT
|
||||
|
||||
//======================================================== HIGH RES GRAPHICS
|
||||
if (HIRES){
|
||||
vRamBase = PAGE * 0x2000; // PAGE is 1 or 2
|
||||
int word, bits[16], bit, colorShift;
|
||||
|
||||
for (int line=0; line<TEXT*8; line++){ // for every line
|
||||
int y = line;
|
||||
for (int col=0; col<40; col += 2){ // for every 7 horizontal dots
|
||||
int x = col * 7;
|
||||
|
||||
// put the two next bytes into one word (in reverse order)
|
||||
word = (uint16_t)(ram[ vRamBase + offsetHGR[line] + col + 1 ]) << 8;
|
||||
word += ram[ vRamBase + offsetHGR[line] + col ];
|
||||
|
||||
// check if this group of 7 dots need a redraw (ie were modified)
|
||||
// or redraw all screen every 1/2 second (every 30 frames)
|
||||
if (previousDots[line][col] != word || frame == 0){
|
||||
previousDots[line][col] = word;
|
||||
|
||||
// store all bits of the word into the bits array
|
||||
for (bit=0; bit<16; bit++) bits[bit] = (word >> bit) & 1;
|
||||
|
||||
colorShift = bits[7] * 4; // select the right colorset
|
||||
bit = 1; // starting at 2nd bit
|
||||
|
||||
while (bit < 15){ // until we reach bit7 of 2nd byte
|
||||
// color clashing is not implemented
|
||||
if (bit == 7){ // into the second byte
|
||||
colorShift=bits[15]*4; // update the color set
|
||||
bit = 8; // skip bit 7
|
||||
colorIdx = colorShift + (bits[8] << 1) + bits[6];
|
||||
}
|
||||
else
|
||||
colorIdx = colorShift + (bits[bit] << 1) + bits[bit - 1];
|
||||
|
||||
// plot first half dot
|
||||
SDL_SetRenderDrawColor(rdr, hcolor[colorIdx][0], \
|
||||
hcolor[colorIdx][1], hcolor[colorIdx][2], SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderDrawPoint(rdr, x, y);
|
||||
|
||||
// plot second half dot, using slightly darker colors
|
||||
SDL_SetRenderDrawColor(rdr, hhcolor[colorIdx][0],
|
||||
hhcolor[colorIdx][1], hhcolor[colorIdx][2], SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderDrawPoint(rdr, x + 1, y);
|
||||
|
||||
x += 2; // proceed to the next dot
|
||||
bit += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//========================================================= lOW RES GRAPHICS
|
||||
else if (TEXT != 0){ // not in full text
|
||||
vRamBase = PAGE * 0x0400;
|
||||
for (int col=0; col<40; col++){ // for each column
|
||||
pixelGR.x = col * 7;
|
||||
for (int line=0; line<TEXT; line++){ // for each row
|
||||
pixelGR.y = line * 8; // first block
|
||||
|
||||
glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory
|
||||
|
||||
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 != 24){
|
||||
vRamBase = PAGE * 0x0400;
|
||||
for (int col=0; col<40; col++){ // for each column
|
||||
dstRect.x = col * 7;
|
||||
for (int line=TEXT; line<24; line++){ // for each row
|
||||
dstRect.y = line * 8;
|
||||
|
||||
glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory
|
||||
|
||||
if (glyph < 0x40) glyphAttribute = A_INVERSE; // is INVERSE ?
|
||||
else if (glyph > 0x7F) glyphAttribute = A_NORMAL; // is NORMAL ?
|
||||
else glyphAttribute = 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 (glyphAttribute==A_NORMAL || (glyphAttribute==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]);
|
||||
puce6502Exec(100000); // drive speed up
|
||||
}
|
||||
|
||||
//========================================================= SDL RENDER FRAME
|
||||
if (++frame == 30) frame = 0; // 1/2 second timer
|
||||
frameTime = SDL_GetTicks() - frameStart; // frame duration
|
||||
if (frameDelay > frameTime) SDL_Delay(frameDelay - frameTime); // wait vsync
|
||||
SDL_RenderPresent(rdr); // swap buffers
|
||||
|
||||
} // while (running)
|
||||
|
||||
//================================================ RELEASE RESSOURSES AND EXIT
|
||||
SDL_AudioQuit();
|
||||
SDL_Quit();
|
||||
return(0);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 837 KiB |
Loading…
Reference in New Issue