dolphin_load_state
Restores a saved emulator state from a specific slot, overwriting all live state. Use it after saving state to revert experiments or undo changes.
Instructions
PURPOSE: Load a previously-saved state from the given slot, replacing all live state. USAGE: Counterpart to dolphin_save_state. The classic snapshot/experiment/restore loop: save_state(N) → run experiment → load_state(N) to undo. BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state. The state file MUST come from the same game disc and same Dolphin build that produced it; loading an incompatible state typically crashes the core (no recovery without restarting Dolphin). RETURNS: 'Load 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:609-612 (handler)The MCP tool handler that executes 'dolphin_load_state' — calls the Python bridge via DolphinClient with 'savestate.load_from_slot' and returns a success message.
case "dolphin_load_state": { await dol.call("savestate.load_from_slot", [p.slot as number]); return ok(`Load state triggered for slot ${p.slot}`); } - src/tools.ts:458-473 (schema)The tool registration/schema definition for 'dolphin_load_state' — defines name, description, and inputSchema requiring a 'slot' integer (0-255).
{ name: "dolphin_load_state", description: "PURPOSE: Load a previously-saved state from the given slot, replacing all live state. " + "USAGE: Counterpart to dolphin_save_state. The classic snapshot/experiment/restore loop: save_state(N) → run experiment → load_state(N) to undo. " + "BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state. The state file MUST come from the same game disc and same Dolphin build that produced it; loading an incompatible state typically crashes the core (no recovery without restarting Dolphin). " + "RETURNS: 'Load 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-618 (registration)The registration function that wires up all tools (including 'dolphin_load_state') into the MCP server via ListToolsRequestSchema and CallToolRequestSchema handlers.
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)The Python bridge helper function that actually calls Dolphin's savestate.load_from_slot API — invoked remotely by the Node.js handler.
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