diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 152 |
1 files changed, 116 insertions, 36 deletions
@@ -44,15 +44,59 @@ static u8 keys_mask(const char *keys, int frame) { } } +static void print_usage(FILE *out, const char *prog) { + fprintf(out, +"usage: %s [options] <rom>\n" +"\n" +"Emulate a Game Boy / Game Boy Color ROM. The ROM path is the final\n" +"argument; all options precede it.\n" +"\n" +"display:\n" +" --sixel [scale] sixel graphics output; scale = pixel zoom 1-6 (def 2)\n" +" --chrome draw a Game Boy body around the LCD (sixel display)\n" +" --palette NAME recolor the LCD: dmg|green (pea green), pocket|gray\n" +" --green alias for --palette dmg\n" +" --frameskip N draw 1 of every (N+1) frames\n" +"\n" +"speed:\n" +" --fps N emulation speed cap in frames/sec (default 59.73)\n" +" --uncapped run as fast as possible (alias: --turbo)\n" +"\n" +"boot:\n" +" --bios [path] run a boot ROM first (default bios/gbc_bios.bin)\n" +"\n" +"control channels:\n" +" --fifo [path] button-input named pipe (default /tmp/gbc.fifo)\n" +" --sock [path] control/debug socket (default /tmp/gbc.sock)\n" +"\n" +"headless & testing:\n" +" --headless no video; wire serial <-> stdio (OS console)\n" +" --test [seconds] run N seconds then exit, logging serial (default 30)\n" +" --shot N [file] run N frames, write a PPM screenshot (default shot.ppm)\n" +" --keys STR scripted button taps: u d l r a b s(elect) e(start) .\n" +"\n" +" -h, --help show this help and exit\n", + prog); +} + int main(int argc, char **argv) { - if (argc < 2) { - fprintf(stderr, "usage: %s <rom> [--test seconds] [--sixel [scale]] " - "[--frameskip n] [--fps n] [--uncapped] [--fifo [path]] " - "[--sock [path]] [--headless]\n", - argv[0]); + const char *prog = argv[0]; + // --help / -h anywhere on the line wins. + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + print_usage(stdout, prog); + return 0; + } + if (argc < 2) { print_usage(stderr, prog); return 1; } + + // The ROM is the final argument; everything before it is an option. + const char *rom = argv[argc - 1]; + if (rom[0] == '-') { + fprintf(stderr, "%s: no ROM specified (it must be the final argument)\n\n", + prog); + print_usage(stderr, prog); return 1; } - const char *rom = argv[1]; bool test = false; double seconds = 30.0; int shot_frames = -1; @@ -63,41 +107,76 @@ int main(int argc, char **argv) { int frameskip = 0; // draw 1 of every (frameskip+1) frames bool sixel = false; // use sixel graphics output int sixel_scale = 2; // integer pixel zoom for sixel + bool chrome = false; // draw a Game Boy body around the LCD (sixel) + const char *palette = NULL; // LCD recolor: dmg/green, pocket/gray, or off bool headless = false; // no video; serial <-> stdio (OS debug console) + const char *bios_path = NULL; // boot ROM to run before the cartridge const char *keys = NULL; // scripted button taps for testing (u d l r a b s e .) - for (int i = 2; i < argc; i++) { - if (!strcmp(argv[i], "--test")) { + + // Parse options in argv[1 .. argc-2]; the final argument is the ROM and is + // excluded here so options with optional values never swallow it. + for (int i = 1; i < argc - 1; i++) { + const char *a = argv[i]; + // A value is available only if the next token is still within the + // option range (not the ROM) and isn't itself an option. + bool has_val = (i + 1 < argc - 1) && argv[i+1][0] != '-'; + if (!strcmp(a, "--test")) { test = true; - if (i + 1 < argc) seconds = atof(argv[++i]); - } else if (!strcmp(argv[i], "--shot")) { - if (i + 1 < argc) shot_frames = atoi(argv[++i]); - if (i + 1 < argc && argv[i+1][0] != '-') shot_path = argv[++i]; - } else if (!strcmp(argv[i], "--keys")) { - if (i + 1 < argc) keys = argv[++i]; - } else if (!strcmp(argv[i], "--fifo")) { - fifo_path = (i + 1 < argc && argv[i+1][0] != '-') - ? argv[++i] : "/tmp/gbc.fifo"; - } else if (!strcmp(argv[i], "--sock") || !strcmp(argv[i], "--socket")) { - sock_path = (i + 1 < argc && argv[i+1][0] != '-') - ? argv[++i] : "/tmp/gbc.sock"; - } else if (!strcmp(argv[i], "--fps")) { - if (i + 1 < argc) target_fps = atof(argv[++i]); - } else if (!strcmp(argv[i], "--frameskip")) { - if (i + 1 < argc) frameskip = atoi(argv[++i]); - } else if (!strcmp(argv[i], "--sixel")) { + if (has_val) seconds = atof(argv[++i]); + } else if (!strcmp(a, "--shot")) { + if (!has_val) { fprintf(stderr, "%s: --shot needs a frame count\n", prog); return 1; } + shot_frames = atoi(argv[++i]); + if ((i + 1 < argc - 1) && argv[i+1][0] != '-') shot_path = argv[++i]; + } else if (!strcmp(a, "--keys")) { + if (!has_val) { fprintf(stderr, "%s: --keys needs a value\n", prog); return 1; } + keys = argv[++i]; + } else if (!strcmp(a, "--fifo")) { + fifo_path = has_val ? argv[++i] : "/tmp/gbc.fifo"; + } else if (!strcmp(a, "--sock") || !strcmp(a, "--socket")) { + sock_path = has_val ? argv[++i] : "/tmp/gbc.sock"; + } else if (!strcmp(a, "--fps")) { + if (!has_val) { fprintf(stderr, "%s: --fps needs a value\n", prog); return 1; } + target_fps = atof(argv[++i]); + } else if (!strcmp(a, "--frameskip")) { + if (!has_val) { fprintf(stderr, "%s: --frameskip needs a value\n", prog); return 1; } + frameskip = atoi(argv[++i]); + } else if (!strcmp(a, "--sixel")) { sixel = true; - if (i + 1 < argc && argv[i+1][0] != '-') sixel_scale = atoi(argv[++i]); - } else if (!strcmp(argv[i], "--uncapped") || !strcmp(argv[i], "--turbo")) { + if (has_val) sixel_scale = atoi(argv[++i]); + } else if (!strcmp(a, "--chrome")) { + chrome = true; + } else if (!strcmp(a, "--palette")) { + palette = has_val ? argv[++i] : "dmg"; + } else if (!strcmp(a, "--green")) { + palette = "dmg"; + } else if (!strcmp(a, "--uncapped") || !strcmp(a, "--turbo")) { target_fps = 0; - } else if (!strcmp(argv[i], "--headless")) { + } else if (!strcmp(a, "--headless")) { headless = true; + } else if (!strcmp(a, "--bios") || !strcmp(a, "--boot")) { + bios_path = has_val ? argv[++i] : "bios/gbc_bios.bin"; + } else { + fprintf(stderr, "%s: unknown option '%s'\n\n", prog, a); + print_usage(stderr, prog); + return 1; } } if (frameskip < 0) frameskip = 0; GB *gb = calloc(1, sizeof(GB)); if (cart_load(&gb->cart, rom) != 0) { free(gb); return 1; } + if (bios_path && gb_load_bootrom(gb, bios_path) != 0) { + cart_free(&gb->cart); free(gb); return 1; + } gb_reset(gb); + if (bios_path) + fprintf(stderr, "booting through BIOS: %s\n", bios_path); + + // Display/capture options that also affect screenshots and video recording, + // so set them before the --shot / recording paths. (--sixel below is purely + // a live-display concern.) + if (palette) render_set_palette(palette); + if (chrome) render_set_chrome(true); if (test) { gb->serial_log = true; @@ -161,11 +240,10 @@ int main(int argc, char **argv) { } if (raw_tty) tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio); if (shot_frames >= 0) { // --headless --shot FILE: dump final LCD + int cw, ch; const u8 *cap = render_capture_frame(gb, &cw, &ch); FILE *pf = fopen(shot_path, "wb"); - fprintf(pf, "P6\n%d %d\n255\n", SCREEN_W, SCREEN_H); - for (int y = 0; y < SCREEN_H; y++) - for (int x = 0; x < SCREEN_W; x++) - fwrite(gb->ppu.fb[y][x], 1, 3, pf); + fprintf(pf, "P6\n%d %d\n255\n", cw, ch); + fwrite(cap, 1, (size_t)cw * ch * 3, pf); fclose(pf); } cart_free(&gb->cart); free(gb); return 0; @@ -173,11 +251,10 @@ int main(int argc, char **argv) { if (shot_frames >= 0) { for (int f = 0; f < shot_frames; f++) run_frame(gb); + int cw, ch; const u8 *cap = render_capture_frame(gb, &cw, &ch); FILE *pf = fopen(shot_path, "wb"); - fprintf(pf, "P6\n%d %d\n255\n", SCREEN_W, SCREEN_H); - for (int y = 0; y < SCREEN_H; y++) - for (int x = 0; x < SCREEN_W; x++) - fwrite(gb->ppu.fb[y][x], 1, 3, pf); + fprintf(pf, "P6\n%d %d\n255\n", cw, ch); + fwrite(cap, 1, (size_t)cw * ch * 3, pf); fclose(pf); fprintf(stderr, "wrote %s after %d frames\n", shot_path, shot_frames); cart_free(&gb->cart); free(gb); return 0; @@ -198,6 +275,9 @@ int main(int argc, char **argv) { sock_path); } if (sixel) render_set_sixel(true, sixel_scale); + if (chrome && !sixel) + fprintf(stderr, "note: --chrome only affects live display in --sixel mode " + "(it still applies to --shot / recordings)\n"); term_init(); // The emulator always advances every GB frame at the paced native rate so |
