/* 3dsspec - Nintendo 3DS Spectrum emulator Copyright (C) 2021 Ian Cowburn This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see ------------------------------------------------------------------------- Provides the emulation for the Spectrum */ #include #include #include #include #include <3ds.h> #include "spec.h" #include "gui.h" #include "framebuffer.h" #include "snap.h" #include "stream.h" #include "config.h" #include "spec48_bin.h" #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #define HIBYTE(w) ((w)>>8) #define LOBYTE(w) ((w)&0xff) /* ---------------------------------------- STATICS */ #define ROMLEN 0x4000 #define ROM_SAVE 0x4c6 #define ROM_LOAD 0x562 #define ED_SAVE 0xf0 #define ED_LOAD 0xf1 #define SCAN_CYCLES 224 #define FRAME_CYCLES 69888 /* The 3DS screen */ #define GFX_W 400 #define GFX_H 240 /* The Spectrum screen and memory */ #define SCR_W 256 #define SCR_H 192 #define TXT_W 32 #define TXT_H 24 #define SCRDATA 0x4000 #define ATTR 0x5800 #define OFF_X ((GFX_W-SCR_W)/2) #define OFF_Y ((GFX_H-SCR_H)/2) #define ATTR_AT(x,y) mem[ATTR+(x)+((y)/8)*32] Z80Byte mem[0x10000]; static int border; static int scanline; #define TOPL 64 /* Scanlines before 'first' line */ #define SCRL SCR_H /* Scanlines making up screen data */ #define BOTL 56 /* Scanlines after 'last' line */ #define TOTL (TOPL+SCRL+BOTL) /* GFX vars */ #define FLASH 16 /* Frames per flash */ static Framebuffer *fb; static int flash=0; static int flashctr=0; #define NVAL 205 /* Normal RGB intensity */ #define BVAL 255 /* Bright RGB intensity */ static Z80Byte *line[SCR_H]; /* Accelerators to screen data */ #define NO_COLS 16 static struct { u16 col; int r,g,b; } coltable[NO_COLS]= { {0, 0x00,0x00,0x00}, /* BLACK */ {0, 0x00,0x00,NVAL}, /* BLUE */ {0, NVAL,0x00,0x00}, /* RED */ {0, NVAL,0x00,NVAL}, /* MAGENTA */ {0, 0x00,NVAL,0x00}, /* GREEN */ {0, 0x00,NVAL,NVAL}, /* CYAN */ {0, NVAL,NVAL,0x00}, /* YELLOW */ {0, NVAL,NVAL,NVAL}, /* WHITE */ {0, 0x00,0x00,0x00}, /* BLACK */ {0, 0x00,0x00,BVAL}, /* BLUE */ {0, BVAL,0x00,0x00}, /* RED */ {0, BVAL,0x00,BVAL}, /* MAGENTA */ {0, 0x00,BVAL,0x00}, /* GREEN */ {0, 0x00,BVAL,BVAL}, /* CYAN */ {0, BVAL,BVAL,0x00}, /* YELLOW */ {0, BVAL,BVAL,BVAL}, /* WHITE */ }; /* The keyboard */ static Z80Byte matrix[8]; static struct { int row; int bit; } key_matrix[]= { {3,0x01}, {3,0x02}, {3,0x04}, {3,0x08}, {3,0x10}, /* 1 - 5 */ {4,0x10}, {4,0x08}, {4,0x04}, {4,0x02}, {4,0x01}, /* 6 - 0 */ {2,0x01}, {2,0x02}, {2,0x04}, {2,0x08}, {2,0x10}, /* Q - T */ {5,0x10}, {5,0x08}, {5,0x04}, {5,0x02}, {5,0x01}, /* Y - P */ {1,0x01}, {1,0x02}, {1,0x04}, {1,0x08}, {1,0x10}, /* A - G */ {6,0x10}, {6,0x08}, {6,0x04}, {6,0x02}, {6,0x01}, /* H - NL */ {0,0x01}, {0,0x02}, {0,0x04}, {0,0x08}, {0,0x10}, /* CAPS - V */ {7,0x10}, {7,0x08}, {7,0x04}, {7,0x02}, {7,0x01} /* B - SPACE */ }; /* Sound */ #define SAMPLE_BUFF_SZ (SAMPLE_RATE/60) #define SAMPLE_CYCLE (FRAME_CYCLES/SAMPLE_BUFF_SZ) static u8 *sample[2]; static int sound_buffer; static u8 sound_level; static int sound_ptr; static ndspWaveBuf wave_buff[2]; /* ---------------------------------------- PRIVATE FUNCTIONS */ static void RomPatch(void) { static const Z80Byte save[]= { 0xed, ED_SAVE, /* (SAVE) */ 0xc9, /* RET */ 0xff /* End of patch */ }; static const Z80Byte load[]= { 0x08, /* EX AF,AF' */ 0xed, ED_LOAD, /* (LOAD) */ 0xc9, /* RET */ 0xff /* End of patch */ }; int f; for(f=0;save[f]!=0xff;f++) { mem[ROM_SAVE+f]=save[f]; } for(f=0;load[f]!=0xff;f++) { mem[ROM_LOAD+f]=load[f]; } } static void DrawScanline(int y, int sline) { int aline; int f,r; int ink,paper,t; Z80Byte *scr; Z80Byte b; Z80Byte att; aline=sline-TOPL; y = fb->height - y - 1; if (aline>=0 && alinewidth; f++) { FB_ADDR(fb, f, y) = coltable[border].col; } scr=line[aline]; for(f=0;f>3; if (att&0x40) { ink+=8; paper+=8; } if ((att&0x80)&&(flash)) { t=ink; ink=paper; paper=t; } for(r=0,b=*scr++;r<8;r++) { if (b & 0x80) { FB_ADDR(fb, f*8+r+OFF_X, y) = coltable[ink].col; } else { FB_ADDR(fb, f*8+r+OFF_X, y) = coltable[paper].col; } b = b << 1; } } } else { for(f = 0; f < fb->width; f++) { FB_ADDR(fb, f, y) = coltable[border].col; } } } static int CheckTimers(Z80 *z80, Z80Val val) { int ret = TRUE; if (DSSPEC_Config[DSSPEC_SOUND] && Z80GetTimer(z80, Z80_TIMER_1) >= SAMPLE_CYCLE) { Z80SetTimer(z80, Z80_TIMER_1, 0); sample[sound_buffer][sound_ptr++] = sound_level; } if (val > SCAN_CYCLES) { int y; Z80ResetCycles(z80, val-SCAN_CYCLES); /* Increment scan line and check for frame flyback */ scanline++; if (scanline==TOTL) { scanline=0; flashctr++; if (flashctr==FLASH) { flash^=1; flashctr=0; } Z80Interrupt(z80,0xff); if (DSSPEC_Config[DSSPEC_SOUND]) { ndspChnWaveBufAdd(0, wave_buff + sound_buffer); sound_buffer = !sound_buffer; memset(sample[sound_buffer], 0, SAMPLE_BUFF_SZ); sound_ptr = 0; Z80SetTimer(z80, Z80_TIMER_1, 0); } ret = FALSE; } /* Draw scanline */ y = scanline - TOPL + OFF_Y; if (y >= 0 && y < fb->height) { DrawScanline(y, scanline); } } return ret; } static int EDCallback(Z80 *z80, Z80Val data) { switch((Z80Byte)data) { case ED_SAVE: z80->AF.b[Z80_LO_WORD] &= ~eZ80_Carry; break; case ED_LOAD: if (TAPLoad(z80->AF.b[Z80_HI_WORD], &z80->IX.w, &z80->DE.w, SPECWriteMemSNAP)) { z80->AF.b[Z80_LO_WORD] |= eZ80_Carry; z80->BC.w=0xb001; } else { z80->AF.b[Z80_LO_WORD] &= ~eZ80_Carry; z80->BC.w = 0xff01; } break; default: break; } return TRUE; } /* ---------------------------------------- EXPORTED INTERFACES */ void SPECInit(Z80 *z80) { int f; for(f = 0; f < NO_COLS; f++) { coltable[f].col = RGB8_to_565(coltable[f].r, coltable[f].g, coltable[f].b); } /* Load the ROM */ memcpy(mem,spec48_bin,ROMLEN); /* Patch the ROM */ RomPatch(); Z80LodgeCallback(z80,eZ80_EDHook,EDCallback); Z80LodgeCallback(z80,eZ80_Instruction,CheckTimers); /* Create sound buffers and wave vars */ sample[0] = linearAlloc(SAMPLE_BUFF_SZ); sample[1] = linearAlloc(SAMPLE_BUFF_SZ); wave_buff[0].data_vaddr = sample[0]; wave_buff[0].nsamples = SAMPLE_BUFF_SZ; wave_buff[1].data_vaddr = sample[1]; wave_buff[1].nsamples = SAMPLE_BUFF_SZ; sound_buffer = 0; SPECReset(z80); } void SPECStartFrame(Framebuffer *framebuffer) { fb = framebuffer; } void SPECHandleKey(SoftKey key, int is_pressed) { if (key=ROMLEN) { mem[addr]=val; } } void SPECWriteMemSNAP(Z80Word addr, Z80Byte val) { if (addr>=ROMLEN) { mem[addr]=val; } } Z80Byte SPECReadPort(Z80 *z80, Z80Word port) { Z80Byte lo=port&0xff; Z80Byte hi=port>>8; Z80Byte b=0xff; int f; switch(lo) { case 0x1f: /* Kempston joystick */ break; case 0x7f: /* Fuller joystick */ break; case 0xfb: /* ZX Printer */ break; default: /* ULA */ if (!(lo&1)) { /* Key matrix */ b=0xff; for(f=0;f<8;f++) if (!(hi&(1<