dolphin_save_state
Save the complete emulator state (RAM, registers, GPU, audio, timing) to a numbered slot for rollback before risky writes or sharing reproduction cases.
Instructions
PURPOSE: Save complete emulator state (RAM, registers, GPU, audio, timing) to a numbered slot. USAGE: Rollback point before risky writes, bookmarks, repro sharing. Companion dolphin_load_state restores from the same slot. Dolphin maps slots 1-10 to F1-F10 in the GUI by default; 0 and 11-255 are programmatic-only. BEHAVIOR: DESTRUCTIVE TO TARGET SLOT: silently overwrites prior contents — no prompt, no backup. Bound to the exact game disc and Dolphin build; loading mismatched usually crashes the core. The bridge call returns when Felk schedules the save, NOT when the file is on disk. RETURNS: 'Save state triggered for slot N'.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| slot | Yes | Slot (0-255). 1-10 are mapped to F1-F10 in Dolphin's GUI. |
Implementation Reference
- src/tools.ts:605-607 (handler)Handler for dolphin_save_state tool. Calls the bridge's 'savestate.save_to_slot' RPC with the slot number and returns a confirmation string.
case "dolphin_save_state": { await dol.call("savestate.save_to_slot", [p.slot as number]); return ok(`Save state triggered for slot ${p.slot}`); - src/tools.ts:442-456 (schema)Tool definition (name, description, inputSchema) for dolphin_save_state. Slot is required integer (0-255).
{ name: "dolphin_save_state", description: "PURPOSE: Save complete emulator state (RAM, registers, GPU, audio, timing) to a numbered slot. " + "USAGE: Rollback point before risky writes, bookmarks, repro sharing. Companion dolphin_load_state restores from the same slot. Dolphin maps slots 1-10 to F1-F10 in the GUI by default; 0 and 11-255 are programmatic-only. " + "BEHAVIOR: DESTRUCTIVE TO TARGET SLOT: silently overwrites prior contents — no prompt, no backup. Bound to the exact game disc and Dolphin build; loading mismatched usually crashes the core. The bridge call returns when Felk schedules the save, NOT when the file is on disk. " + "RETURNS: 'Save state triggered for slot N'.", inputSchema: { type: "object", required: ["slot"], properties: { slot: { type: "integer", minimum: 0, maximum: 255, description: "Slot (0-255). 1-10 are mapped to F1-F10 in Dolphin's GUI." }, }, additionalProperties: false, }, - src/tools.ts:488-617 (registration)The tool is registered in the TOOLS array (line 442-456) and dispatched in the CallToolRequestSchema handler switch statement (line 605-608) inside registerTools().
export function registerTools(server: Server, dol: DolphinClient): 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>; const a = () => p.address as number; switch (name) { case "dolphin_ping": { const r = await dol.call<{ bridge_version: string; dolphin: string }>("bridge.ping"); return ok(`OK — bridge v${r.bridge_version} (${r.dolphin})`); } case "dolphin_get_info": { const r = await dol.call<{ bridge_version: string; dolphin: string }>("bridge.ping"); return ok( `Bridge version: ${r.bridge_version}\n` + `Dolphin label: ${r.dolphin}`, ); } case "dolphin_read8": return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u8", [a()]))}`); case "dolphin_read16": return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u16", [a()]))}`); case "dolphin_read32": return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u32", [a()]))}`); case "dolphin_read64": { const v = BigInt(await dol.call<number | string>("memory.read_u64", [a()]) as never); return ok(`${addrHex(a())}: ${fmtHex(v)}`); } case "dolphin_read_range": { const len = p.length as number; const hex = await dol.call<string>("memory.read_bytes", [a(), len]); const bytes = hex.match(/.{2}/g) ?? []; const spaced = bytes.map((b) => b.toUpperCase()).join(" "); return ok(`${addrHex(a())} [${bytes.length} bytes]:\n${spaced}`); } case "dolphin_write8": { await dol.call("memory.write_u8", [a(), p.value as number]); return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`); } case "dolphin_write16": { await dol.call("memory.write_u16", [a(), p.value as number]); return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`); } case "dolphin_write32": { await dol.call("memory.write_u32", [a(), p.value as number]); return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`); } case "dolphin_write64": { const v = BigInt(p.value as string); // Felk's API takes a Python int; JS BigInt won't JSON-serialise so we // convert to a string and the bridge parses it back via int(str). // (For now the bridge actually accepts a JSON number — but 2^53 boundary // hits hard, so we use string transport and let the bridge widen.) await dol.call("memory.write_u64", [a(), v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : Number(v)]); return ok(`Wrote ${fmtHex(v)} → ${addrHex(a())}`); } case "dolphin_press_gc_buttons": { const port = (p.port as number | undefined) ?? 0; const state = p.state as Record<string, unknown>; await dol.call("controller.set_gc_buttons", [port, state]); const keys = Object.keys(state).join(",") || "(empty)"; return ok(`Set GC port ${port}: ${keys}`); } case "dolphin_press_wiimote_buttons": { const port = (p.port as number | undefined) ?? 0; const state = p.state as Record<string, unknown>; await dol.call("controller.set_wiimote_buttons", [port, state]); const keys = Object.keys(state).join(",") || "(empty)"; return ok(`Set Wii Remote port ${port}: ${keys}`); } case "dolphin_set_wiimote_pointer": { const port = (p.port as number | undefined) ?? 0; const x = p.x as number, y = p.y as number; await dol.call("controller.set_wiimote_pointer", [port, x, y]); return ok(`Set Wii Remote port ${port} pointer to (${x}, ${y})`); } case "dolphin_set_wiimote_acceleration": { const port = (p.port as number | undefined) ?? 0; const x = p.x as number, y = p.y as number, z = p.z as number; await dol.call("controller.set_wiimote_acceleration", [port, x, y, z]); return ok(`Set Wii Remote port ${port} accel to (${x}, ${y}, ${z})`); } case "dolphin_set_wiimote_angular_velocity": { const port = (p.port as number | undefined) ?? 0; const x = p.x as number, y = p.y as number, z = p.z as number; await dol.call("controller.set_wiimote_angular_velocity", [port, x, y, z]); return ok(`Set Wii Remote port ${port} angular_velocity to (${x}, ${y}, ${z})`); } case "dolphin_reset": await dol.call("emulation.reset"); return ok("Reset triggered."); case "dolphin_frame_advance": { // Implemented client-side as polling on frame.get_count rather than // a bridge-side blocking wait — keeps the bridge coroutine free to // service other commands and avoids per-call long-hold tying up the // dispatcher. const frames = p.frames as number; const start = await dol.call<number>("frame.get_count"); const target = start + frames; const pollEveryMs = 16; const overallTimeoutMs = 15000; const deadline = Date.now() + overallTimeoutMs; let now = start; while (now < target) { if (Date.now() > deadline) { throw new Error(`frame_advance(${frames}) timed out after ${overallTimeoutMs}ms (start=${start}, target=${target}, reached=${now}); emulator may be paused`); } await new Promise((r) => setTimeout(r, pollEveryMs)); now = await dol.call<number>("frame.get_count"); } return ok(`Advanced to frame ${now} (waited ${now - start} frames).`); } case "dolphin_save_state": { await dol.call("savestate.save_to_slot", [p.slot as number]); return ok(`Save state triggered for slot ${p.slot}`); } case "dolphin_load_state": { await dol.call("savestate.load_from_slot", [p.slot as number]); return ok(`Load state triggered for slot ${p.slot}`); } default: throw new Error(`Unknown tool: ${name}`); } }); - bridge/mcp_bridge.py:123-124 (helper)Python bridge side: the _save_to_slot helper that calls dolphin.savestate.save_to_slot() with the slot argument, mapped in the HANDLERS dict under 'savestate.save_to_slot'.
def _save_to_slot(p): savestate.save_to_slot(p[0]); return None def _load_from_slot(p): savestate.load_from_slot(p[0]); return None - bridge/mcp_bridge.py:163-164 (helper)HANDLERS dict registration in the Python bridge mapping 'savestate.save_to_slot' to _save_to_slot.
"savestate.save_to_slot": _save_to_slot, "savestate.load_from_slot": _load_from_slot,