/* 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 $Id: main.c 77 2010-11-23 08:10:25Z ianc $ */ #include #include #include #include #include <3ds.h> #include "framebuffer.h" #include "gui.h" #include "keyboard.h" #include "z80.h" #include "spec.h" #include "config.h" #include "snapshot.h" #include "snap.h" #include "debug.h" #define DSSPEC_VERSION "0.1" #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /* Macro for PRINT AT in the upper framebuffer */ #define AT(x,y) (x)*8,upper.height-1-(y)*8 /* ---------------------------------------- STATIC DATA */ static const char *main_menu[]= { "Reset Spectrum", "Load TAP File", "Configure", "Map Joypad to Keys", "Save Memory Snapshot", "Load Memory Snapshot", "Save Joypad/Key State", "Load Joypad/Key State", "Debug", "Exit 3DSSPEC", "Cancel", NULL }; typedef enum { MenuReset, MenuLoadTAP, MenuConfigure, MenuMapJoypad, MenuSaveSnapshot, MenuLoadSnapshot, MenuSaveMappings, MenuLoadMappings, MenuDebug, MenuExit } MenuOpt; static const char *debug_menu[]= { "Enable Debug Output", "Disable Debug Output", "Disassembler", "Memory Viewer", "CPU Info", "Cancel", NULL }; typedef enum { DebugMenuEnableOutput, DebugMenuDisableOutput, DebugMenuDisassembler, DebugMenuMemoryView, DebugMenuCPUInfo, } DebugMenuOpt; /* ---------------------------------------- DISPLAY FUNCS */ static void Splash(void) { static char scroller[]= { " " "Welcome to 3DSSPEC, a Sinclair Spectrum 48K emulator for the Nintendo " "3DS. You can safely ignore this message. I was just bored for half " "an hour. And no retro game is complete without a side-scroller... " "Thanks to Slay Radio, Ladytron, the Genki Rockets, the High " "Voltage SID Collection, The Prodigy, Paradise Lost and " "Retro Gamer for coding fuel. Extra special thanks to devkitPro for " "the development environment." }; static const char *text[]= { "3DSSPEC \177 2023 Ian Cowburn", " ", "Spectrum ROM \177 1982 Amstrad", " ", "PRESS A TO CONTINUE", " ", "https://deathstation9000.org.uk/", " ", NULL }; int f; int y; int scr_x = 0; int scr_y = 240; Framebuffer upper; Framebuffer lower; SNAP_Enable(TRUE); while(!(hidKeysDown() & KEY_A)) { FB_StartFrame(&upper, &lower); FB_Clear(&upper, COL_BLACK); FB_Clear(&lower, COL_BLACK); FB_Blit(&upper, IMG_SPLASH, 0, scr_y); FB_Print(&upper, "10 REM VERSION \001" DSSPEC_VERSION "\001\n" "20 PRINT \"\001THE SPECTRUM IS ACE\001\"\n" "30 GOTO 20", 0, 230, COL_BLACK, COL_TRANSPARENT); FB_printf(&lower, scr_x, 8, COL_WHITE, COL_TRANSPARENT, "%-42.42s",scroller); y = lower.height - 20; for(f=0;text[f];f++) { FB_Centre(&lower, text[f], y, COL_WHITE, COL_TRANSPARENT); y -= 8; } FB_EndFrame(); if (scr_y > 0) { scr_y--; } if (--scr_x == -8) { size_t l = sizeof scroller; char c; scr_x = 0; c = scroller[0]; memmove(scroller, scroller+1, l-2); scroller[l-2] = c; } } } /* ---------------------------------------- JOYPAD MAPPING */ static void MapJoypad(void) { SoftKeyEvent ev; SoftKey pad = NUM_SOFT_KEYS; int done = FALSE; Framebuffer upper; Framebuffer lower; while(!done) { FB_StartFrame(&upper, &lower); SK_DisplayKeyboard(); FB_Clear(&upper, COL_WHITE); FB_Print(&upper, "Press the joypad button you want\n" "to define and then the Spectrum key\n" "you want to use for that button.\n\n" "Press MENU to finish.", 0, upper.height - 40, COL_BLACK,COL_TRANSPARENT); if (pad != NUM_SOFT_KEYS) { FB_printf(&upper, 0, 80, COL_BLACK, COL_TRANSPARENT, "defining\n \001%s\001",SK_KeyName(pad)); } FB_EndFrame(); while(SK_GetBareEvent(&ev)) { if (ev.pressed) { if (ev.key==SK_ABOUT || ev.key==SK_CONFIG) { done = true; } } else { if (ev.key>=SK_PAD_UP && ev.key<=SK_PAD_SELECT) { pad = ev.key; } if (ev.key<=SK_SPACE && pad!=NUM_SOFT_KEYS) { SK_DefinePad(pad,ev.key); pad = NUM_SOFT_KEYS; } } } } } /* ---------------------------------------- DEBUG FUNCTIONS */ static void DebugDisassembler(Z80 *cpu) { static Z80Word addr = 0; int done = FALSE; Z80Word curr_addr = 0; Framebuffer upper; Framebuffer lower; SoftKeyEvent ev; int f; while(!done && aptMainLoop()) { FB_StartFrame(&upper, &lower); SK_DisplayKeyboard(); curr_addr = addr; FB_Clear(&upper, COL_WHITE); for(f=upper.height - 1; f > 0; f -= 8) { FB_printf(&upper, 0, f, COL_RED, COL_TRANSPARENT, "%4.4x:", (unsigned)curr_addr); FB_printf(&upper, 6*8, f, COL_BLACK, COL_TRANSPARENT, "%s", Z80Disassemble(cpu, &curr_addr)); } FB_EndFrame(); while(SK_GetBareEvent(&ev)) { if (ev.pressed) { switch(ev.key) { case SK_PAD_UP: addr--; break; case SK_PAD_DOWN: addr++; break; case SK_PAD_R: addr = curr_addr; break; case SK_PAD_L: addr -= 20; break; case SK_PAD_START: done = TRUE; break; case SK_PAD_B: { char addrstr[32] = {0}; if (GUI_Input("Enter address", addrstr, 31)) { addr = (Z80Word)strtoul(addrstr, NULL, 0); } break; } default: break; } } } } } static void DebugMemory(Z80 *cpu) { static Z80Word addr = 0; int done = FALSE; Z80Word curr_addr = 0; Framebuffer upper; Framebuffer lower; SoftKeyEvent ev; int f; int r; int rows; while(!done && aptMainLoop()) { FB_StartFrame(&upper, &lower); SK_DisplayKeyboard(); curr_addr = addr; FB_Clear(&upper, COL_WHITE); rows = 0; for(f=upper.height - 1; f > 0; f -= 8) { FB_printf(&upper, 0, f, COL_RED, COL_TRANSPARENT, "%4.4x:", (unsigned)curr_addr); for(r=0; r < 8; r++) { char c = '.'; FB_printf(&upper, 6*8 + r*24, f, COL_BLACK, COL_TRANSPARENT, "%2.2x", (unsigned)Z80_MEMORY[curr_addr]); if (Z80_MEMORY[curr_addr] >= 32 && Z80_MEMORY[curr_addr] < 128) { c = Z80_MEMORY[curr_addr]; } FB_printf(&upper, 31*8 + r*8, f, COL_BLUE, COL_TRANSPARENT, "%c", c); curr_addr++; } rows++; } FB_EndFrame(); while(SK_GetBareEvent(&ev)) { if (ev.pressed) { switch(ev.key) { case SK_PAD_UP: addr--; break; case SK_PAD_DOWN: addr++; break; case SK_PAD_R: addr += rows * 8; break; case SK_PAD_L: addr -= rows * 8; break; case SK_PAD_START: done = TRUE; break; case SK_PAD_B: { char addrstr[32] = {0}; if (GUI_Input("Enter address", addrstr, 31)) { addr = (Z80Word)strtoul(addrstr, NULL, 0); } break; } default: break; } } } } } static void DebugCPU(Z80 *cpu) { static enum { AddrPC, AddrSP, AddrHL, AddrBC, AddrDE, AddrIX, AddrIY, NumAddr } addr_type = AddrPC; const char *addr_text[]= { "PC", "SP", "HL", "BC", "DE", "IX", "IY" }; int done = FALSE; Framebuffer upper; Framebuffer lower; SoftKeyEvent ev; Z80Word addr; Z80Word base; int f; while(!done && aptMainLoop()) { FB_StartFrame(&upper, &lower); SK_DisplayKeyboard(); FB_Clear(&upper, COL_WHITE); addr = cpu->PC; FB_printf(&upper, AT(0,0), COL_BLACK, COL_TRANSPARENT, "PC: %4.4x %s", (unsigned)cpu->PC, Z80Disassemble(cpu, &addr)); FB_printf(&upper, AT(0,2), COL_BLACK, COL_TRANSPARENT, "AF: %4.4x", (unsigned)cpu->AF.w); FB_printf(&upper, AT(0,3), COL_BLACK, COL_TRANSPARENT, "BC: %4.4x", (unsigned)cpu->BC.w); FB_printf(&upper, AT(0,4), COL_BLACK, COL_TRANSPARENT, "DE: %4.4x", (unsigned)cpu->DE.w); FB_printf(&upper, AT(0,5), COL_BLACK, COL_TRANSPARENT, "HL: %4.4x", (unsigned)cpu->HL.w); FB_printf(&upper, AT(0,6), COL_BLACK, COL_TRANSPARENT, "IX: %4.4x", (unsigned)cpu->IX.w); FB_printf(&upper, AT(0,7), COL_BLACK, COL_TRANSPARENT, "IY: %4.4x", (unsigned)cpu->IX.w); FB_printf(&upper, AT(9,2), COL_BLACK, COL_TRANSPARENT, "AF': %4.4x", (unsigned)cpu->AF_); FB_printf(&upper, AT(9,3), COL_BLACK, COL_TRANSPARENT, "BC': %4.4x", (unsigned)cpu->BC_); FB_printf(&upper, AT(9,4), COL_BLACK, COL_TRANSPARENT, "DE': %4.4x", (unsigned)cpu->DE_); FB_printf(&upper, AT(9,5), COL_BLACK, COL_TRANSPARENT, "HL': %4.4x", (unsigned)cpu->HL_); switch(addr_type) { case AddrPC: addr = cpu->PC; break; case AddrSP: addr = cpu->SP; break; case AddrHL: addr = cpu->HL.w; break; case AddrBC: addr = cpu->BC.w; break; case AddrDE: addr = cpu->DE.w; break; case AddrIX: addr = cpu->IX.w; break; case AddrIY: addr = cpu->IY.w; break; default: break; } base = addr; addr -= 5; for(f=0; f < 11; f++) { if (addr == base) { FB_printf(&upper, AT(0,9+f), COL_BLACK, COL_LIGHTGREY, "%s %4.4x: %2.2x", addr_text[addr_type], (unsigned)addr, (unsigned)Z80_MEMORY[addr]); } else { FB_printf(&upper, AT(3,9+f), COL_BLACK, COL_TRANSPARENT, "%4.4x: %2.2x", (unsigned)addr, (unsigned)Z80_MEMORY[addr]); } addr++; } FB_printf(&upper, AT(0,21), COL_BLACK, COL_TRANSPARENT, "IFF1: %2.2x IFF2: %2.2x IM: %2.2x I:%2.2x R:%2.2x", (unsigned)cpu->IFF1, (unsigned)cpu->IFF2, (unsigned)cpu->IM, (unsigned)cpu->I, (unsigned)cpu->R); FB_printf(&upper, AT(0,23), COL_BLACK, COL_TRANSPARENT, "Flags: SZ5H3PNC"); FB_printf(&upper, AT(7,24), COL_BLACK, cpu->AF.b.lo & eZ80_Sign ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(8,24), COL_BLACK, cpu->AF.b.lo & eZ80_Zero ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(9,24), COL_BLACK, cpu->AF.b.lo & eZ80_Hidden5 ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(10,24), COL_BLACK, cpu->AF.b.lo & eZ80_HalfCarry ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(11,24), COL_BLACK, cpu->AF.b.lo & eZ80_Hidden3 ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(12,24), COL_BLACK, cpu->AF.b.lo & eZ80_PV ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(13,24), COL_BLACK, cpu->AF.b.lo & eZ80_Neg ? COL_GREEN : COL_RED, " "); FB_printf(&upper, AT(14,24), COL_BLACK, cpu->AF.b.lo & eZ80_Carry ? COL_GREEN : COL_RED, " "); FB_EndFrame(); if (SK_KeyPressed(SK_PAD_X)) { Z80SingleStep(cpu); } while(SK_GetBareEvent(&ev)) { if (ev.pressed) { switch(ev.key) { case SK_PAD_L: if (addr_type == AddrPC) { addr_type = AddrIY; } else { addr_type--; } break; case SK_PAD_R: addr_type = (addr_type + 1) % NumAddr; break; case SK_PAD_START: done = TRUE; break; case SK_PAD_B: Z80SingleStep(cpu); break; default: break; } } } } } static void DebugMenu(Z80 *cpu) { switch(GUI_Menu(debug_menu)) { case DebugMenuEnableOutput: { char host[32] = {0}; char port[32] = {0}; debug_enabled = TRUE; GUI_Input("Enter host", host, 31); GUI_Input("Enter port", port, 31); if (!host[0]) { strcpy(host, "192.168.0.77"); } if (!port[0]) { strcpy(port, "12345"); } DEBUG_SetAddress(host, port); break; } case DebugMenuDisableOutput: debug_enabled = FALSE; break; case DebugMenuDisassembler: DebugDisassembler(cpu); break; case DebugMenuMemoryView: DebugMemory(cpu); break; case DebugMenuCPUInfo: DebugCPU(cpu); break; } } /* ---------------------------------------- MAIN */ int main(int argc, char *argv[]) { Z80 *z80; int quit = FALSE; float mix[12] = {1.0, 1.0}; u32 *SOC_buffer; int soc_initialised = FALSE; gfxInit(GSP_RGB565_OES, GSP_RGB565_OES, FALSE); ndspInit(); ndspSetOutputMode(NDSP_OUTPUT_MONO); ndspChnSetInterp(0, NDSP_INTERP_NONE); ndspChnSetRate(0, SAMPLE_RATE); ndspChnSetFormat(0, NDSP_FORMAT_MONO_PCM8); ndspChnSetMix(0, mix); FB_Init(); GUI_Init(); SOC_buffer = (u32*)memalign(0x1000,0x100000); if (SOC_buffer) { int ret; if ((ret = socInit(SOC_buffer, 0x100000)) == 0) { soc_initialised = TRUE; } else { char msg[256]; snprintf(msg, sizeof msg, "Failed to initialise SOC\n0x%08X", (unsigned int)ret); GUI_Alert(FALSE, msg); } } else { GUI_Alert(FALSE, "Failed to allocate memory for SOC!"); } z80 = Z80Init(SPECReadPort, SPECWritePort); if (!z80) { GUI_Alert(TRUE,"Failed to initialise\nthe Z80 CPU emulation!"); } SPECInit(z80); Splash(); LoadConfig(); SPECReconfigure(); SK_DisplayKeyboard(); SK_SetSticky(SK_SHIFT,DSSPEC_Config[DSSPEC_STICKY_SHIFT]); SK_SetSticky(SK_SYMBOL,DSSPEC_Config[DSSPEC_STICKY_SHIFT]); if (DSSPEC_Config[DSSPEC_LOAD_DEFAULT_SNAPSHOT]) { SNAP_Load(z80, "AUTO", SNAP_TYPE_FULL); } while(!quit && aptMainLoop()) { SoftKeyEvent ev; Framebuffer upper; Framebuffer lower; FB_StartFrame(&upper, &lower); SK_DisplayKeyboard(); SPECStartFrame(&upper); Z80Exec(z80); FB_EndFrame(); while(SK_GetEvent(&ev)) { switch(ev.key) { case SK_ABOUT: case SK_CONFIG: if (ev.pressed) { switch(GUI_Menu(main_menu)) { case MenuReset: SPECReset(z80); break; case MenuLoadTAP: { char file[FILENAME_MAX]; if (GUI_FileSelect(last_dir, file, ".TAP")) { TAPCloseTape(); TAPOpenTape(file); } break; } case MenuConfigure: GUI_Config(); SK_SetSticky (SK_SHIFT, DSSPEC_Config[DSSPEC_STICKY_SHIFT]); SK_SetSticky (SK_SYMBOL, DSSPEC_Config[DSSPEC_STICKY_SHIFT]); SPECReconfigure(); break; case MenuMapJoypad: MapJoypad(); break; case MenuSaveSnapshot: SNAP_Save(z80, SNAP_TYPE_FULL); break; case MenuLoadSnapshot: SNAP_Load(z80, NULL, SNAP_TYPE_FULL); break; case MenuSaveMappings: SNAP_Save(z80, SNAP_TYPE_KEYBOARD); break; case MenuLoadMappings: SNAP_Load(z80, NULL, SNAP_TYPE_KEYBOARD); break; case MenuDebug: DebugMenu(z80); break; case MenuExit: quit = TRUE; break; } } break; default: SPECHandleKey(ev.key,ev.pressed); break; } } } ndspExit(); gfxExit(); if (soc_initialised) { socExit(); } return 0; }