retroarch_read_memory
Read up to 4096 bytes from the emulated memory of a libretro core via its system memory map, returning an array of byte values.
Instructions
Read up to 4096 bytes from emulated memory via the loaded core's system memory map. Returns an array of byte values.
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 (in the core's address space) | |
| length | Yes |
Implementation Reference
- src/tools.ts:205-209 (handler)Tool handler for 'retroarch_read_memory': calls ra.readMemory() and formats the hex output.
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}`); } - src/tools.ts:43-53 (schema)Schema/registration definition for 'retroarch_read_memory' tool, including name, description, and inputSchema.
{ name: "retroarch_read_memory", description: `Read up to 4096 bytes from emulated memory via the loaded core's system memory map. Returns an array of byte values.\n\n${MEMORY_NOTE}`, inputSchema: { type: "object", required: ["address", "length"], properties: { address: { type: "integer", description: "Memory address (in the core's address space)" }, length: { type: "integer", minimum: 1, maximum: 4096 }, }, }, - src/tools.ts:176-246 (registration)registerTools() registers all tools via ListToolsRequestSchema and CallToolRequestSchema - the switch case dispatches to the 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}`); } }); } - src/retroarch.ts:148-155 (helper)RetroArchClient.readMemory() - sends READ_CORE_MEMORY command via UDP and parses the reply bytes via parseMemoryReply().
/** Memory read via libretro's system memory map (preferred). */ async readMemory(addr: number, length: number): Promise<Uint8Array> { if (length <= 0) throw new Error("length must be positive"); if (length > 4096) throw new Error("length exceeds 4096 byte limit"); const cmd = `READ_CORE_MEMORY 0x${addr.toString(16)} ${length}`; const r = (await this.query(cmd)).toString().trim(); return parseMemoryReply(r, "READ_CORE_MEMORY", length); } - src/retroarch.ts:230-257 (helper)parseMemoryReply() - parses the 'READ_CORE_MEMORY <addr> b1 b2 ...' response into a Uint8Array, handling failure shape.
function parseMemoryReply(reply: string, expectedCmd: string, expectedLen: number): Uint8Array { const tokens = reply.split(/\s+/); if (tokens[0] !== expectedCmd) { throw new Error(`unexpected reply prefix (got "${tokens[0]}", expected "${expectedCmd}")`); } const tail = tokens.slice(2); if (tail.length === 0) { throw new Error(`${expectedCmd} returned no bytes`); } // Failure shape: "<cmd> <addr> -1 <error_msg>" if (tail[0] === "-1") { const err = tail.slice(1).join(" ") || "(no error message)"; throw new Error(`${expectedCmd} failed: ${err}`); } const out = new Uint8Array(tail.length); for (let i = 0; i < tail.length; i++) { const v = parseInt(tail[i], 16); if (Number.isNaN(v) || v < 0 || v > 255) { throw new Error(`${expectedCmd}: malformed byte at index ${i}: "${tail[i]}"`); } out[i] = v; } if (out.length !== expectedLen) { // Not strictly an error — RetroArch may clamp at memory-region boundaries. // Caller can decide what to do with a short read. } return out; }