mgba_save_state
Save the current emulator state to a numbered slot or absolute file path. Use as a checkpoint for later loading or to seed cartridge SRAM on Game Boy.
Instructions
Save the current emulator state. Pass either slot (0-9, mGBA-managed slot file) or path (absolute file path; only works on builds that expose the file API). Useful for capturing checkpoints to load later — and as a clean way to seed cartridge SRAM on Game Boy without fighting the MBC.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| slot | No | Save state slot (0-9) | |
| path | No | Absolute file path (alternative to slot) |
Implementation Reference
- src/tools.ts:222-231 (schema)Tool definition (name, description, inputSchema) for the mgba_save_state tool. Accepts optional 'slot' (0-9) or 'path' (absolute file path).
name: "mgba_save_state", description: "Save the current emulator state. Pass either `slot` (0-9, mGBA-managed slot file) or `path` (absolute file path; only works on builds that expose the file API). Useful for capturing checkpoints to load later — and as a clean way to seed cartridge SRAM on Game Boy without fighting the MBC.", inputSchema: { type: "object", properties: { slot: { type: "integer", minimum: 0, maximum: 9, description: "Save state slot (0-9)" }, path: { type: "string", description: "Absolute file path (alternative to slot)" }, }, }, }, - src/tools.ts:385-394 (handler)Handler/case for mgba_save_state in the CallToolRequestSchema switch. Validates that either slot or path is provided, then calls mgba.call('save_state', ...) to delegate to the mGBA Lua bridge. Returns the saved path or slot number.
case "mgba_save_state": { if (p.slot === undefined && p.path === undefined) { throw new Error("provide either `slot` (0-9) or `path`"); } const r = await mgba.call<{ slot?: number; path?: string }>("save_state", { ...(p.slot !== undefined ? { slot: p.slot } : {}), ...(p.path !== undefined ? { path: p.path } : {}), }); return ok(r.path ? `Saved state to ${r.path}` : `Saved state to slot ${r.slot}`); } - src/tools.ts:258-411 (registration)The registerTools function registers all tools (including mgba_save_state) via server.setRequestHandler for ListToolsRequestSchema and CallToolRequestSchema. The tool name is matched by switch-case at line 385.
export function registerTools(server: Server, mgba: MgbaClient): void { server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS })); server.setRequestHandler(CallToolRequestSchema, async (req) => { const { name, arguments: args = {} } = req.params; const p = args as Record<string, unknown>; switch (name) { case "mgba_ping": { const r = await mgba.call<string>("ping"); return ok(r); } case "mgba_get_info": { const r = await mgba.call<{ title?: string; code?: string; frame?: number; platform?: number | string; capabilities?: Record<string, boolean>; }>("get_info"); const lines = [ `Title: ${r.title ?? "(unavailable)"}`, `Code: ${r.code ?? "(unavailable)"}`, `Platform: ${r.platform ?? "(unavailable)"}`, `Frame: ${r.frame ?? "(unavailable)"}`, ]; if (r.capabilities) { const present = Object.entries(r.capabilities).filter(([, v]) => v).map(([k]) => k); const missing = Object.entries(r.capabilities).filter(([, v]) => !v).map(([k]) => k); lines.push(""); lines.push(`Capabilities present: ${present.length ? present.join(", ") : "(none)"}`); if (missing.length) lines.push(`Missing on this build: ${missing.join(", ")}`); } return ok(lines.join("\n")); } case "mgba_read8": { const v = await mgba.call<number>("read8", { address: p.address }); return ok(`0x${(p.address as number).toString(16).toUpperCase()}: ${formatHex(v)}`); } case "mgba_read16": { const v = await mgba.call<number>("read16", { address: p.address }); return ok(`0x${(p.address as number).toString(16).toUpperCase()}: ${formatHex(v)}`); } case "mgba_read32": { const v = await mgba.call<number>("read32", { address: p.address }); return ok(`0x${(p.address as number).toString(16).toUpperCase()}: ${formatHex(v)}`); } case "mgba_write8": { await mgba.call("write8", { address: p.address, value: p.value }); return ok(`Wrote ${formatHex(p.value)} → 0x${(p.address as number).toString(16).toUpperCase()}`); } case "mgba_write16": { await mgba.call("write16", { address: p.address, value: p.value }); return ok(`Wrote ${formatHex(p.value)} → 0x${(p.address as number).toString(16).toUpperCase()}`); } case "mgba_write32": { await mgba.call("write32", { address: p.address, value: p.value }); return ok(`Wrote ${formatHex(p.value)} → 0x${(p.address as number).toString(16).toUpperCase()}`); } case "mgba_read_range": { const bytes = await mgba.call<number[]>("read_range", { address: p.address, length: p.length, }); const hex = bytes .map((b) => b.toString(16).padStart(2, "0").toUpperCase()) .join(" "); const addr = (p.address as number).toString(16).toUpperCase(); return ok(`0x${addr} [${bytes.length} bytes]:\n${hex}`); } case "mgba_write_range": { const r = await mgba.call<{ written: number }>("write_range", { address: p.address, bytes: p.bytes, }); const addr = (p.address as number).toString(16).toUpperCase(); return ok(`Wrote ${r.written} bytes → 0x${addr}`); } case "mgba_press_buttons": { const r = await mgba.call<{ queued: boolean; queue_size: number }>("press_buttons", { buttons: p.buttons, frames: p.frames ?? 1, release_frames: p.release_frames ?? 1, }); const keys = (p.buttons as string[]).join("+"); return ok( `Queued press: ${keys} ` + `(hold ${p.frames ?? 1}f, release ${p.release_frames ?? 1}f). ` + `Queue size: ${r.queue_size}`, ); } case "mgba_advance_frames": { const frame = await mgba.call<number>("advance_frames", { count: p.count ?? 1 }); return ok(`Advanced ${p.count ?? 1} frame(s). Current frame: ${frame}`); } case "mgba_pause": { await mgba.call("pause"); return ok("Emulation paused"); } case "mgba_unpause": { await mgba.call("unpause"); return ok("Emulation resumed"); } case "mgba_reset": { await mgba.call("reset"); return ok("ROM reset"); } case "mgba_screenshot": { const path = await mgba.call<string>("screenshot", p.path ? { path: p.path } : {}); return ok(`Screenshot saved: ${path}`); } case "mgba_save_state": { if (p.slot === undefined && p.path === undefined) { throw new Error("provide either `slot` (0-9) or `path`"); } const r = await mgba.call<{ slot?: number; path?: string }>("save_state", { ...(p.slot !== undefined ? { slot: p.slot } : {}), ...(p.path !== undefined ? { path: p.path } : {}), }); return ok(r.path ? `Saved state to ${r.path}` : `Saved state to slot ${r.slot}`); } case "mgba_load_state": { if (p.slot === undefined && p.path === undefined) { throw new Error("provide either `slot` (0-9) or `path`"); } const r = await mgba.call<{ slot?: number; path?: string }>("load_state", { ...(p.slot !== undefined ? { slot: p.slot } : {}), ...(p.path !== undefined ? { path: p.path } : {}), }); return ok(r.path ? `Loaded state from ${r.path}` : `Loaded state from slot ${r.slot}`); } default: throw new Error(`Unknown tool: ${name}`); } }); }