Skip to main content
Glama
simen
by simen

readScreen

Extract displayed text from Commodore 64 screen memory by converting PETSCII codes to readable ASCII. Get 25 lines of 40 characters to view program output during debugging.

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

  • The core handler function for the readScreen tool. Determines current VIC-II bank and screen memory address by reading CIA2 ($DD00) and VIC $D018 registers. Reads 1000 bytes of screen RAM. Converts PETSCII screen codes to readable ASCII text using screenToText helper. Handles full/summary formats, includes raw data option, detects graphics mode, and adds 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 input schema defining parameters for readScreen: format (full/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:1053-1161 (registration)
    MCP server tool registration for readScreen, including detailed description, input schema validation, and handler function 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); } } );
  • Helper function that converts raw screen RAM buffer (1000 bytes) into 25 lines x 40 chars of ASCII text by mapping C64 screen codes to printable characters 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; }
  • Calculates VIC-II memory bank (0-3) and base address ($0000, $4000, $8000, $C000) from CIA #2 port A register value ($DD00). Essential for locating screen RAM.
    export function getVicBank(cia2PortA: number): { bank: number; baseAddress: number } { // CIA2 port A bits 0-1 (inverted) select the bank const bankBits = (~cia2PortA) & 0x03; return { bank: bankBits, baseAddress: bankBits * 0x4000, }; }

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