mirror of
https://github.com/ArthurFerreira2/reinette-II-plus.git
synced 2024-12-10 07:52:58 +00:00
v0.4 stable
This commit is contained in:
parent
68436adb8b
commit
bb4c4f0f9a
4
Makefile
4
Makefile
@ -1,5 +1,5 @@
|
||||
CC = gcc
|
||||
FLAGS = -std=c99 -pedantic -Wpedantic -Wall -O3 -Werror
|
||||
FLAGS = -std=c99 -pedantic -Wpedantic -Wall -O3
|
||||
|
||||
LIBS = -lSDL2
|
||||
# comment this line if you are under Linux :
|
||||
@ -12,6 +12,6 @@ reinetteII+: reinetteII+.c puce6502.c $(WIN32-RES)
|
||||
$(CC) $^ $(FLAGS) $(WIN32-LIBS) $(LIBS) -o $@
|
||||
|
||||
reinetteII+.res: reinetteII+.rc
|
||||
windres reinetteII+.rc -O coff -o $(WIN32-RES)
|
||||
windres $^ -O coff -o $(WIN32-RES)
|
||||
|
||||
all: reinetteII+
|
||||
|
45
README.md
45
README.md
@ -8,19 +8,20 @@ After [reinette](https://github.com/ArthurFerreira2/reinette) (Apple 1 emulator)
|
||||
|
||||
\* reinette has two meanings in French : it's a little frog but also a delicious kind of apple
|
||||
|
||||
[download windows binaries](https://github.com/ArthurFerreira2/reinette-II-plus/releases/tag/0.3b)
|
||||
[download windows binaries](https://github.com/ArthurFerreira2/reinette-II-plus/releases/tag/0.4b)
|
||||
|
||||
### Featuring :
|
||||
|
||||
* all video modes in color
|
||||
* mono sound with mute/unmute
|
||||
* 48KB (no language card support)
|
||||
* 64KB (language card support)
|
||||
* paddles/joystick with trim adjustment
|
||||
* 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
|
||||
* screen scaling by integer increments
|
||||
* easy screenshot
|
||||
|
||||
|
||||
It uses the same MOS 6502 CPU emulator as her sisters (now christened [puce6502](https://github.com/ArthurFerreira2/puce6502)).\
|
||||
@ -48,18 +49,28 @@ Pressing the ALT key while dropping the file inserts it into drive 2.
|
||||
|
||||
Use the functions keys to control the emulator itself :
|
||||
```
|
||||
* F1 : writes the changes of the floppy in drive 0 back to host
|
||||
* F2 : writes the changes of the floppy in drive 1 back to host
|
||||
* F3 : zoom out down to 1:1 pixels
|
||||
* F4 : zoom in, max 8x
|
||||
* F5 : reduce joystick trim
|
||||
* F6 : increase joystick trim
|
||||
* F7 : paste text from clipboard
|
||||
* F8 : mute / un-mute sound
|
||||
* F9 : monochrome / color display (only in HGR mode)
|
||||
* F10 : pause / un-pause the emulator
|
||||
* F11 : reset / CTRL-F11 : break
|
||||
* F12 : quit the emulator
|
||||
* F1 : display save how to
|
||||
* ctrl F1 : writes the changes of the floppy in drive 0 back to host
|
||||
* alt F1 : writes the changes of the floppy in drive 1 back to host
|
||||
* F2 : save a screenshot into the screenshots directory
|
||||
* F3 : paste text from clipboard
|
||||
* F4 : mute / unmute sound
|
||||
* shift F4 : increase volume
|
||||
* ctrl F4 : decrease volume
|
||||
* F5 : reset joystick release speed,
|
||||
* shift F5 : increase joystick release speed
|
||||
* crtl F5 : decrease joystick release speed,
|
||||
* F6 : reset joystick action speed,
|
||||
* shift F6 : increase joystick action speed
|
||||
* crtl F6 : decrease joystick action speed,
|
||||
* 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
|
||||
* F11 : reset
|
||||
* F12 : about, help
|
||||
|
||||
Paddles / Joystick :
|
||||
|
||||
@ -76,12 +87,12 @@ Paddles / Joystick :
|
||||
|
||||
* ~~high pitch noise at high volume on windows (Linux Ubuntu tested OK)~~
|
||||
* ~~sound cracks when playing for long period (intro music for example)~~
|
||||
* CPU is not 100% cycle accurate - see source file for more details
|
||||
* ~~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 video is inaccurate, and does not implement color fringing~~
|
||||
* ~~disk ][ access is artificially accelerated~~ - considered as a feature
|
||||
* 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 software requiring the language card)
|
||||
* ~~only has 48KB of RAM (can't run software requiring the language card)~~
|
||||
* and many others ...
|
||||
|
||||
### To do
|
||||
@ -91,7 +102,7 @@ Paddles / Joystick :
|
||||
* check for more accurate RGB values.
|
||||
* ~~implement color fringe effect in HGR~~
|
||||
* ~~re-implement Paddles and Joystick support for analog simulation~~
|
||||
* implement the language card and extend the RAM of **reinette II plus** to 64K to support more software.
|
||||
* ~~implement the language card and extend the RAM of **reinette II plus** to 64K to support more software.~~
|
||||
* for 6502 coders :
|
||||
* add the ability to insert a binary file at a specified address
|
||||
* give the user the option to start with the original Apple II rom
|
||||
|
77
puce6502.c
77
puce6502.c
@ -1,5 +1,4 @@
|
||||
/*
|
||||
|
||||
puce6502 - MOS 6502 cpu emulator
|
||||
Last modified 1st of August 2020
|
||||
Copyright (c) 2018 Arthur Ferreira (arthur.ferreira2@gmail.com)
|
||||
@ -27,15 +26,16 @@
|
||||
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"
|
||||
#include <stdio.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);
|
||||
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
|
||||
@ -60,15 +60,7 @@ struct Register {
|
||||
} reg;
|
||||
|
||||
|
||||
// instruction timing :
|
||||
// http://nparker.llx.com/a2/opcodes.html
|
||||
// http://wouter.bbcmicro.net/general/6502/6502_opcodes.html
|
||||
//
|
||||
// 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
|
||||
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,
|
||||
@ -83,14 +75,48 @@ static int cycles[256] = { // cycle count per instruction
|
||||
//=============================================================== 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
|
||||
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 == 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;
|
||||
else if (address < ROMSTART) softSwitches(address, 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -117,7 +143,7 @@ inline static void branch(){ // used by the 8 branch instructions
|
||||
reg.PC += ope.address;
|
||||
}
|
||||
|
||||
inline static void makeUpdates(uint8_t val){ // used by ASL, LSR, ROL and ROR
|
||||
inline static void makeUpdates(uint8_t val){ // used by ASL, LSR, ROL and ROR
|
||||
if (ope.setAcc){
|
||||
reg.A = val;
|
||||
ope.setAcc = false;
|
||||
@ -153,6 +179,7 @@ static void ZPX(){ // Zero Page,X
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -169,12 +196,14 @@ static void ABS(){ // ABSolute
|
||||
}
|
||||
|
||||
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;
|
||||
@ -212,7 +241,7 @@ void BRK(){ // BReaK
|
||||
push(reg.PC & 0xFF);
|
||||
push(reg.SR | BREAK);
|
||||
reg.SR |= INTR;
|
||||
reg.PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8);
|
||||
reg.PC = readMem(0xFFFE) | ((readMem(0xFFFF) << 8)); // IRQ/BRK FFFE FFFF
|
||||
}
|
||||
|
||||
static void CLD(){ // CLear Decimal
|
||||
@ -532,7 +561,7 @@ static void (*addressing[])(void) = {
|
||||
//========================================================= USER INTERFACE (API)
|
||||
|
||||
void puce6502Reset(){
|
||||
reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8);
|
||||
reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); // RESET FFFC FFFD
|
||||
reg.SP = 0xFF;
|
||||
reg.SR = (reg.SR | INTR) & ~DECIM;
|
||||
ope.setAcc = false;
|
||||
@ -552,10 +581,6 @@ void puce6502Exec(long long int cycleCount){
|
||||
|
||||
//=================================================== ADDED FOR REINETTE II PLUS
|
||||
|
||||
void puce6502Break() {
|
||||
void puce6502Break(){
|
||||
BRK();
|
||||
}
|
||||
|
||||
void puce6502Goto(uint16_t address) {
|
||||
reg.PC = address;
|
||||
}
|
||||
|
20
puce6502.h
20
puce6502.h
@ -30,25 +30,33 @@
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _CPU_H
|
||||
#define _CPU_H
|
||||
#ifndef _PUCE6502_H
|
||||
#define _PUCE6502_H
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef enum {false, true} bool;
|
||||
|
||||
#define RAMSIZE 0xC000
|
||||
#define ROMSTART 0xD000
|
||||
#define ROMSIZE 0x3000
|
||||
#define RAMSIZE 0xC000
|
||||
#define LGCSTART 0xD000
|
||||
#define LGCSIZE 0x3000
|
||||
#define BK2START 0xD000
|
||||
#define BK2SIZE 0x1000
|
||||
#define SL6START 0xC600
|
||||
#define SL6SIZE 0x00FF
|
||||
|
||||
uint8_t rom[ROMSIZE];
|
||||
uint8_t ram[RAMSIZE];
|
||||
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
|
||||
|
||||
long long int ticks;
|
||||
|
||||
void puce6502Exec(long long int cycleCount);
|
||||
void puce6502Reset();
|
||||
void puce6502Break();
|
||||
void puce6502Goto(uint16_t address);
|
||||
|
||||
#endif
|
||||
|
498
reinetteII+.c
498
reinetteII+.c
@ -1,31 +1,30 @@
|
||||
/*
|
||||
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 13th of August 2020
|
||||
Copyright (c) 2020 Arthur Ferreira (arthur.ferreira2gmail.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:
|
||||
|
||||
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.
|
||||
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 <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include "puce6502.h"
|
||||
|
||||
@ -33,12 +32,14 @@
|
||||
//================================================================ SOFT SWITCHES
|
||||
|
||||
uint8_t KBD = 0; // $C000, $C010 ascii value of keyboard input
|
||||
bool SPKR = false; // $C030 Speaker toggle
|
||||
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
|
||||
|
||||
//====================================================================== PADDLES
|
||||
|
||||
@ -46,28 +47,27 @@ uint8_t PB0 = 0;
|
||||
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}; // GC0 and GC1 Countdown
|
||||
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 GCActionSpeed = 16; // Game Controller speed at which it goes to the edges
|
||||
uint8_t GCReleaseSpeed = 16; // Game Controller speed at which it returns to center
|
||||
const float GCFreq = 0.15; // the freq at which the 556 timer decreases the GC values
|
||||
long long int GCCrigger; // the tick at which the GCs have been reseted
|
||||
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 float paddle(int pdl){
|
||||
GCC[pdl] -= (ticks - GCCrigger) * GCFreq;
|
||||
if (GCC[pdl] < 0) GCC[pdl] = 0;
|
||||
return(GCC[pdl]);
|
||||
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 void resetPaddles(){
|
||||
GCC[0] = GCP[0] * GCP[0];
|
||||
GCC[1] = GCP[1] * GCP[1];
|
||||
GCCrigger = ticks;
|
||||
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
|
||||
|
||||
#define audioBufferSize 4096 // found to be large enought
|
||||
@ -75,14 +75,25 @@ Sint8 audioBuffer[2][audioBufferSize] = {0};
|
||||
SDL_AudioDeviceID audioDevice;
|
||||
bool muted = false; // mute/unmute
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
//====================================================================== DISK ][
|
||||
|
||||
uint8_t slot6[256] = {0}; // P5A disk ][ PROM in slot 6
|
||||
int curDrv = 0; // only one can be enabled at a time
|
||||
int curDrv = 0; // Current Drive - only one can be enabled at a time
|
||||
|
||||
struct drive{
|
||||
char filename[512]; // the full disk image path
|
||||
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
|
||||
@ -93,58 +104,52 @@ struct drive{
|
||||
|
||||
|
||||
int insertFloppy(SDL_Window *wdo, char *filename, int drv){
|
||||
int i, a, b;
|
||||
char title[1024];
|
||||
|
||||
FILE *f = fopen(filename, "rb"); // open it in read binary
|
||||
if (!f){
|
||||
printf("Could not open %s\n", filename);
|
||||
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);
|
||||
}
|
||||
if (fread(disk[drv].data, 1, 232960, f) != 232960){ // load it into memory and check size
|
||||
printf("Floppy image should be exactly 232960 Bytes long\n");
|
||||
return(0);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
sprintf(disk[drv].filename,"%s", filename); // update disk filename
|
||||
sprintf(disk[drv].filename,"%s", filename); // update disk filename record
|
||||
|
||||
f = fopen(filename, "ab"); // check if file is writeable
|
||||
if (f){
|
||||
disk[drv].readOnly = false; // f will be NULL if open in W failed
|
||||
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 = true;
|
||||
else disk[drv].readOnly = false; // f is NULL, no writable, no need to close it
|
||||
|
||||
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;
|
||||
|
||||
sprintf(title, "reinette II+ D1: %s D2: %s", disk[0].filename + a, \
|
||||
disk[1].filename + b);
|
||||
sprintf(title, "reinette II+ 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] && !disk[drive].readOnly){ // is there's a floppy ? is it writable ?
|
||||
FILE *f = fopen(disk[drive].filename, "wb");
|
||||
if (f){ // open in write/binary succeeded
|
||||
if (fwrite(disk[drive].data, 1, 232960, f) != 232960){ // check we could write the full lenght
|
||||
printf("Write failed\n");
|
||||
return(0); // failed to write
|
||||
}
|
||||
fclose(f); // release the ressource
|
||||
}
|
||||
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);
|
||||
}
|
||||
return(1); // success
|
||||
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
|
||||
@ -157,9 +162,9 @@ void stepMotor(uint16_t address){
|
||||
int phase = address >> 1;
|
||||
|
||||
phasesBB[curDrv][pIdxB[curDrv]] = phasesB[curDrv][pIdxB[curDrv]];
|
||||
phasesB[curDrv][pIdx[curDrv]] = phases[curDrv][pIdx[curDrv]];
|
||||
phasesB[curDrv][pIdx[curDrv]] = phases[curDrv][pIdx[curDrv]];
|
||||
pIdxB[curDrv] = pIdx[curDrv];
|
||||
pIdx[curDrv] = phase;
|
||||
pIdx[curDrv] = phase;
|
||||
|
||||
if (!(address & 1)){ // head not moving (PHASE x OFF)
|
||||
phases[curDrv][phase] = false;
|
||||
@ -178,31 +183,25 @@ void stepMotor(uint16_t address){
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
//========================================== 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){
|
||||
static uint8_t dLatch = 0; // disk ][ I/O reg
|
||||
static long long int lastTick = 0LL;
|
||||
|
||||
if ((address >> 8) == 0xC6) return(slot6[address - 0xC600]); // disk ][ PROM
|
||||
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); // key STROBE
|
||||
case 0xC010: KBD &= 0x7F; return(KBD); // KBDSTROBE
|
||||
|
||||
case 0xC020: // TAPEOUT (shall we listen it ?)
|
||||
case 0xC020: // TAPEOUT (shall we listen it ? - try SAVE from applesoft)
|
||||
case 0xC030: // SPEAKER
|
||||
case 0xC033: // apple invader uses it to output sound !
|
||||
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
|
||||
}
|
||||
break;
|
||||
case 0xC033: playSound(); break; // apple invader uses $C033 to output sound !
|
||||
|
||||
case 0xC050: TEXT = false; break; // Graphics
|
||||
case 0xC051: TEXT = true; break; // Text
|
||||
@ -216,39 +215,28 @@ uint8_t softSwitches(uint16_t address, uint8_t value){
|
||||
case 0xC061: return(PB0); // Push Button 0
|
||||
case 0xC062: return(PB1); // Push Button 1
|
||||
case 0xC063: return(PB2); // Push Button 2
|
||||
case 0xC064: return(paddle(0) != 0 ? 0x80: 0x00); // Paddle 0
|
||||
case 0xC065: return(paddle(1) != 0 ? 0x80: 0x00); // Paddle 1
|
||||
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 0xC070: resetPaddles(); 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 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: // 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 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
|
||||
disk[curDrv].nibble = (disk[curDrv].nibble + 1) % 0x1A00; // turn floppy of 1 nibble
|
||||
return(dLatch);
|
||||
|
||||
case 0xC0ED: dLatch = value; break; // Load Data Latch
|
||||
@ -259,74 +247,95 @@ uint8_t softSwitches(uint16_t address, uint8_t value){
|
||||
|
||||
case 0xC0EF: disk[curDrv].writeMode = true; break; // latch for WRITE
|
||||
|
||||
// default: printf("Uncaught Soft Switch access at %04X\n", address);
|
||||
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(0); // catch all
|
||||
return(ticks%256); // catch all
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========================================================== 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;
|
||||
const double frameDelay = 1000.0 / 60.0; // targeting 60 FPS
|
||||
double fps = 60;
|
||||
Uint32 frameStart = 0, frameTime = 0, frame = 0;
|
||||
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) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[DEBUG] > %s", SDL_GetError());
|
||||
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);
|
||||
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);
|
||||
SDL_PauseAudioDevice(audioDevice, muted);
|
||||
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<audioBufferSize; i++){ // two audio buffers,
|
||||
audioBuffer[true][i] = 120; // one used when SPKR is true
|
||||
audioBuffer[false][i] = -120; // the other when SPKR is false
|
||||
audioBuffer[true][i] = volume; // one used when SPKR is true
|
||||
audioBuffer[false][i] = -volume; // the other when SPKR is false
|
||||
}
|
||||
|
||||
// LOAD NORMAL AND REVERSE CHARACTERS BITMAPS
|
||||
|
||||
SDL_Surface *tmpSurface = SDL_LoadBMP("assets/font-normal.bmp"); // load the normal font
|
||||
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);
|
||||
|
||||
tmpSurface = SDL_LoadBMP("assets/font-reverse.bmp"); // load the reverse font
|
||||
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);
|
||||
|
||||
|
||||
// VARIABLES USED IN THE VIDEO PRODUCTION
|
||||
|
||||
uint16_t vRamBase = 0x0400; // can be $400, $800, $2000 or $4000
|
||||
uint16_t previousDots[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.
|
||||
uint8_t lineLimit;
|
||||
uint8_t lineLimit; // the line limit between GR and TEXT
|
||||
uint8_t glyph; // a TEXT character, or 2 blocks in GR
|
||||
uint8_t colorIdx = 0; // to index the color arrays
|
||||
bool monochrome = false;
|
||||
bool monochrome = false; // starting in color
|
||||
enum characterAttribute {A_NORMAL, A_INVERSE, A_FLASH} glyphAttr; // character attribute in TEXT
|
||||
|
||||
SDL_Rect drvRect[2] = { {272, 188, 4, 4}, {276, 188, 4, 4} }; // disk drive status squares
|
||||
@ -358,9 +367,9 @@ int main(int argc, char *argv[]){
|
||||
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
|
||||
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,
|
||||
@ -379,38 +388,41 @@ int main(int argc, char *argv[]){
|
||||
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
|
||||
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
|
||||
|
||||
// VM INITIALIZATION
|
||||
|
||||
// VM INITIALIZATION
|
||||
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);
|
||||
|
||||
FILE *f = fopen("rom/appleII+.rom", "rb"); // load the Apple II+ ROM
|
||||
if (!f){
|
||||
printf("Could not open appleII+.rom\n"); // file not found
|
||||
return(1); // exit
|
||||
}
|
||||
if (fread(rom, 1, ROMSIZE, f) != ROMSIZE){ // the file is too small
|
||||
printf("appleII+.rom should be 12 KB\n");
|
||||
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);
|
||||
|
||||
f = fopen("rom/diskII.rom", "rb"); // load the P5A disk ][ PROM
|
||||
if (!f){
|
||||
printf("Could not open diskII.rom\n"); // file not found
|
||||
return(1); // exit
|
||||
}
|
||||
if (fread(slot6, 1, 256, f) != 256){ // file too small
|
||||
printf("diskII.rom should be 256 Bytes\n");
|
||||
return(1); // exit
|
||||
}
|
||||
fclose(f);
|
||||
if (argc > 1) insertFloppy(wdo, argv[1], 0); // load .nib in parameter into drive 0
|
||||
|
||||
if (argc > 1) insertFloppy(wdo, argv[1], 0); // load .nib in parameter into drive 0
|
||||
for (int i=0; i<RAMSIZE; i++) ram[i] = 0xAA; // Joust and Planetoids won't work if page zero is zeroided
|
||||
|
||||
puce6502Reset(); // reset the 6502
|
||||
puce6502Reset(); // reset the 6502
|
||||
|
||||
|
||||
//================================================================== MAIN LOOP
|
||||
@ -422,10 +434,9 @@ int main(int argc, char *argv[]){
|
||||
if (!paused){
|
||||
puce6502Exec((long long int)(1023000.0 / fps)); // using actualized frame rate
|
||||
while (disk[curDrv].motorOn && ++tries) // until motor is off or i reaches 255+1=0
|
||||
puce6502Exec(10000); // artificial drive speed up
|
||||
puce6502Exec(5000); // artificial drive speed up
|
||||
}
|
||||
|
||||
|
||||
//=============================================================== USER INPUT
|
||||
|
||||
while (SDL_PollEvent(&event)){
|
||||
@ -448,11 +459,14 @@ int main(int argc, char *argv[]){
|
||||
|
||||
if (event.type == SDL_DROPFILE){ // user dropped a file
|
||||
char* filename = event.drop.file; // get full pathname
|
||||
insertFloppy(wdo, filename, alt); // if ALT : drv 1 else drv 0
|
||||
if (!insertFloppy(wdo, filename, alt)) // if ALT : 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)) // unless ALT or CTRL were
|
||||
puce6502Goto(0xC600); // pressed, force reboot
|
||||
if (!(alt || ctrl)){ // unless ALT or CTRL were
|
||||
ram[0x3F4]=0; // unset the Power-UP byte
|
||||
puce6502Reset(); // do a cold reset
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYDOWN) // a key has been pressed
|
||||
@ -460,46 +474,118 @@ int main(int argc, char *argv[]){
|
||||
|
||||
// EMULATOR CONTROL :
|
||||
|
||||
case SDLK_F1: saveFloppy(0); break; // save disk 0 back to host
|
||||
case SDLK_F2: saveFloppy(1); break; // save disk 1 back to host
|
||||
|
||||
case SDLK_F3: if ((zoom-=2) < 0) zoom = 0; // zoom out
|
||||
case SDLK_F4: if (++zoom > 8) zoom = 8; // zoom in
|
||||
SDL_SetWindowSize(wdo, 280 * zoom, 192 * zoom);
|
||||
SDL_RenderSetScale(rdr, zoom, zoom);
|
||||
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", "\nThere was an error 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", "\nThere was an error 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_F5: if (GCReleaseSpeed > 1 ) GCReleaseSpeed -= 2; break; // PDL Trim
|
||||
case SDLK_F6: if (GCReleaseSpeed < 127) GCReleaseSpeed += 2; break; // PDL Trim
|
||||
|
||||
case SDLK_F7: // paste txt 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; // Line Feed to Carriage Ret
|
||||
puce6502Exec(400000); // to process each char
|
||||
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;
|
||||
}
|
||||
SDL_free(clipboardText);
|
||||
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;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F8: muted = !muted; break; // mute
|
||||
case SDLK_F9: monochrome = !monochrome; break; // ...
|
||||
case SDLK_F10: paused = !paused; break; // pause / un-pause
|
||||
case SDLK_F12: running = false; break; // goodbye
|
||||
|
||||
case SDLK_F11: // reset
|
||||
if (ctrl)
|
||||
puce6502Break();
|
||||
else {
|
||||
puce6502Reset();
|
||||
softSwitches(0xC0E9,0); // drive0En
|
||||
softSwitches(0xC0E8,0); // motorOff
|
||||
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<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_F8: monochrome = !monochrome; break; // toggle monochrome for HGR mode
|
||||
case SDLK_F9: paused = !paused; break; // toggle pause
|
||||
case SDLK_F10: puce6502Break(); // simulate a break
|
||||
case SDLK_F11: puce6502Reset(); // 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
|
||||
@ -514,10 +600,10 @@ int main(int argc, char *argv[]){
|
||||
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_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 ? 0x90: 0xD0; break; // p
|
||||
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
|
||||
@ -528,7 +614,6 @@ int main(int argc, char *argv[]){
|
||||
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_RETURN: KBD = ctrl ? 0x8A: 0x8D; break; // LF CR
|
||||
case SDLK_LEFTBRACKET: KBD = ctrl ? 0x9B: 0xDB; break; // [ {
|
||||
case SDLK_BACKSLASH: KBD = ctrl ? 0x9C: 0xDC; break; // \ |
|
||||
case SDLK_RIGHTBRACKET: KBD = ctrl ? 0x9D: 0xDD; break; // ] }
|
||||
@ -554,7 +639,8 @@ int main(int argc, char *argv[]){
|
||||
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;
|
||||
case SDLK_ESCAPE: KBD = 0x9B; break; // ESC
|
||||
case SDLK_RETURN: KBD = 0x8D; break; // CR
|
||||
|
||||
// EMULATED JOYSTICK :
|
||||
|
||||
@ -574,7 +660,7 @@ int main(int argc, char *argv[]){
|
||||
}
|
||||
|
||||
for (int pdl=0; pdl<2; pdl++){ // update the two paddles positions
|
||||
if (GCA[pdl]) { // actively pushing the stick
|
||||
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;
|
||||
@ -711,12 +797,12 @@ int main(int argc, char *argv[]){
|
||||
if (++frame > 30) frame = 0;
|
||||
|
||||
frameTime = SDL_GetTicks() - frameStart; // frame duration
|
||||
if (frameTime < frameDelay) { // do we have time ?
|
||||
if (frameTime < frameDelay){ // do we have time ?
|
||||
SDL_Delay(frameDelay - frameTime); // wait 'vsync'
|
||||
SDL_RenderPresent(rdr); // swap buffers
|
||||
frameTime = SDL_GetTicks() - frameStart; // update frameTime
|
||||
}
|
||||
fps = (frameDelay / (double)frameTime) * 60.0; // calculate the actual frame rate
|
||||
} // else skip the frame
|
||||
fps = 1000.0 / frameTime; // calculate the actual frame rate
|
||||
|
||||
} // while (running)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
1 ICON "assets/reinetteII+.ico"
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,3,1,0
|
||||
PRODUCTVERSION 0,3,0,0
|
||||
FILEVERSION 0,4,1,0
|
||||
PRODUCTVERSION 0,4,0,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
@ -9,12 +9,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Arthur Ferreira"
|
||||
VALUE "FileDescription", "Apple II+ emulator"
|
||||
VALUE "FileVersion", "0.3.1.0"
|
||||
VALUE "FileVersion", "0.4.1.0"
|
||||
VALUE "InternalName", "reinette II plus"
|
||||
VALUE "LegalCopyright", "Arthur Ferreira"
|
||||
VALUE "OriginalFilename", "reinetteII+.exe"
|
||||
VALUE "ProductName", "reinette II plus"
|
||||
VALUE "ProductVersion", "0.3"
|
||||
VALUE "ProductVersion", "0.4"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
BIN
reinetteII+.res
Normal file
BIN
reinetteII+.res
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user