aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.c
blob: 273d6b795e87f255f715b88f20726c188e9cc585 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include "gb.h"
#include "cpu.h"
#include "render.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>

static volatile sig_atomic_t running = 1;
static void on_sig(int s) { (void)s; running = 0; }

static double now_sec(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec / 1e9;
}

static void run_frame(GB *gb) {
    gb->ppu.frame_ready = false;
    // safety bound in case LCD is off (no frame_ready)
    u64 budget = gb->cycles + 70224 * 2;
    while (!gb->ppu.frame_ready && gb->cycles < budget)
        cpu_step(gb);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s <rom> [--test seconds]\n", argv[0]);
        return 1;
    }
    const char *rom = argv[1];
    bool test = false;
    double seconds = 30.0;
    int shot_frames = -1;
    const char *shot_path = "shot.ppm";
    for (int i = 2; i < argc; i++) {
        if (!strcmp(argv[i], "--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];
        }
    }

    GB *gb = calloc(1, sizeof(GB));
    if (cart_load(&gb->cart, rom) != 0) { free(gb); return 1; }
    gb_reset(gb);

    if (test) {
        gb->serial_log = true;
        u64 target = (u64)(seconds * 4194304.0);
        while (gb->cycles < target) cpu_step(gb);
        fprintf(stderr, "\n[ran %llu cycles]\n", (unsigned long long)gb->cycles);
        cart_free(&gb->cart);
        free(gb);
        return 0;
    }

    if (shot_frames >= 0) {
        for (int f = 0; f < shot_frames; f++) run_frame(gb);
        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);
        fclose(pf);
        fprintf(stderr, "wrote %s after %d frames\n", shot_path, shot_frames);
        cart_free(&gb->cart); free(gb); return 0;
    }

    signal(SIGINT, on_sig);
    signal(SIGTERM, on_sig);
    term_init();

    const double frame_time = 1.0 / 59.73;
    double next = now_sec();
    while (running) {
        if (!input_poll(gb)) break;
        run_frame(gb);
        render_frame(gb);

        next += frame_time;
        double t = now_sec();
        double sleep = next - t;
        if (sleep > 0) {
            struct timespec ts = { (time_t)sleep, (long)((sleep - (time_t)sleep) * 1e9) };
            nanosleep(&ts, NULL);
        } else if (sleep < -0.1) {
            next = t; // fell behind; resync
        }
    }

    term_restore();
    cart_free(&gb->cart);
    free(gb);
    return 0;
}