Programming the MEGA65 — Chapter 11: MEGA65 ROM Guide

Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor

Introduction

The MEGA65's ROM holds the KERNAL, the screen editor, BASIC 65, the DOS, the monitor, the character sets, and a complete set of C64 ROMs for C64 mode, in a 128KB image. Unlike the C64's, it is still changing: new releases appear regularly, fixing bugs and adding features.

That makes the question "which parts can I call?" more important than it was on the C64. On a C64, programmers called almost any ROM routine they found, because the ROM never changed. On the MEGA65, a routine that is not part of an official interface may move in the next release. The MEGA65 team treats the KERNAL jump table as officially supported, along with certain documented facts about the memory layout such as $2001 being the start of BASIC (Dan Sanderson, MEGA65 Digest). Almost everything else is internal.

This chapter therefore concentrates on the stable interfaces:

There is no complete map of BASIC 65's internal routines here, of the kind West gave for the C64's BASIC. Such a map would be out of date within months, and code built on it would break with it. Where earlier chapters needed BASIC internals, such as the USR function's conversion routines, the safest course is to check the documentation for the ROM version in use.

ROM Layout and Versions

The ROM is a single 128KB image, loaded from the SD card into chip RAM at $20000-$3FFFF and write-protected, as Chapter 5 described. Because it is an ordinary file, the installed version is simply whichever image the SD card holds, and different machines may run different versions.

Where things are

