Skip to main content
Glama
simen
by simen

readScreen

Extract text displayed on the Commodore 64 screen by converting PETSCII screen codes to readable ASCII. Returns 25 lines of 40 characters for debugging C64 programs.

Instructions

Read the C64 screen memory and return it as interpreted text.

Converts PETSCII screen codes to readable ASCII. Returns 25 lines of 40 characters.

Use this instead of readMemory($0400) when you want to see what's displayed on screen.

Note: This reads from the current screen RAM location (may not be $0400 if the program moved it). In bitmap modes, the data won't represent text.

Options:

  • format: "full" (default) returns all 25 lines, "summary" returns only non-empty lines

  • includeRaw: Also return raw screen codes (default: false)

Related tools: readColorRam, readVicState, readMemory

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
formatNoOutput format: 'full' (all 25 lines) or 'summary' (non-empty lines only)
includeRawNoInclude raw screen codes array (default: false)

Implementation Reference

  • Main handler function for readScreen tool. Reads VIC bank and screen address registers, fetches screen memory, converts to text using screenToText, formats output per options (full/summary/raw), detects graphics mode, and provides contextual hints.
    async (args) => { try { // First get VIC bank from CIA2 const cia2Data = await client.readMemory(0xdd00, 0xdd00); const bankInfo = getVicBank(cia2Data[0]); // Get screen address from $D018 const d018Data = await client.readMemory(0xd018, 0xd018); const videoAddrs = getVideoAddresses(d018Data[0], bankInfo.baseAddress); // Read screen RAM (1000 bytes) const screenData = await client.readMemory( videoAddrs.screenAddress, videoAddrs.screenAddress + 999 ); // Convert to text const textLines = screenToText(screenData); // Find non-empty lines for summary const nonEmptyLines = textLines .map((line, idx) => ({ line: idx, content: line })) .filter((l) => l.content.trim().length > 0); const useSummaryFormat = args.format === "summary"; const response: Record<string, unknown> = { screenAddress: { value: videoAddrs.screenAddress, hex: `$${videoAddrs.screenAddress.toString(16).padStart(4, "0")}`, }, vicBank: bankInfo.bank, format: useSummaryFormat ? "summary" : "full", }; if (useSummaryFormat) { // Summary format: only non-empty lines with line numbers response.lines = nonEmptyLines.map((l) => ({ lineNumber: l.line, content: l.content, })); response.totalLines = 25; response.nonEmptyCount = nonEmptyLines.length; } else { // Full format: all 25 lines response.lines = textLines; response.summary = { nonEmptyLines: nonEmptyLines.length, preview: nonEmptyLines.length > 0 ? nonEmptyLines.slice(0, 3).map((l) => `Line ${l.line}: "${l.content}"`) : ["Screen appears empty"], }; } if (args.includeRaw) { response.raw = Array.from(screenData); } // Check graphics mode and add hint const d011Data = await client.readMemory(0xd011, 0xd011); const d016Data = await client.readMemory(0xd016, 0xd016); const graphicsMode = getGraphicsMode(d011Data[0], d016Data[0]); response.graphicsMode = graphicsMode.mode; if (graphicsMode.bitmap) { response.hint = "Warning: VIC-II is in bitmap mode - screen RAM contains bitmap data, not text."; } else if (nonEmptyLines.length === 0) { response.hint = "Screen appears empty or contains only spaces."; } else { response.hint = `Screen has ${nonEmptyLines.length} non-empty line(s). First: "${nonEmptyLines[0]?.content || ""}"`; } return formatResponse(response); } catch (error) { return formatError(error as ViceError); } }
  • Zod schema for readScreen tool inputs: optional format ('full' or 'summary') and includeRaw boolean.
    inputSchema: z.object({ format: z .enum(["full", "summary"]) .optional() .describe("Output format: 'full' (all 25 lines) or 'summary' (non-empty lines only)"), includeRaw: z .boolean() .optional() .describe("Include raw screen codes array (default: false)"), }),
  • src/index.ts:1041-1149 (registration)
    MCP server registration of the readScreen tool, including name, description, input schema, and handler reference.
    server.registerTool( "readScreen", { description: `Read the C64 screen memory and return it as interpreted text. Converts PETSCII screen codes to readable ASCII. Returns 25 lines of 40 characters. Use this instead of readMemory($0400) when you want to see what's displayed on screen. Note: This reads from the current screen RAM location (may not be $0400 if the program moved it). In bitmap modes, the data won't represent text. Options: - format: "full" (default) returns all 25 lines, "summary" returns only non-empty lines - includeRaw: Also return raw screen codes (default: false) Related tools: readColorRam, readVicState, readMemory`, inputSchema: z.object({ format: z .enum(["full", "summary"]) .optional() .describe("Output format: 'full' (all 25 lines) or 'summary' (non-empty lines only)"), includeRaw: z .boolean() .optional() .describe("Include raw screen codes array (default: false)"), }), }, async (args) => { try { // First get VIC bank from CIA2 const cia2Data = await client.readMemory(0xdd00, 0xdd00); const bankInfo = getVicBank(cia2Data[0]); // Get screen address from $D018 const d018Data = await client.readMemory(0xd018, 0xd018); const videoAddrs = getVideoAddresses(d018Data[0], bankInfo.baseAddress); // Read screen RAM (1000 bytes) const screenData = await client.readMemory( videoAddrs.screenAddress, videoAddrs.screenAddress + 999 ); // Convert to text const textLines = screenToText(screenData); // Find non-empty lines for summary const nonEmptyLines = textLines .map((line, idx) => ({ line: idx, content: line })) .filter((l) => l.content.trim().length > 0); const useSummaryFormat = args.format === "summary"; const response: Record<string, unknown> = { screenAddress: { value: videoAddrs.screenAddress, hex: `$${videoAddrs.screenAddress.toString(16).padStart(4, "0")}`, }, vicBank: bankInfo.bank, format: useSummaryFormat ? "summary" : "full", }; if (useSummaryFormat) { // Summary format: only non-empty lines with line numbers response.lines = nonEmptyLines.map((l) => ({ lineNumber: l.line, content: l.content, })); response.totalLines = 25; response.nonEmptyCount = nonEmptyLines.length; } else { // Full format: all 25 lines response.lines = textLines; response.summary = { nonEmptyLines: nonEmptyLines.length, preview: nonEmptyLines.length > 0 ? nonEmptyLines.slice(0, 3).map((l) => `Line ${l.line}: "${l.content}"`) : ["Screen appears empty"], }; } if (args.includeRaw) { response.raw = Array.from(screenData); } // Check graphics mode and add hint const d011Data = await client.readMemory(0xd011, 0xd011); const d016Data = await client.readMemory(0xd016, 0xd016); const graphicsMode = getGraphicsMode(d011Data[0], d016Data[0]); response.graphicsMode = graphicsMode.mode; if (graphicsMode.bitmap) { response.hint = "Warning: VIC-II is in bitmap mode - screen RAM contains bitmap data, not text."; } else if (nonEmptyLines.length === 0) { response.hint = "Screen appears empty or contains only spaces."; } else { response.hint = `Screen has ${nonEmptyLines.length} non-empty line(s). First: "${nonEmptyLines[0]?.content || ""}"`; } return formatResponse(response); } catch (error) { return formatError(error as ViceError); } } );
  • Core helper that converts 1000-byte screen RAM buffer to 25 lines of readable ASCII text by mapping screen codes via screenCodeToAscii.
    export function screenToText(screenData: Buffer | number[]): string[] { const data = Buffer.isBuffer(screenData) ? screenData : Buffer.from(screenData); const lines: string[] = []; for (let row = 0; row < 25; row++) { const offset = row * 40; let line = ""; for (let col = 0; col < 40; col++) { if (offset + col < data.length) { line += screenCodeToAscii(data[offset + col]); } } // Trim trailing spaces but keep the line lines.push(line.trimEnd()); } return lines; }
  • Helper to calculate screen and character generator memory addresses from VIC $D018 register and VIC bank base.
    export function getVideoAddresses( d018: number, bankBase: number ): { screenAddress: number; charAddress: number } { const screenOffset = ((d018 >> 4) & 0x0f) * 0x0400; const charOffset = ((d018 >> 1) & 0x07) * 0x0800; return { screenAddress: bankBase + screenOffset, charAddress: bankBase + charOffset, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/simen/vice-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server