/* 3ds81 - Nintendo 3DS ZX81 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 <3ds.h> #include "framebuffer.h" #include "gui.h" #include "keyboard.h" #include "z80.h" #include "zx81.h" #include "tapes.h" #include "config.h" #include "snapshot.h" #include "debug.h" #ifndef DS81_VERSION #define DS81_VERSION "DEV " __TIME__ "/" __DATE__ #endif #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 ZX81", "Select Tape", "Configure", "Map Joypad to Keys", "Save Memory Snapshot", "Load Memory Snapshot", "Save Joypad/Key State", "Load Joypad/Key State", "Help", "Debug", "Exit 3DS81", "Cancel", NULL }; typedef enum { MenuReset, MenuSelectTape, MenuConfigure, MenuMapJoypad, MenuSaveSnapshot, MenuLoadSnapshot, MenuSaveMappings, MenuLoadMappings, MenuHelp, 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 3DS81, a ZX81 emulator for the Ninetendo 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." }; static const char *text[]= { "3DS81 \177 2021 Ian Cowburn", " ", "ZX81 ROM \177 1981", "Nine Tiles Networks LTD", " ", "PRESS A TO CONTINUE", " ", "https://noddybox.co.uk/", " ", "If you place .P tape files in", "the top directory or ZX81", "then you should be able to load", "GAME.P with the command", "LOAD \"GAME\"", NULL }; int f; int y; int scr_x = 0; int scr_y = 240; Framebuffer upper; Framebuffer lower; ZX81EnableFileSystem(TRUE); 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" DS81_VERSION "\001\n" "20 PRINT \"\001THE ZX81 IS ACE\001\"\n" "30 GOTO 20", 0, 230, COL_WHITE, 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; } } } /* ---------------------------------------- HELP */ static void ShowHelp(void) { struct { FB_Colour col; const char *text; } help[] = { {COL_RED, "KEYBOARD"}, {COL_BLACK, "Use the touchscreen to press keys on the computer."}, {COL_BLACK, "The ZX81 had a keyword entry system, so when the"}, {COL_BLACK, "cursor is an inverse K then the keyword printed"}, {COL_BLACK, "at the top of the key is displayed. Therefore"}, {COL_BLACK, "to load a tape press the J key, then the SHIFT"}, {COL_BLACK, "key (which by default stays pressed until you"}, {COL_BLACK, "click it again) and press the P key to enter a"}, {COL_BLACK, "quote, press the SHIFT key to stop pressing it,"}, {COL_BLACK, "type the name of the .P file and close it with"}, {COL_BLACK, "another quote, then press ENTER"}, {COL_TRANSPARENT, ""}, {COL_RED, "LOADING TAPES"}, {COL_BLACK, "There are two ways of loading tapes in the"}, {COL_BLACK, "simulator. The first is to put .P files in"}, {COL_BLACK, "either the root directory of the SD card"}, {COL_BLACK, "or a directory called ZX81. Files from"}, {COL_BLACK, "either place can be loaded by typing"}, {COL_BLACK, "LOAD \"GAME\" to load GAME.P. Alternatively"}, {COL_BLACK, "you can type LOAD \"*\" to display a file"}, {COL_BLACK, "selector that allows you to select the tape"}, {COL_BLACK, "to load. LOAD \"\" can be used to load an"}, {COL_BLACK, "internal tape image once it has been selected."}, {COL_TRANSPARENT, NULL} }; int done = FALSE; u32 key; Framebuffer upper; Framebuffer lower; while(!done) { int f; FB_StartFrame(&upper, &lower); FB_Clear(&upper, COL_WHITE); FB_Clear(&lower, COL_BLACK); for(f = 0; help[f].text; f++) { FB_Print(&upper, help[f].text, 0, upper.height - f * 8, help[f].col,COL_TRANSPARENT); } FB_Print(&lower, "Scroll around help by using the\n" "control pad\n" "Press A to finish.", 0, lower.height - 40, COL_YELLOW,COL_TRANSPARENT); FB_EndFrame(); key = hidKeysDown(); if (key & KEY_A) { done = TRUE; } } } /* ---------------------------------------- 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 ZX81 key\n" "you want to use.\n\n" "Press CONFIG 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 = '.'; unsigned b; b = ZX81ReadDisassem(cpu, curr_addr); FB_printf(&upper, 6*8 + r*24, f, COL_BLACK, COL_TRANSPARENT, "%2.2x", b); if (b >= 32 && b < 128) { c = b; } 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)ZX81ReadDisassem(cpu, addr)); } else { FB_printf(&upper, AT(3,9+f), COL_BLACK, COL_TRANSPARENT, "%4.4x: %2.2x", (unsigned)addr, (unsigned)ZX81ReadDisassem(cpu, 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; gfxInit(GSP_RGB565_OES, GSP_RGB565_OES, false); FB_Init(); z80 = Z80Init(ZX81ReadMem, ZX81WriteMem, ZX81ReadPort, ZX81WritePort, ZX81ReadDisassem); if (!z80) { GUI_Alert(TRUE,"Failed to initialise\nthe Z80 CPU emulation!"); } ZX81Init(z80); Splash(); LoadConfig(); ZX81Reconfigure(); SK_DisplayKeyboard(); SK_SetSticky(SK_SHIFT,DS81_Config[DS81_STICKY_SHIFT]); if (DS81_Config[DS81_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(); Z80Exec(z80); ZX81RenderDisplay(&upper, 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: ZX81Reset(z80); break; case MenuSelectTape: SelectTape(); break; case MenuConfigure: GUI_Config(); SK_SetSticky(SK_SHIFT, DS81_Config[DS81_STICKY_SHIFT]); ZX81Reconfigure(); 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 MenuHelp: ShowHelp(); break; case MenuDebug: DebugMenu(z80); break; case MenuExit: quit = TRUE; break; } } break; default: ZX81HandleKey(ev.key,ev.pressed); break; } } } gfxExit(); return 0; }