aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-16 12:49:02 +0200
committergbc dev <gbc@localhost>2026-07-16 12:49:02 +0200
commitca6f9bb91893cf50c8e09bf7396085a7d69e80ab (patch)
tree64ccd983aaf5322f423489caf4c68d4949dab1bb
parentheadless: interactive TTY mode (raw stdin) for driving a guest OS live (diff)
downloadsl0pboy-ca6f9bb91893cf50c8e09bf7396085a7d69e80ab.tar.gz
sl0pboy-ca6f9bb91893cf50c8e09bf7396085a7d69e80ab.tar.xz
sl0pboy-ca6f9bb91893cf50c8e09bf7396085a7d69e80ab.zip
headless: $ED opcode = clean power-off (exit + battery save)
The unused SM83 opcode $ED now sets gb->poweroff, which breaks the headless loop so the normal cart_free()->cart_save() path runs. Lets a guest OS shut the emulator down cleanly (and flush its .sav) instead of relying on an external timeout/SIGTERM, which could SIGKILL before the save. run_frame() also stops on poweroff so exit is immediate.
-rw-r--r--src/cpu.c2
-rw-r--r--src/gb.h1
-rw-r--r--src/main.c4
3 files changed, 5 insertions, 2 deletions
diff --git a/src/cpu.c b/src/cpu.c
index 9e7f685..5b73724 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -407,6 +407,8 @@ int cpu_step(GB *gb) {
case 0xCB: do_cb(gb); break;
+ case 0xED: gb->poweroff = true; break; // (illegal on HW) clean-exit signal
+
default:
// 0x40-0xBF block: LD r,r' and ALU A,r
if (op >= 0x40 && op < 0x80) {
diff --git a/src/gb.h b/src/gb.h
index 7218648..7959e60 100644
--- a/src/gb.h
+++ b/src/gb.h
@@ -116,6 +116,7 @@ struct GB {
bool serial_log; // print serial output to stderr (blargg tests)
int serial_out_fd; // if >=0, transmitted serial bytes written here (stdout)
int serial_in_fd; // if >=0, received serial bytes read here (stdin), else 0xFF
+ bool poweroff; // set by the $ED opcode: request a clean emulator exit
};
void gb_init(GB *gb);
diff --git a/src/main.c b/src/main.c
index 648ded2..935c9e7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -24,7 +24,7 @@ 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)
+ while (!gb->ppu.frame_ready && gb->cycles < budget && !gb->poweroff)
cpu_step(gb);
}
@@ -120,7 +120,7 @@ int main(int argc, char **argv) {
double start_t = now_sec();
u64 start_cyc = gb->cycles;
const double HZ = 4194304.0;
- while (running) {
+ while (running && !gb->poweroff) {
run_frame(gb); // advance ~a frame's worth of cycles
if (!uncapped) {
double target = start_t + (gb->cycles - start_cyc) / HZ;