The ROM is a 128KB image that the MEGA65 holds at $20000-$3FFFF, in banks 2 and 3. Its arrangement comes from the C65's. The C65 brings its system ROM into the CPU's window using both banking mechanisms: $D030 for some regions, and MAP for the rest (MEGA65 ROM issue #62). ROM 920422 is laid out as follows, according to the ROM's build files:

ROM address Contents
$20000-$23FFF DOS
$24000-$28FFF BASIC 65 (first part)
$29000-$29FFF Character set A
$2A000-$2BFFF C64-mode BASIC
$2C000-$2CFFF DOS and KERNAL code
$2D000-$2DFFF Character set C
$2E000-$2FFFF End of C64-mode BASIC, then the C64-mode KERNAL
$30000-$31FFF Machine-language monitor
$32000-$3BFFF BASIC 65
$3C000-$3CFFF Unused
$3D000-$3DFFF Character set B
$3E000-$3EFFF Screen editor
$3F000-$3FFFF KERNAL, including the jump table and hardware vectors

Treat this table as orientation only. The ROM's internal arrangement is not an interface, MEGA65 releases have reorganised it, and code should reach the ROM only through the jump table and vectors described in the following sections.

Identifying the version

MEGA65 ROM versions are six-digit numbers such as 920422, shown in the start-up banner. Release and beta ROMs are numbered 92xxxx and are meant to stay compatible with existing software. Development ROMs, numbered 99xxxx, are built separately and may include experimental features that break existing software. The two series run side by side; most owners should run a 92xxxx ROM (MEGA65 ROM source, README; owners can request access through the MEGA65 ROM issue repository).

The ROM version matters most to programs that depend on behaviour added in a particular release, such as the BASIC 65 features dated in Chapter 6. Such programs should say which version they need. Programs that keep to the interfaces in this chapter rarely need to check at all.

As on every Commodore machine since the C64, the byte at $FF80 is set aside for a KERNAL version number (pagetable.com). On the MEGA65 it is always $FF, the C65's "not released" value, so it says nothing about the ROM version (MEGA65 ROM source, jump table).

From ROM 920417, the KERNAL's VERSIONQ routine at $FF2F returns the six-digit ROM version shown in the banner (see Extended entries). It loads the version into Q, the 32-bit register made up of A, X, Y and Z, as a little-endian binary integer: 920422 is $000E0B66. A single STQ stores it in four bytes:

        jsr $ff2f       ; VERSIONQ
        stq version     ; four bytes, low byte first

On a ROM older than 920417, $FF2F is not VERSIONQ, and calling it is likely to crash the program. At the time of writing, 920417 and later are beta releases; the latest stable ROM is 920413 (MEGA65 ROM change log).

The version is also stored as text. The ROM image, which the MEGA65 holds at $20000, has the letter V followed by the six ASCII digits at $20016-$2001C, for example V920422. Reading this string is discouraged: its address is not part of the ROM's supported interface, and it may not work with future ROM versions. A program that must also run on ROMs older than 920417 can fall back on it, accepting that risk.

The KERNAL Jump Table

The jump table is a list of JMP instructions at the top of the KERNAL, each at a fixed address. Programs call the fixed address, and the JMP there leads to wherever the routine currently lives. When a new ROM moves a routine, only the JMP changes.

The MEGA65-mode KERNAL derives from the C65's, which in turn derives from the C128's. Its table has two parts: the standard entries shared with the C64, and an extended block below them inherited from the C128 by way of the C65, to which the MEGA65 has added entries of its own.

Standard entries

These 39 entries occupy the same addresses as on the C64, and most do the same jobs (pagetable.com). Four addresses hold different routines on the MEGA65: KEY, MONEXIT, ScanStopKey and SCRORG take the places of the C64's SCNKEY, SETTMO, UDTIM and SCREEN. MEMTOP, MEMBOT and IOBASE are reserved. Some routines that keep their C64 names take different arguments: the clock routines work in BCD hours, minutes, seconds and tenths. PLOT keeps the C64's registers, with the line in X and the column in Y, but measures them from the top-left corner of the current window rather than of the screen. The inputs and outputs in the table come from the MEGA65 Book's KERNAL Jump Table appendix, checked against the ROM source. Where they disagree, as for PLOT, the table follows the ROM.

X/Y means a 16-bit value with the low byte in X and the high byte in Y. C is the carry flag: routines that can fail return with C=1 and an error code in A. A dash means the routine takes no input or returns no result. Unless the table says otherwise, assume a KERNAL routine changes every register and flag. All of them expect the KERNAL's normal memory map, with the base page register B set to $00.

Address Name Purpose Input Output
$FF81 CINT Initialise screen editor and video — (IRQs disabled) —
$FF84 IOINIT Initialise I/O devices and the timer interrupt — (IRQs disabled) —
$FF87 RAMTAS Test and initialise RAM — —
$FF8A RESTOR Restore the default KERNAL vectors — (IRQs disabled) —
$FF8D VECTOR Read or set the KERNAL vectors C=1 to read, with X/Y = destination; C=0 to set, with X/Y = source. The table is 56 bytes (32 before ROM 920400) —
$FF90 SETMSG Enable or disable KERNAL messages A = message flags (bit 7 = control messages, bit 6 = error messages) —
$FF93 SECOND Send a secondary address after LISTEN A = secondary address —
$FF96 TKSA Send a secondary address after TALK A = secondary address —
$FF99 MEMTOP Reserved, do not use — —
$FF9C MEMBOT Reserved, do not use — —
$FF9F KEY Handle special keys (Mega+Shift, No Scroll, Ctrl-S); called by the IRQ handler — —
$FFA2 MONEXIT The monitor’s exit to BASIC; not for use by programs — —
$FFA5 ACPTR Read a byte from the serial bus — A = byte received
$FFA8 CIOUT Send a byte to the serial bus A = byte to send —
$FFAB UNTLK Send UNTALK — —
$FFAE UNLSN Send UNLISTEN — —
$FFB1 LISTEN Send LISTEN A = device number (0–31) —
$FFB4 TALK Send TALK A = device number (0–31) —
$FFB7 READST Read the I/O status byte — A = status byte
$FFBA SETLFS Set logical file, device and secondary address A = logical file, X = device, Y = secondary address ($FF for none) —
$FFBD SETNAM Set the file name A = name length (0 for none), X/Y = address of name (bank set by SETBNK) —
$FFC0 OPEN Open a logical file — (call SETBNK, SETLFS and SETNAM first) C=1 on error, A = error code
$FFC3 CLOSE Close a logical file A = logical file; C=1 to close without sending a close command C=1 on error, A = error code
$FFC6 CHKIN Set the input channel X = logical file C=1 on error, A = error code
$FFC9 CHKOUT Set the output channel X = logical file C=1 on error, A = error code
$FFCC CLRCHN Restore default channels — —
$FFCF BASIN Input a character (CHRIN) — C=0: A = byte read; C=1: A = error code
$FFD2 BSOUT Output a character (CHROUT) A = byte to write C=1 on error, A = error code
$FFD5 LOAD Load or verify a file A = 0 to load, 1 to verify (add $40 for raw mode); X/Y = load address (bank set by SETBNK) C=1 on error, A = error code; X/Y = end address
$FFD8 SAVE Save memory to a file A = base-page address of a pointer to the start; X/Y = end address + 1 C=1 on error, A = error code
$FFDB SETTIM Set the system clock Y = hours (0–23), X = minutes, A = seconds, all in BCD; Z = tenths (0–9) —
$FFDE RDTIM Read the system clock — Y = hours (0–23), X = minutes, A = seconds, all in BCD; Z = tenths (0–9)
$FFE1 STOP Test the STOP key — Z=1 if STOP is pressed
$FFE4 GETIN Get a character from the keyboard buffer — A = character; C=1 on error
$FFE7 CLALL Close all files — —
$FFEA ScanStopKey Test the STOP key without closing channels or clearing the keyboard buffer — A = $7F and N=0 if STOP is pressed; A = $FF and N=1 if not
$FFED SCRORG Return the size and position of the screen window — X = window width, Y = window height; A/Z = screen-memory address of the window’s top-left corner (low/high); C=0 for 80 columns, C=1 for 40
$FFF0 PLOT Read or set the cursor position C=1 to read; C=0 to set, with X = line, Y = column, relative to the window origin X = line, Y = column, relative to the window origin (when read); C=1 on error (when set)
$FFF3 IOBASE Reserved, do not use; on the C65 it always returns $D000 — —

SCRORG is worth using on the MEGA65, where the text screen may be 40 or 80 columns wide and 25 or 50 rows high, and the WINDOW command can shrink the area the editor uses. A program that asks SCRORG for the window's size and position works in every mode.

Extended entries

The C65 kept most of the C128's additions, dropped three that made sense only on a C128 (DMA_CALL, DLCHR and GETCFG), and added MONITOR_CALL. The removals moved many entries to new addresses, so they do not match the C128 (pagetable.com). The MEGA65 has extended the table downwards: the entries from $FF2F to $FF4A are its own additions and are not on the C65. They arrived between ROM 920398 and 920417, as the table notes, so a program that uses one should check the ROM version first; KEYLOCKS was renamed SYSFLAGS in ROM 920418 (MEGA65 ROM change log).

Address Name Purpose Input Output
$FF2F VERSIONQ Load the ROM version number into Q (ROM 920417 and later) — Q (A, X, Y, Z) = ROM version, as a 32-bit little-endian integer
$FF32 RESET_RUN Reset the computer, optionally running the program in memory (ROM 920416 and later) A = 0 for a KERNAL warm boot, 1 for a Hypervisor reset, 2 for a warm boot that then RUNs the BASIC program at $2001 Does not return
$FF35 CURSOR Enable or disable the blinking cursor (ROM 920415 and later) C=0 to enable, C=1 to disable —
$FF38 — Reserved, do not use — —
$FF3B SAVEFL Save to a file, with flags (ROM 920412 and later) As SAVE, plus Z = flags (bit 6 = raw mode: omit the two-byte address header) C=1 on error, A = error code
$FF3E — Reserved, do not use — —
$FF41 GETIO Read the current input and output devices (ROM 920398 and later) — X = input device (0 = keyboard), Y = output device (3 = screen)
$FF44 GETLFS Read the current logical file, device and secondary address (ROM 920398 and later) — A = logical file, X = device, Y = secondary address ($FF for none)
$FF47 SYSFLAGS Read or set the system flags (keyboard and mode locks) (ROM 920398 and later) C=1 to read; C=0 to set, with A = flags (bit 4 = GO64 locked, bit 5 = raw function keys, bit 6 = No Scroll locked, bit 7 = Mega+Shift locked; preserve bits 0–3) A = flags (when read)
$FF4A ADDKEY Add a character to the keyboard buffer (ROM 920398 and later) A = PETSCII code C=1 if the buffer is full
$FF4D SPIN_SPOUT Set up fast serial for I/O C=0 to set up for input (SPINP), C=1 for output (SPOUT) —
$FF50 CLOSE_ALL Close all files on a device A = device number (0–31) —
$FF53 C64MODE Switch to C64 mode — (call with JMP) Does not return
$FF56 MONITOR_CALL Enter the machine-language monitor — (call with JMP) Does not return
$FF59 BOOT_CALL Boot a program from disk — (call with JMP) Does not return
$FF5C PHOENIX Initialise function cartridges — (call with JMP) Does not return
$FF5F LKUPLA Find a free logical file number A = logical file C=0 if found: A = logical file, X = device, Y = secondary address ($FF for none); C=1 if not found
$FF62 LKUPSA Find a free secondary address Y = secondary address C=0 if found: A = logical file, X = device, Y = secondary address ($FF for none); C=1 if not found
$FF65 SWAPPER Switch screen width — —
$FF68 PFKEY Program a function key A = base-page address of a pointer to the string (low, high, bank); X = key number (1–16); Y = string length C=1 if the update failed
$FF6B SETBNK Set the banks for LOAD and SAVE data and the file name Banks 0–5: A = data bank, X = file name bank. 28-bit addresses: A = $80 + address bits 24–27, Y = bits 16–23; X = $80 + file name bits 24–27, Z = bits 16–23 —
$FF6E JSRFAR Call a routine in another bank $02 = bank (0–5), $03 = address high, $04 = address low; $05–$09 = status, A, X, Y and Z to pass to the routine. In banks 1–5 the target must lie in $2000–$7FFF Status, A, X, Y and Z as the routine left them
$FF71 JMPFAR Jump to a routine in another bank As JSRFAR (call with JMP) Does not return
$FF74 LDA_FAR Load a byte from another bank X = base-page address of a pointer, Y = index, Z = bank A = byte read
$FF77 STA_FAR Store a byte in another bank A = byte to store; X = base-page address of a pointer, Y = index, Z = bank —
$FF7A CMP_FAR Compare A with a byte in another bank A = byte to compare; X = base-page address of a pointer, Y = index, Z = bank Flags set as by CMP
$FF7D PRIMM Print the zero-terminated string that follows the JSR A zero-terminated string of up to 255 bytes, including the zero, after the JSR All registers preserved

The last six are not among the entries the C65 moved, so they keep their C128 addresses. PRIMM at $FF7D is confirmed on the MEGA65 (MEGA65 Digest). The far-memory calls do not take the same arguments as on the C128: LDA_FAR, STA_FAR and CMP_FAR take the bank in the Z register, and JSRFAR and JMPFAR take their target and register values from base page locations $02-$09. The inputs and outputs come from the MEGA65 Book's KERNAL Jump Table appendix.

Where the far calls run

The far-call routines don't run from ROM. They exist to change the memory map, and code running in the KERNAL at $E000-$FFFF could map itself out halfway through. So the KERNAL's start-up routine copies them into bank 0 RAM from $0380, below $2000, where they stay visible whatever the mapping, and the jump-table entries at $FF6E (JSRFAR) and $FF71 (JMPFAR) simply jump to those copies. Disassembling $FF6E therefore shows a JMP into page 3 (MEGA65 ROM source, install and the RAM-copied far-call block).

Always call them through the jump table, never at their RAM addresses. The jump-table addresses are the ROM's promise; where the copies land is an internal detail, decided by the size of the code ahead of them, and it changes when that code changes. It has already happened between two released ROMs. For SYS TO, ROM 920414 added a MAP-offset version of JSRFAR, which BASIC reaches through the reserved entry $FF38, and four bytes of code to support it. On the stable ROM 920413, JMPFAR's copy starts at $03BB; on 920414 and later it starts at $03BF, and the MAP-offset entry at $0397 does not exist at all (MEGA65 ROM change log; MEGA65 ROM source). A program that called $03BB directly would work on the stable ROM and jump into the middle of an instruction on every beta since, and the reverse holds for a program built against a beta. Calling $FF71 works on both. As with every jump-table call, the KERNAL ROM must be mapped at $E000-$FFFF to reach the entry.

Example: printing with PRIMM

PRIMM reads the bytes after the JSR as a string, prints them, and returns to the instruction after the terminating zero:

        jsr $ff7d       ; PRIMM
        .text "HELLO, MEGA65", 13, 0
        rts             ; execution continues here

It is the shortest way to print a fixed message from machine language, and it needs no pointer setup.

RAM Vectors

The jump table lets programs call the KERNAL. The RAM vectors let programs change what the KERNAL does. Each is a two-byte address in RAM that the KERNAL jumps through at a key moment, such as handling an interrupt or outputting a character. Pointing a vector at your own routine inserts that routine into the KERNAL's work.

The standard vector table

The C65's vector table at $0314-$0333 follows the C64 and C128 layout, with the vector the C64 left unused now serving the monitor (pagetable.com). The MEGA65 KERNAL inherits it from the C65.

Address Name Jumped through for
$0314 CINV IRQ handler
$0316 CBINV BRK handler
$0318 NMINV NMI handler
$031A IOPEN OPEN
$031C ICLOSE CLOSE
$031E ICHKIN CHKIN
$0320 ICKOUT CHKOUT
$0322 ICLRCH CLRCHN
$0324 IBASIN BASIN
$0326 IBSOUT BSOUT
$0328 ISTOP STOP
$032A IGETIN GETIN
$032C ICLALL CLALL
$032E EXMON Monitor command input
$0330 ILOAD LOAD
$0332 ISAVE SAVE

The C65 followed this table with two more groups of two-byte vectors. From $0334 to $0343 are the low-level serial bus routines, in the order TALK, LISTEN, TKSA, SECOND, ACPTR, CIOUT, UNTLK and UNLSN. From $0344 to $034B are four screen editor vectors: CTLVEC, SHFVEC and ESCVEC, through which the editor handles control, shifted and escape characters, and KEYSCAN, which is called after a key is read and before it is buffered.

The VECTOR call at $FF8D copies the vectors to or from memory, and RESTOR at $FF8A resets the KERNAL's own vectors to the defaults. From ROM 920400, VECTOR copies all three groups, a 56-byte table from $0314 to $034B; earlier ROMs copied only the first 32 bytes. Give VECTOR room for 56 bytes (MEGA65 ROM change log). Using VECTOR to save the table before changing it, and to put it back afterwards, is safer than editing individual entries.

Chaining into the IRQ

A cooperative program that wants code to run on every system interrupt, such as a background music player, points CINV at its own routine and ends that routine by jumping to the old handler, so that keyboard scanning and the clock keep working:

install sei
        lda $0314
        sta oldirq
        lda $0315
        sta oldirq+1
        lda #<myirq
        sta $0314
        lda #>myirq
        sta $0315
        cli
        rts

myirq   inc $d020       ; the routine's own work
        jmp (oldirq)    ; continue with the KERNAL handler

oldirq  .word 0

Before jumping through CINV, the KERNAL saves A, X, Y and Z and the base page register B, then sets B to $00. A handler that ends by jumping on to the KERNAL's handler, as above, can therefore use all of them freely: the KERNAL restores them before its RTI. Changing the vector inside SEI and CLI prevents an interrupt from arriving when only one byte has been written.

Hardware vectors

Separate from the RAM vectors are the CPU's own vectors at the top of the KERNAL ROM: NMI at $FFFA, RESET at $FFFC and IRQ/BRK at $FFFE. They point into the KERNAL; for NMI and IRQ, it then jumps through the RAM vectors. A takeover program that switches the KERNAL out replaces them with its own, as shown in Chapter 8.

BASIC vectors and USR

BASIC 65 has its own vectors, which belong to BASIC rather than the KERNAL. The one programs most often need is the USR vector, two bytes at $02F8, low byte first, read in the system bank: BANK 128 : WPOKE $02F8, $1800 points USR at a routine at $1800 (MEGA65 Book, p. B-285). Chapter 9 describes writing USR routines. Other BASIC vectors and work areas are internal and should not be assumed from the C64 or C128.

Hypervisor Services

The Hypervisor's services, documented by the MEGA65 Book for Hyppo version 1.2, are reached through trap registers at $D640-$D67F. The service number goes in A, other inputs in X, Y and Z, and the write is followed by CLV, because the CPU may or may not execute the byte after the trap when the service returns. On return, carry set means success; carry clear means failure, with an error code in A. Registers not used for results are preserved, and the trap registers exist only in the MEGA65 I/O personalities, the normal one and the Ethernet one (MEGA65 Book, pp. N-4, N-5; mega65-core, gs4510.vhdl).

Most services use $D640. A few use other trap registers: $D641 for ROM write-protection, $D642 for configuration and freeze-slot services, $D643 for serial monitor output, and $D67F for a program to freeze itself (MEGA65 Book, Appendix N).

Services at $D640

The table lists the services the Book documents. "Not in 1.2" marks services the Book defines but describes as not implemented in Hyppo 1.2; they return error $82 there.

The Input column lists the registers a service reads besides A, and anything that must be set up first. A page is the high byte of a page-aligned address between $0000 and $7E00. The Output column lists results besides the carry flag. "Not documented" means the Book gives no details for a service it does not implement.

A Service Status in 1.2 Purpose Input Output
$00 getversion Yes Hyppo version in A.X, HDOS version in Y.Z — A.X = Hyppo version, Y.Z = HDOS version (major.minor)
$02 getdefaultdrive Yes SD card partition selected at boot — A = drive number
$04 getcurrentdrive Yes Current drive — A = drive number
$06 selectdrive Yes Select a drive (SD card partition) X = drive number —
$08 getdrivesize Not in 1.2 Size of a drive Not documented Not documented
$0A getcwd Not in 1.2 Current directory Not documented Not documented
$0C chdir Yes Change directory — (find the directory’s entry first with findfile, findfirst, findnext or readdir) —
$0E mkdir Not in 1.2 Create a directory Not documented Not documented
$10 rmdir Not in 1.2 Remove a directory Not documented Not documented
$12 opendir Yes Open a directory for reading — (select the drive and directory first) A = file descriptor
$14 readdir Yes Read the next directory entry X = file descriptor; Y = page of an 87-byte destination Directory entry at $YY00: long name, 8.3 name, start cluster, length and attributes
$16 closedir Yes Close a directory X = file descriptor —
$18 openfile Yes Open a file — (find the file first) A = file descriptor
$1A readfile Yes Read a sector of the open file — (reads the current open file) X/Y = number of bytes read
$1C writefile Yes Write a sector to the open file — (writes the current open file; the Book’s description is incomplete) —
$1E mkfile Partly implemented Create a file in the current directory, as one contiguous block, so a D81 image made this way can be mounted. Limited: 8.3 names only, and a name without an extension may cause problems; normal files only; space is allocated in 512KB steps, and a nearly full card is not handled properly (mega65-core, Hyppo dos.asm, release 0.97 and development) Name set first with setname; Z:Y:X = size in bytes, up to 16MB C=0 and A = $8D if the file exists, or $8E if the directory is full
$20 closefile Yes Close a file X = file descriptor —
$22 closeall Yes Close all files and directories — —
$24 seekfile Not in 1.2 Seek within a file Not documented Not documented
$26 rmfile Yes Delete a file — (find the file first) —
$28 fstat Not in 1.2 File information Not documented Not documented
$2A rename Not in 1.2 Rename a file Not documented Not documented
$2C filedate Not in 1.2 File date Not documented Not documented
$2E setname Yes Set the current file name Y = page of the name —
$30 findfirst Yes Find the first file matching the name — (set the name with setname first) A = file descriptor for the directory, which may need closing with closedir
$32 findnext Yes Find the next matching file — (call findfirst first) —
$34 findfile Yes Find a file by name — (set the name with setname first) —
$36 loadfile Yes Load the named file to $00ZZYYXX X/Y/Z = load address, low/middle/high; set the name with setname first —
$38 geterrorcode Yes Error code of the last failed service — A = error code of the last failed service
$3A setup_transfer_area Yes Set the page used to pass data Y = page of the transfer area —
$3C cdrootdir Yes Change to the root directory X = drive number —
$3E loadfile_attic Yes Load the named file to $08ZZYYXX X/Y/Z = load address in attic RAM, low/middle/high; set the name with setname first —
$40 d81attach0 Yes, deprecated from 1.3 (use attach) Attach a D81 image as drive 0 — (set the image name with setname first) —
$42 d81detach Yes Detach disk images — —
$44 d81write_en Yes Allow writing to attached images — —
$46 d81attach1 Yes, deprecated from 1.3 (use attach) Attach a D81 image as drive 1 — (set the image name with setname first) —
$48 get_proc_desc Yes Read the process descriptor Y = page of a 256-byte destination Process descriptor at $YY00: task ID and name, attached disk images, file descriptors
$4A attach Yes Attach a disk image X bit 0 = drive (0 or 1); bit 7 = 0 to attach, 1 to detach; when detaching, bit 1 = both drives and bit 6 = do not reattach real drives. Set the image name with setname before attaching —
$50-$6E task and message services Not in 1.2 Create, switch and exit tasks; send and receive messages Not documented Not documented
$70 toggle_rom_writeprotect Yes Toggle write-protection of the ROM in banks 2 and 3 — A bit 2 set if the ROM area is write-protected
$72 toggle_force_4502 Yes Toggle the CPU's 6502 personality — A bit 5 set for the 45GS02 personality, clear for 6502
$74 get_mapping Yes Copy the MAP state to six bytes at page Y Y = page of a 6-byte destination At $YY00: MAPLO and MAPHI (big-endian words), then their megabyte offsets
$76 set_mapping Yes Set the MAP state from memory Y = page of a 6-byte source, laid out as for get_mapping — (execution continues with the new mapping)
$7C serial_monitor_write Yes Write to the serial monitor Y = ASCII character —
$7E reset Yes Warm boot the MEGA65 — Does not return

Several services have extra requirements. setname needs the name to start on a page boundary between $0000 and $7E00, be shorter than 63 characters and end with a zero byte; it is ASCII, and Hyppo does not convert from PETSCII. Services that pass data use the transfer area, which setname also sets to the name's page. The file services work on the FAT32 file system of the SD cards, not on floppy disks or disk images (MEGA65 Book, pp. N-10, N-38).

Versions

Hyppo is updated with the MEGA65 core, independently of the ROM. In August 2026, for example, a pull request was opened against mega65-core to fill in getdisksize (the Book's getdrivesize), getcwd, mkdir, rmdir, seekfile and rename, finish fstat, and add long file name support to mkfile; it had not been merged at the time of writing (mega65-core PR #946). A program should therefore call getversion first and treat error $82, not implemented, as an expected result for newer services rather than as a failure of the program.

Error codes

The Book lists these error codes (MEGA65 Book, pp. N-7, N-8). Its table also has lower-numbered codes for partition, argument and read/write errors, such as $10 for an invalid address, which setname returns when Y is above $7E, and it notes that the list may not be exhaustive.

Code Meaning
$80 No such drive
$81 Name too long
$82 Service not implemented
$83 File larger than 16MB
$84 Too many open files
$85 Invalid cluster
$86 Is a directory
$87 Not a directory
$88 File not found
$89 Invalid or closed file descriptor
$8A Disk image has the wrong length
$8B Disk image is fragmented on the SD card
$8C No space on the SD card
$8D File already exists
$8E Directory full
$8F Image already attached to the other drive
$FF End of file or directory, or no such trap

Writing ROM-Independent Code

A MEGA65 program may be run on a ROM newer, or older, than the one it was written on. The habits below keep it working across versions.

Practice Why
Call the KERNAL only through the jump table Routine addresses move between releases; jump table addresses do not
Change KERNAL behaviour through RAM vectors, using VECTOR and RESTOR The vector table is an interface; patching ROM code is not
Avoid BASIC 65 internals BASIC's routines, work areas and token handling change from release to release
Use the Hypervisor for SD card files Its services are documented and its version can be checked
Check versions only where a feature demands it Version checks for features you do not use only add ways to fail
Use a private base page, as in Chapter 9 BASIC 65 and KERNAL zero-page usage is private to the ROM
Prefer flat addressing and DMA to MAP in cooperative code The ROM's map settings are internal, and the CPU cannot read MAP back directly; only the Hypervisor's get_mapping can

Zero page and system RAM

The C64's zero page was so well documented that programmers routinely borrowed its locations. On the MEGA65 that is risky. The MEGA65 team keeps what it calls a private API contract over the KERNAL's zero page, so its use can change (MEGA65 ROM issue #80). The same issue records a ROM release that began using a zero-page location in C64 mode that third-party software also used, breaking that software.

A routine that needs base page storage should therefore bring its own, by pointing B at a page it owns. A routine that must share zero page should restrict itself to locations the ROM documentation for its target version describes as free.

When a ROM routine is unavoidable

Sometimes a program genuinely needs an internal ROM routine, such as BASIC's floating-point conversion for USR. In that case:

  1. Keep every such address in one place in the source, labelled with the ROM version it was taken from.
  2. Have the program check the ROM version at start-up and refuse to run, with a clear message, on a version it was not tested with.
  3. Note the dependency in the program's documentation, so users know to look for an update after upgrading their ROM.

This turns a silent crash after a ROM update into a clear message, which is the best that can be done for code outside the official interfaces.

Summary

Chapter 12 turns to the VIC-IV and graphics.