disassemble
Convert 6502 machine code from Commodore 64 memory into readable assembly instructions with addresses, bytes, mnemonics, and labels for debugging.
Instructions
Disassemble 6502 machine code at a memory address.
Returns human-readable assembly instructions with:
Address and raw bytes
Mnemonic and operand
Branch target addresses (for branch instructions)
Known KERNAL/BASIC labels
Options:
address: Start address (default: current PC)
count: Number of instructions (default: 10)
Related tools: readMemory, getRegisters, step
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | No | Start address (default: current PC) | |
| count | No | Number of instructions to disassemble (default: 10) |
Implementation Reference
- src/index.ts:862-928 (handler)MCP tool handler for 'disassemble'. Determines start address (defaults to PC), reads memory from VICE client, calls disassemble utility, enhances with labels, formats assembly listing, and returns structured JSON response with _meta.async (args) => { try { // Get PC if no address specified let startAddress = args.address; if (startAddress === undefined) { const regResponse = await client.getRegisters(); const count = regResponse.body.readUInt16LE(0); let offset = 2; for (let i = 0; i < count && offset < regResponse.body.length; i++) { const id = regResponse.body[offset]; const size = regResponse.body[offset + 1]; offset += 2; if (id === 3 && size === 2) { startAddress = regResponse.body.readUInt16LE(offset); break; } offset += size; } startAddress = startAddress ?? 0; } const instructionCount = args.count || 10; // Read enough bytes (max 3 bytes per instruction) const bytesToRead = Math.min(instructionCount * 3, 0x10000 - startAddress); const endAddress = Math.min(startAddress + bytesToRead - 1, 0xffff); const memData = await client.readMemory(startAddress, endAddress); // Disassemble const instructions = disassemble(memData, startAddress, instructionCount); // Add labels for known addresses const instructionsWithLabels = instructions.map((instr) => { const label = getLabelForAddress(instr.address); const targetLabel = instr.branchTarget ? getLabelForAddress(instr.branchTarget) : undefined; return { ...instr, label, targetLabel, }; }); // Format for output const lines = instructionsWithLabels.map((instr) => { let line = `${instr.addressHex}: ${instr.bytesHex.padEnd(8)} ${instr.fullInstruction}`; if (instr.label) line += ` ; ${instr.label}`; if (instr.targetLabel) line += ` ; -> ${instr.targetLabel}`; return line; }); return formatResponse({ startAddress: { value: startAddress, hex: `$${startAddress.toString(16).padStart(4, "0")}`, }, instructionCount: instructions.length, instructions: instructionsWithLabels, listing: lines.join("\n"), hint: instructions.length > 0 && instructions[0].mnemonic === "BRK" ? "First instruction is BRK - this might be uninitialized memory or data" : `Disassembled ${instructions.length} instruction(s) from $${startAddress.toString(16).padStart(4, "0")}`, }); } catch (error) { return formatError(error as ViceError); } }
- src/index.ts:852-860 (schema)Zod input schema defining optional 'address' (0-FFFF, defaults to current PC) and 'count' (1-100 instructions, default 10) parameters for the disassemble tool.inputSchema: z.object({ address: z .number() .min(0) .max(0xffff) .optional() .describe("Start address (default: current PC)"), count: z.number().min(1).max(100).optional().describe("Number of instructions to disassemble (default: 10)"), }),
- src/index.ts:836-929 (registration)Registration of the 'disassemble' MCP tool via server.registerTool, specifying name, description, input schema, and handler function.server.registerTool( "disassemble", { description: `Disassemble 6502 machine code at a memory address. Returns human-readable assembly instructions with: - Address and raw bytes - Mnemonic and operand - Branch target addresses (for branch instructions) - Known KERNAL/BASIC labels Options: - address: Start address (default: current PC) - count: Number of instructions (default: 10) Related tools: readMemory, getRegisters, step`, inputSchema: z.object({ address: z .number() .min(0) .max(0xffff) .optional() .describe("Start address (default: current PC)"), count: z.number().min(1).max(100).optional().describe("Number of instructions to disassemble (default: 10)"), }), }, async (args) => { try { // Get PC if no address specified let startAddress = args.address; if (startAddress === undefined) { const regResponse = await client.getRegisters(); const count = regResponse.body.readUInt16LE(0); let offset = 2; for (let i = 0; i < count && offset < regResponse.body.length; i++) { const id = regResponse.body[offset]; const size = regResponse.body[offset + 1]; offset += 2; if (id === 3 && size === 2) { startAddress = regResponse.body.readUInt16LE(offset); break; } offset += size; } startAddress = startAddress ?? 0; } const instructionCount = args.count || 10; // Read enough bytes (max 3 bytes per instruction) const bytesToRead = Math.min(instructionCount * 3, 0x10000 - startAddress); const endAddress = Math.min(startAddress + bytesToRead - 1, 0xffff); const memData = await client.readMemory(startAddress, endAddress); // Disassemble const instructions = disassemble(memData, startAddress, instructionCount); // Add labels for known addresses const instructionsWithLabels = instructions.map((instr) => { const label = getLabelForAddress(instr.address); const targetLabel = instr.branchTarget ? getLabelForAddress(instr.branchTarget) : undefined; return { ...instr, label, targetLabel, }; }); // Format for output const lines = instructionsWithLabels.map((instr) => { let line = `${instr.addressHex}: ${instr.bytesHex.padEnd(8)} ${instr.fullInstruction}`; if (instr.label) line += ` ; ${instr.label}`; if (instr.targetLabel) line += ` ; -> ${instr.targetLabel}`; return line; }); return formatResponse({ startAddress: { value: startAddress, hex: `$${startAddress.toString(16).padStart(4, "0")}`, }, instructionCount: instructions.length, instructions: instructionsWithLabels, listing: lines.join("\n"), hint: instructions.length > 0 && instructions[0].mnemonic === "BRK" ? "First instruction is BRK - this might be uninitialized memory or data" : `Disassembled ${instructions.length} instruction(s) from $${startAddress.toString(16).padStart(4, "0")}`, }); } catch (error) { return formatError(error as ViceError); } } );
- src/utils/disasm.ts:255-373 (helper)Core helper function implementing 6502 disassembly. Parses opcodes using predefined OPCODES table, handles all addressing modes, formats operands, calculates branch targets, and returns structured DisassembledInstruction array.export function disassemble( data: Buffer | number[], startAddress: number, count?: number ): DisassembledInstruction[] { const bytes = Buffer.isBuffer(data) ? data : Buffer.from(data); const result: DisassembledInstruction[] = []; let offset = 0; let instructionCount = 0; while (offset < bytes.length) { if (count !== undefined && instructionCount >= count) break; const address = startAddress + offset; const opcode = bytes[offset]; const entry = OPCODES[opcode]; if (!entry) { // Unknown opcode - treat as single byte data result.push({ address, addressHex: `$${address.toString(16).padStart(4, "0")}`, bytes: [opcode], bytesHex: opcode.toString(16).padStart(2, "0"), mnemonic: "???", operand: `$${opcode.toString(16).padStart(2, "0")}`, fullInstruction: `??? $${opcode.toString(16).padStart(2, "0")}`, size: 1, }); offset++; instructionCount++; continue; } const [mnemonic, mode, size] = entry; // Check if we have enough bytes if (offset + size > bytes.length) break; const instrBytes: number[] = []; for (let i = 0; i < size; i++) { instrBytes.push(bytes[offset + i]); } const bytesHex = instrBytes.map((b) => b.toString(16).padStart(2, "0")).join(" "); let operand = ""; let branchTarget: number | undefined; let branchTargetHex: string | undefined; switch (mode) { case "impl": operand = ""; break; case "acc": operand = "A"; break; case "imm": operand = `#$${bytes[offset + 1].toString(16).padStart(2, "0")}`; break; case "zp": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")}`; break; case "zpx": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")},X`; break; case "zpy": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")},Y`; break; case "abs": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")}`; break; case "abx": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")},X`; break; case "aby": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")},Y`; break; case "ind": operand = `($${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")})`; break; case "izx": operand = `($${bytes[offset + 1].toString(16).padStart(2, "0")},X)`; break; case "izy": operand = `($${bytes[offset + 1].toString(16).padStart(2, "0")}),Y`; break; case "rel": { // Relative branch - calculate target address const displacement = bytes[offset + 1]; // Convert to signed const signed = displacement > 127 ? displacement - 256 : displacement; branchTarget = (address + 2 + signed) & 0xffff; branchTargetHex = `$${branchTarget.toString(16).padStart(4, "0")}`; operand = branchTargetHex; break; } } const fullInstruction = operand ? `${mnemonic} ${operand}` : mnemonic; result.push({ address, addressHex: `$${address.toString(16).padStart(4, "0")}`, bytes: instrBytes, bytesHex, mnemonic, operand, fullInstruction, size, branchTarget, branchTargetHex, }); offset += size; instructionCount++; } return result; }
- src/utils/disasm.ts:241-253 (schema)TypeScript interface defining the structure of a single disassembled 6502 instruction, used as output type by the disassemble function and tool handler.export interface DisassembledInstruction { address: number; addressHex: string; bytes: number[]; bytesHex: string; mnemonic: string; operand: string; fullInstruction: string; size: number; // For branches: the target address branchTarget?: number; branchTargetHex?: string; }