459 lines
10 KiB
C
459 lines
10 KiB
C
/*
|
|
|
|
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 <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#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;
|
|
}
|
|
|