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;
}
|