diff --git a/Makefile b/Makefile index 32374dd..7e9408f 100644 --- a/Makefile +++ b/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+ diff --git a/README.md b/README.md index 2fd9f57..b64dcb0 100644 --- a/README.md +++ b/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 diff --git a/puce6502.c b/puce6502.c index c94f7b0..cbb5417 100644 --- a/puce6502.c +++ b/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 // 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; -} diff --git a/puce6502.h b/puce6502.h index 01f22ea..3ede70f 100644 --- a/puce6502.h +++ b/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 diff --git a/reinetteII+.c b/reinetteII+.c index 16e58df..f509225 100644 --- a/reinetteII+.c +++ b/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 +#include #include #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 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 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 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) diff --git a/reinetteII+.rc b/reinetteII+.rc index c5e1ed0..fcfc903 100644 --- a/reinetteII+.rc +++ b/reinetteII+.rc @@ -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" diff --git a/reinetteII+.res b/reinetteII+.res new file mode 100644 index 0000000..9ba7866 Binary files /dev/null and b/reinetteII+.res differ