retroarch_write_memory
Write a byte sequence to emulated memory at a specified address. Uses the system memory map and returns the number of bytes actually written.
Instructions
Write a byte sequence to emulated memory via the system memory map. Returns the number of bytes actually written (may be less than requested if a read-only descriptor is hit). Disables hardcore mode for the session.
RetroArch exposes two distinct memory APIs:
READ_CORE_MEMORY / WRITE_CORE_MEMORY (used by retroarch_read/write_memory): Goes through the libretro core's system memory map. Most reliable when the core advertises a memory map (most cores do). Errors with "no memory map defined" if the loaded core doesn't.
READ_CORE_RAM / WRITE_CORE_RAM (used by retroarch_read/write_ram): Uses the achievement (CHEEVOS) address space. Works even when no core memory map is defined, but addresses follow CHEEVOS conventions, not the system bus. Use when read_memory returns "no memory map defined".
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | Memory address | |
| bytes | Yes |
Implementation Reference
- src/tools.ts:211-214 (handler)Handler for the retroarch_write_memory tool. Calls ra.writeMemory() with the address and bytes from the request args, then returns a success message with the number of bytes written.
case "retroarch_write_memory": { const n = await ra.writeMemory(p.address as number, p.bytes as number[]); return ok(`Wrote ${n} bytes → ${addrHex(p.address as number)}`); } - src/tools.ts:55-71 (schema)Schema/registration definition for retroarch_write_memory. Defines name, description (with MEMORY_NOTE about core memory map), and inputSchema requiring 'address' (integer) and 'bytes' (array of integers 0-255, 1-4096 items).
{ name: "retroarch_write_memory", description: `Write a byte sequence to emulated memory via the system memory map. Returns the number of bytes actually written (may be less than requested if a read-only descriptor is hit). Disables hardcore mode for the session.\n\n${MEMORY_NOTE}`, inputSchema: { type: "object", required: ["address", "bytes"], properties: { address: { type: "integer", description: "Memory address" }, bytes: { type: "array", items: { type: "integer", minimum: 0, maximum: 255 }, minItems: 1, maxItems: 4096, }, }, }, }, - src/retroarch.ts:166-179 (helper)The RetroArchClient.writeMemory() method that performs the actual WRITE_CORE_MEMORY UDP command. Converts bytes to hex string, sends to RetroArch via UDP, and parses the response to return the count of bytes written.
/** Memory write via libretro's system memory map (preferred). */ async writeMemory(addr: number, bytes: Uint8Array | number[]): Promise<number> { if (bytes.length === 0) throw new Error("at least one byte required"); if (bytes.length > 4096) throw new Error("byte count exceeds 4096 limit"); const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(" "); const cmd = `WRITE_CORE_MEMORY 0x${addr.toString(16)} ${hex}`; const r = (await this.query(cmd)).toString().trim(); // "WRITE_CORE_MEMORY <addr> <bytes_written>" or "<addr> -1 <error>" const m = r.match(/^WRITE_CORE_MEMORY\s+\S+\s+(-?\d+)(?:\s+(.+))?$/); if (!m) throw new Error(`unexpected WRITE_CORE_MEMORY reply: ${r}`); const n = parseInt(m[1], 10); if (n < 0) throw new Error(`WRITE_CORE_MEMORY failed: ${m[2] ?? "(no error message)"}`); return n; } - src/tools.ts:176-246 (registration)The registerTools function that registers all tools including retroarch_write_memory via the MCP CallToolRequestSchema handler.
export function registerTools(server: Server, ra: RetroArchClient): 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 "retroarch_ping": { const v = await ra.getVersion(); return ok(`OK — RetroArch ${v}`); } case "retroarch_get_status": { const s = await ra.getStatus(); if (s.state === "contentless") return ok("No content loaded"); return ok( `State: ${s.state}\n` + `System: ${s.system}\n` + `Game: ${s.game}\n` + `CRC32: ${s.crc32 ?? "(none reported)"}`, ); } case "retroarch_get_config": { const v = await ra.getConfigParam(p.name as string); return ok(`${p.name} = ${v}`); } case "retroarch_read_memory": { const bytes = await ra.readMemory(p.address as number, p.length as number); const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" "); return ok(`${addrHex(p.address as number)} [${bytes.length} bytes]:\n${hex}`); } case "retroarch_write_memory": { const n = await ra.writeMemory(p.address as number, p.bytes as number[]); return ok(`Wrote ${n} bytes → ${addrHex(p.address as number)}`); } case "retroarch_read_ram": { const bytes = await ra.readRam(p.address as number, p.length as number); const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" "); return ok(`${addrHex(p.address as number)} [${bytes.length} bytes, CHEEVOS]:\n${hex}`); } case "retroarch_write_ram": { await ra.writeRam(p.address as number, p.bytes as number[]); return ok(`Wrote ${(p.bytes as number[]).length} bytes → ${addrHex(p.address as number)} (CHEEVOS, no ack)`); } case "retroarch_pause_toggle": await ra.pauseToggle(); return ok("Pause toggled"); case "retroarch_frame_advance": await ra.frameAdvance(); return ok("Advanced one frame"); case "retroarch_reset": await ra.reset(); return ok("Game reset"); case "retroarch_screenshot": await ra.screenshot(); return ok("Screenshot saved to RetroArch's configured screenshot directory"); case "retroarch_show_message": { await ra.showMessage(p.message as string); return ok(`Showed: ${p.message}`); } case "retroarch_save_state_current": await ra.saveStateCurrent(); return ok("Saved to current slot"); case "retroarch_load_state_current": await ra.loadStateCurrent(); return ok("Loaded from current slot"); case "retroarch_load_state_slot": await ra.loadStateSlot(p.slot as number); return ok(`Loaded from slot ${p.slot}`); case "retroarch_state_slot_plus": await ra.stateSlotPlus(); return ok("Incremented current slot"); case "retroarch_state_slot_minus": await ra.stateSlotMinus(); return ok("Decremented current slot"); default: throw new Error(`Unknown tool: ${name}`); } }); }