/* This file is part of libgtemu, a library for Gigatron emulation. Copyright (C) 2019 David Heiko Kolf Published under the BSD-2-Clause license. https://opensource.org/licenses/BSD-2-Clause */ #include #include #include #include #include "gtsdl.h" struct MainState { const char *romfile; const char *sendfile; const char *textfile; int displayhelp; int ramexpansion; char *sendbuffer; size_t sendbuffersize; }; static int loadfile (const char *fname, void *buffer, size_t elementsize, size_t elementcount, size_t *readcount) { FILE *f = fopen(fname, "rb"); if (f == NULL) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, fname, strerror(errno), NULL); return 0; } *readcount = fread(buffer, elementsize, elementcount, f); if (*readcount >= elementcount) { char dummy; /* to check for EOF, try to read a further byte */ if (fread(&dummy, 1, 1, f) > 0) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, fname, "File is too large.", NULL); fclose(f); return 0; } } else if (ferror(f)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, fname, strerror(errno), NULL); fclose(f); return 0; } fclose(f); return 1; } static int loadrom (const char *fname, struct GTState *gt) { size_t datasize; if (!loadfile(fname, gt->rom, sizeof(struct GTRomEntry), gt->romcount, &datasize)) { return 0; } return 1; } static void startloader (struct GTState *gt, struct GTPeriph *ph, int wait) { unsigned char *ram = gt->ram; int i; unsigned long long startup, timeout; gtemu_randomizemem(time(0), ram, sizeof(ram)); gt->pc = 0; gtemu_processtick(gt, ph); gt->pc = 0; startup = gtemu_getclock(ph) + 12000000ull; timeout = startup + 36000000ull; while (gtemu_getclock(ph) < startup) { gtemu_processtick(gt, ph); } for (i = 0; i < 5; i++) { /* Press the down button. */ gt->in = 0xff ^ 4; /* Wait until the I/O loop copied the input to 0x07 (serialRaw) and the Main app cleared it again in 0x11 (buttonState). */ while (gtemu_getclock(ph) < timeout && (ram[0x0f] != (0xff ^ 4) || ram[0x11] != 0xff)) { gtemu_processtick(gt, ph); } /* Release the down button. */ gt->in = 0xff; while (gtemu_getclock(ph) < timeout && ram[0x0f] != 0xff) { gtemu_processtick(gt, ph); } } /* Press the A button. */ gt->in = 0xff ^ 0x80; while (gtemu_getclock(ph) < timeout && (ram[0x0f] != (0xff ^ 0x80) || ram[0x11] != 0xff)) { gtemu_processtick(gt, ph); } gt->in = 0xff; if (wait) { /* wait until vPC is in the screen page 0x5a */ while (gtemu_getclock(ph) < timeout && ram[0x17] < 0x5a) { gtemu_processtick(gt, ph); } } } static void sendgt1file (struct MainState *mstate, struct GTState *gt, struct GTPeriph *ph, int restart) { size_t datasize; if (mstate->sendfile == NULL) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Sending GT1 program", "No file specified for sending, use " "the -l option to specify a file.", NULL); return; } if (gtloader_isactive(ph)) { /* Check first whether a file is still being sent, to avoid changing data during the progress. */ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Sending GT1 program", "A file is already being sent.", NULL); return; } if (!loadfile(mstate->sendfile, mstate->sendbuffer, 1, mstate->sendbuffersize, &datasize)) { return; } if (!gtloader_validategt1(mstate->sendbuffer, datasize)) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, mstate->sendfile, "File is not a valid GT1 file.", NULL); return; } if (restart) { startloader(gt, ph, 1); } gtloader_sendgt1(ph, mstate->sendbuffer, datasize); } static void sendtextfile (struct MainState *mstate, struct GTPeriph *ph) { size_t datasize; if (mstate->textfile == NULL) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Sending text", "No file specified for sending, use " "the -t option to specify a file.", NULL); return; } if (gtloader_isactive(ph)) { /* Check first whether a file is still being sent, to avoid changing data during the progress. */ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Sending text", "A file is already being sent.", NULL); return; } if (!loadfile(mstate->textfile, mstate->sendbuffer, 1, mstate->sendbuffersize, &datasize)) { return; } gtloader_sendtext(ph, mstate->sendbuffer, datasize); } static int onkeydown (struct MainState *mstate, struct GTState *gt, struct GTPeriph *ph, SDL_KeyboardEvent *ev) { if (ev->keysym.mod == KMOD_LALT) { switch (ev->keysym.sym) { case 'l': if (!ev->repeat) { startloader(gt, ph, 0); } return 1; case 'x': if (!ev->repeat) { sendgt1file(mstate, gt, ph, 1); } return 1; default: return 0; } } if (ev->keysym.mod == KMOD_LCTRL || ev->keysym.mod == KMOD_RCTRL) { switch (ev->keysym.sym) { case SDLK_F2: if (!ev->repeat) { sendgt1file(mstate, gt, ph, 0); } return 1; case SDLK_F3: if (!ev->repeat) { sendtextfile(mstate, ph); } return 1; default: return 0; } } return 0; } static void ondroppedfile (struct MainState *mstate, struct GTState *gt, struct GTPeriph *ph, const char *fname) { size_t datasize; if (gtloader_isactive(ph)) { /* Check first whether a file is still being sent, to avoid changing data during the progress. */ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, fname, "A file is already being sent.", NULL); return; } if (!loadfile(fname, mstate->sendbuffer, 1, mstate->sendbuffersize, &datasize)) { return; } if (gtloader_validategt1(mstate->sendbuffer, datasize)) { startloader(gt, ph, 1); gtloader_sendgt1(ph, mstate->sendbuffer, datasize); } else { gtloader_sendtext(ph, mstate->sendbuffer, datasize); } } static const char *parsefileoption (int argc, char *argv[], int *i, int *displayhelp) { if (argv[*i][2] != '\0') { return argv[*i] + 2; } (*i)++; if (*i >= argc) { /* no file name given */ *displayhelp = 1; return NULL; } if (argv[*i][0] == '-') { /* another option */ *displayhelp = 1; return NULL; } return argv[*i]; } static void parseargs (int argc, char *argv[], struct MainState *a) { int i; a->romfile = NULL; a->sendfile = NULL; a->textfile = NULL; a->displayhelp = 0; a->ramexpansion = 0; for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { break; } switch(argv[i][1]) { case '-': if (argv[i][2] != '\0') { /* no long options yet */ a->displayhelp = 1; return; } i++; break; case 'h': a->displayhelp = 1; break; case 'l': a->sendfile = parsefileoption(argc, argv, &i, &a->displayhelp); break; case 't': a->textfile = parsefileoption(argc, argv, &i, &a->displayhelp); break; case 'r': a->romfile = parsefileoption(argc, argv, &i, &a->displayhelp); break; case '6': if (argv[i][2] != '4' || argv[i][3] != '\0') { a->displayhelp = 1; return; } a->ramexpansion = 1; break; default: a->displayhelp = 1; return; } } if (i < argc) { a->displayhelp = 1; } } static void displayhelp (const char *progname) { SDL_version linkedsdl; SDL_GetVersion(&linkedsdl); if (progname == NULL) { progname = "gtemu"; } fprintf(stderr, "usage: %s [-h] [options]\n" "\n" "Arguments:\n" " -h Display this help.\n" " -l filename.gt1 GT1 program to be loaded at the start.\n" " -t filename.gtb Text file to be sent with Ctrl-F3.\n" " -r filename.rom ROM file (default name: gigatron.rom).\n" " -64 Expand RAM to 64k.\n" "\n" "Special keys:\n" " Ctrl-F2 Send designated GT1 file.\n" " Ctrl-F3 Send designated text file.\n" " Alt-L Perform hard reset and select loader.\n" " Alt-X Perform hard reset and send GT1 file.\n" " ESC Close the emulation.\n" "\n" "libgtemu version 0.4.0, using SDL version %d.%d.%d.\n", progname, linkedsdl.major, linkedsdl.minor, linkedsdl.patch); } int main (int argc, char *argv[]) { struct MainState mstate; struct GTSDLState s; struct GTState gt; struct GTPeriph ph; unsigned long randstate; size_t outputpos = 0; static struct GTRomEntry rom[0x10000]; static unsigned char ram[0x10000]; static char sendbuffer[0x11000]; static char outputbuffer[128]; mstate.sendbuffer = sendbuffer; mstate.sendbuffersize = sizeof(sendbuffer); parseargs(argc, argv, &mstate); if (mstate.displayhelp) { displayhelp(argc > 0 ? argv[0] : NULL); return EXIT_FAILURE; } gtemu_init(>, rom, sizeof(rom), ram, mstate.ramexpansion ? 0x10000 : 0x8000); randstate = time(0); randstate = gtemu_randomizemem(randstate, rom, sizeof(rom)); randstate = gtemu_randomizemem(randstate, ram, sizeof(ram)); if (!loadrom(mstate.romfile != NULL ? mstate.romfile : "gigatron.rom", >)) { return EXIT_FAILURE; } if (SDL_Init(0) < 0) { const char *sdlerror = SDL_GetError(); fprintf(stderr, "SDL error: %s\n", sdlerror); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL error", sdlerror, NULL); return EXIT_FAILURE; } if (gtsdl_openwindow(&s, "Gigatron")) { gtemu_initperiph(&ph, gtsdl_getaudiofreq(&s), randstate); gtserialout_setbuffer(&ph, outputbuffer, sizeof(outputbuffer), &outputpos); if (mstate.sendfile != NULL) { sendgt1file(&mstate, >, &ph, 1); } for (;;) { SDL_Event ev; int hasevent = gtsdl_runuiframe(&s, >, &ph, &ev); if (outputpos > 0) { fwrite(outputbuffer, sizeof(outputbuffer[0]), outputpos, stdout); fflush(stdout); outputpos = 0; } if (hasevent == 0) { continue; } if (ev.type == SDL_QUIT) { break; } if (ev.type == SDL_KEYDOWN) { if (ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { break; } if (onkeydown(&mstate, >, &ph, &ev.key)) { continue; } } if (ev.type == SDL_DROPFILE) { ondroppedfile(&mstate, >, &ph, ev.drop.file); SDL_free(ev.drop.file); continue; } gtsdl_handleevent(&s, >, &ph, &ev); } } else { const char *sdlerror = SDL_GetError(); fprintf(stderr, "SDL error: %s\n", sdlerror); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL error", sdlerror, NULL); } gtsdl_close(&s); SDL_Quit(); return EXIT_SUCCESS; }