Appendix F: XEX File Format¶
Every X65 binary is a .xex file — boot ROMs, user applications, monitor load payloads. The format is derived from the Atari 8-bit .xex executable format, with three X65-specific extensions for the wider 24-bit address space and the firmware monitor’s INFO command. This appendix documents what is on disk, byte for byte. The build pipeline that produces these files is covered in Chapter 9; the OS/816 shell that loads user-application .xex files is in Chapter 14.
Overall Structure¶
A .xex is a sequence of independently-addressed segments. Each segment names a memory range and supplies the bytes to load there. Segments are processed strictly in file order, and a later segment may overwrite bytes written by an earlier one. All multi-byte fields are little-endian (low byte first).
The file opens with bank $00 selected as the implicit load target. Bank-switch sentinel segments (described below) change which bank subsequent segments target.
File Header¶
The first two bytes of the file must be $FF $FF. The same $FF $FF magic may appear between later segments and is silently skipped by the loader; emitting it is optional after the first segment.
Segment Layout¶
Offset |
Bytes |
Field |
Notes |
|---|---|---|---|
0–1 |
2 |
Header |
|
2–3 |
2 |
Start address A |
Little-endian, |
4–5 |
2 |
End address B |
Little-endian, A ≤ B ≤ |
6… |
B−A+1 |
Payload |
Loaded into A…B of the current bank |
After the payload, another segment may follow immediately (with or without a leading $FFFF) until end-of-file.
Bank-Switch Sentinel — $FFFE Segment¶
The original Atari format addresses 64 KB; the X65 lives in a 24-bit space, so the loader needs a way to retarget banks mid-file.
A segment whose start address and end address are both $FFFE is not a one-byte write to address $FFFE. Instead, the loader reads the segment’s single payload byte and uses it as the bank number for all following segments.
The bank starts at $00 when the file is opened. A typical multi-bank .xex opens with bank-zero segments, then emits an $FFFE-sentinel with payload $01 to switch banks, and the segments that follow now write into bank $01.
FF FE FF FE ; start=$FFFE, end=$FFFE → sentinel
01 ; bank for subsequent segments
HELP/INFO Segment — $FC00 in Bank $00¶
A segment whose start address is $FC00 and whose target bank is $00 is not loaded into RAM. The loader skips its payload entirely.
This region carries user-visible help/info text, displayed by the NORTH firmware monitor’s info command (see Chapter 6). The convention exists because $FC00–$FCFF is reserved for expansion-slot MMIO (see Appendix A) — writing to it during load would be both pointless and unsafe — so the loader repurposes that address as a “this segment is metadata, skip it” signal.
In the words of the firmware help text:
LOAD and INFO read ROM files from a USB drive. A ROM file contains both ASCII information for the user and binary information for the system. The ROM file is an Atari XEX-derived binary format, and may contain many chunks loaded into different memory areas. The chunk marked for load into the area starting at
$FC00in bank$00is special: it carries HELP/INFO text and is not loaded into RAM. TheINFOmonitor command displays it.
A $FFFE-sentinel that has switched the current bank to a non-zero value does not cause subsequent $FC00 segments to be skipped — only bank $00 triggers the skip.
Auto-Run via the Reset Vector — $FFFC–$FFFD¶
When the entire file has been processed, the loader checks whether bytes have been written to both $FFFC and $FFFD (the 65C816 reset vector low and high bytes, in any segment). If so, it releases the CPU reset; the CPU then begins execution at the address held in $FFFC–$FFFD.
The idiomatic auto-run footer is therefore a small segment writing the entry-point address into the reset vector:
FF FC FF FD ; segment loading into $FFFC..$FFFD
00 20 ; reset vector = $2000
Files that omit the reset-vector load successfully but do not start running — a useful posture for libraries, overlays, and data-only .xex images.
RUNAD / INITAD Compatibility Note¶
The classic Atari XEX vectors RUNAD ($02E0–$02E1) and INITAD ($02E2–$02E3) are not honored by the X65 loader. A .xex that relies on writing those addresses to start or hook execution will load its bytes successfully but will not run. Port such code by emitting an extra segment that writes the entry point to $FFFC–$FFFD instead.
Worked Example¶
A minimal auto-running program — load $42 into A at address $2000, then run from there:
FF FF ; magic (required, first segment only mandatory)
00 20 01 20 ; start=$2000, end=$2001
A9 42 ; payload: LDA #$42
FF FC FF FD ; reset-vector segment header
00 20 ; reset vector = $2000 → run after load
Inspecting .xex Files in a Hex Editor¶
The ImHex hex editor (https://imhex.werwolv.net) ships an XEX pattern in its built-in Content Store that highlights segment headers, start/end addresses, and payload boundaries directly on the hex view. Install it via Help → Content Store → search “XEX” → Install; opening any .xex in ImHex afterwards turns the file into a colour-coded structural view, which is the fastest way to confirm that a build matches the layout described above. The pattern follows the Atari layout — the X65-specific $FFFE bank-switch sentinel and $FC00/bank-$00 HELP/INFO segments appear as ordinary segments in the colouring; their meaning is implicit from their addresses.