The documentation for ATAMON (ATAri MONitor) has been missing for over 40 years.
What you are reading is an unofficial documentation, the result of hours of work, testing, decompilation and writing from the disassembled code of ATAMON.
I hope you enjoy it ;-)
Atarinside — March 2026
The program can be found here: https://www.atarinside.com/blog/index.php/atarinside-items/atamon/
You have just loaded ATAMON, ATARI's machine language monitor. But what exactly does a monitor do?
Your Atari 800XL runs on a processor — the 6502 — which executes thousands of instructions per second. These instructions — called "machine language" — are stored in memory as numbers. ATAMON is the tool that lets you view, modify and execute these instructions directly, without going through an intermediate language like BASIC.
Concretely, ATAMON lets you:
Look inside memory: display any area of your Atari's RAM, byte by byte, to see what it contains.
Modify memory: change values directly, fix a bug in a program, or customise a game to your liking.
Disassemble a program: ATAMON translates raw machine language into readable mnemonics (such as LDA, JMP, STA…), allowing you to understand how a program works.
Execute and debug: launch a program and regain control at each BRK breakpoint, observing the processor state — essential for tracking down a bug.
Save sectors: read and rewrite disk sectors directly (via the E and W commands).
Calculate: perform additions, subtractions, logical AND, logical OR directly in hexadecimal or binary.
Who is it for? ATAMON is aimed at programmers, enthusiasts who wish to understand the inner workings of their Atari, or anyone who wants to explore and modify machine language programs. No additional hardware is required: ATAMON runs entirely on your Atari, with your standard disk drive.
How to start? From the Atari DOS menu, select L. BINARY LOAD, type D:ATAMON and press Enter. The program installs itself in memory starting at address $4000, displays its banner, and the Atari cursor indicates it is ready to receive a command. All commands are a single letter — fast and efficient.
Source file: Atamon - D7 - DXG 5724.PRO → ATAMON
Identification: *** ATAMON V1.3 - (c) 1983 by ATARI ***
Type: 6502 machine language monitor / debugger
Size: 4,404 bytes
Format: Atari XEX (multiple segments)
Load address: $4000
Run address (RUNAD): $4000
Source language: 6502 assembly
Publisher: Atari Deutschland GmbH (Atari Germany) — ATAMON is an Atari Germany product, identified by catalogue number DXG 5724
Author: Not identified — the binary contains no programmer name, only (c) 1983 by ATARI
Probable assembly tool: Atari Macro Assembler or MAC/65 (OSS) — inferred from the structure of 256-byte XEX segments (252 bytes of code + 4-byte header), a characteristic output format of native Atari assemblers from 1983
ATAMON is a professional machine language monitor developed by ATARI in 1983. It allows a programmer to:
Examine and modify RAM
Display and modify 6502 registers (program counter, stack pointer, accumulator, X, Y, flags)
Disassemble 6502 code directly in memory
Execute code and regain control at each BRK breakpoint
Read and rewrite disk sectors
Verify and transfer memory regions
Perform hexadecimal calculations
It belongs to the family of machine language monitors that accompanied 8-bit computers of the era on many platforms.
xxxxxxxxxxAddress Contents--------- --------------------------------$4000 Main entry point (RUNAD)$4000-$52B1 ATAMON code and data$4F3C-$4F42 CPU register save area$4E3C Keyboard input buffer (command line)$4F44 ASCII banner + register display format$4F8D Hex digit table + command characters$4F9D Command dispatch table$4FC8 Handler address table (vectors)$4FF9+ Internal 6502 disassembler tables
Memory footprint: approximately 4.5 KB from $4000 to $52B1.
ATAMON also sets MEMLO = $5300 to protect its code.
The entry point at $4000 uses a classic technique: the BRK vector ($0206–$0207).
xxxxxxxxxx$4000 LDA #$40 ; Install BRK handler$4002 STA $0207 ; VBREAK hi = $40$4005 LDA #$0B$4007 STA $0206 ; VBREAK lo = $0B → handler at $400B$400A BRK ; Intentionally trigger BRK; === The 6502 pushes (current address + 2) and flags onto stack, jumps via $0206 ===$400B LDA #$40 ; Reconfigure VBREAK for future use$400D STA $0207$4010 LDA #$1A$4012 STA $0206 ; New VBREAK = $401A$4015 LDA #$43 ; 'C' = "entry via startup" indicator$4017 JMP $401C ; → Save CPU state
Why this technique?
The BRK instruction automatically pushes the address of the next instruction (current address + 2) and the status register onto the stack. ATAMON then retrieves these values to build a complete snapshot of the processor state at the time of entry into the monitor.
xxxxxxxxxx$401C STA $E4 ; Save entry indicator (passed by caller)$401E PLA ; Retrieve A (pushed by OS handler before BRK)$401F STA $4F3D ; Save A$4022 STY $4F3F ; Save Y$4025 STX $4F3E ; Save X$4028 PLA ; Retrieve P (flags, from BRK stack)$4029 STA $4F40 ; Save P$402C CLC$402F PLA ; Retrieve PCL (low address byte, from BRK stack)$4030 ADC #$FF ; Subtract 1 (16-bit: BRK pushes addr+1, we return to real address)$4032 STA $4F41 ; Save adjusted PCL$4035 PLA ; Retrieve PCH (high address byte, from BRK stack)$4036 ADC #$FF ; Continue 16-bit subtraction with carry$4038 STA $4F42 ; Save adjusted PCH$403B TSX$403C STX $4F3C ; Save SP$403F LDX #$FD$4041 TXS ; Reset stack to $FD
Note on the entry mechanism: The Atari OS handler (ROM IRQ) pushes register A via PHA before calling the VBREAK vector. This is why SAVE_CPU performs 4 successive PLAs (A, P, PCL, PCH) even though the BRK instruction only pushes 3 (P, PCL, PCH).
Register save area ($4F3C–$4F42):
xxxxxxxxxx$4F3C SP Stack pointer$4F3D A Accumulator$4F3E X X register$4F3F Y Y register$4F40 P Status register (flags NV\BDIZC)$4F41 PCL Low byte of next instruction address (adjusted -1)$4F42 PCH High byte of next instruction address
This layout is confirmed by the G (Go) handler: LDA $4F3D restores A, LDA $4F40; PHA; PLP restores P, JMP ($4F41) jumps to address $4F42:$4F41.
After initialisation, ATAMON displays its banner and enters the command loop ($4064):
xxxxxxxxxx1. Reset stack to $FD2. Clear input buffer ($4E3C ← $9B)3. Open channel E: (screen editor) via CIO4. Read a line via CIO (IOCB 0)5. Find the first non-space character6. Look up this character in table $4F9D7. If found → load handler address from $4FC8/$4FC98. Execute handler via indirect RTS (push hi-1 / lo-1, RTS)9. Return to step 1
ATAMON does not display an explicit prompt: the input prompt is the Atari screen editor cursor (blinking inverse-video block), managed automatically by the OS. When this cursor is visible, ATAMON is waiting for a command.
The *R, *B, *C headers are ATAMON display prefixes, not the prompt. The asterisk * (code $2A) is emitted by ATAMON before displaying registers, followed by a letter indicating the entry context:
*C — entry via startup or G
*R — R command
*B — return on BREAK (interruption of a program)
Example register display (after R command or a BRK):
xxxxxxxxxx*RPC SP AC XR YR NV\BDIZC; 400B FB 0B 20 01 00110001
The value line always begins with ;. Meaning of each field:
| Field | What it is | Value in the example |
|---|---|---|
PC | Program Counter: address of the next instruction to be executed. This is where your program "is" in memory. | 4000 → ready to execute from $4000 |
SP | Stack Pointer: indicates where the top of the stack is. The Atari stack is at $0100–$01FF; SP=FD means the top is at $01FD, stack nearly empty. | FD → stack reset |
AC | Accumulator A: the 6502's main register, used for calculations, transfers, comparisons. | 00 |
XR | X Register: index register, often used for loops and indexed addressing. | 00 |
YR | Y Register: second index register. | 00 |
NV\BDIZC | Processor flags (P register), displayed bit by bit in binary (0 or 1). Each bit indicates a particular state of the last calculation. See the detailed description in the ; command. | 00110000 → flags I and B active |
This line updates automatically after each program return (following a BRK or G), to reflect the actual processor state at that instant.
The Atari stores the auto-run address of the last loaded program in a specific memory location at address $02E0 (the system vector called RUNAD).
If you load your game, its start address will be stored at $02E0. But if you then load ATAMON right after, ATAMON's start address ($4000) will overwrite your game's!
To work around this and use ATAMON as a detective tool, we reverse the loading order using another DOS menu option (M).
Step 1: Install the monitor "undercover"
From the DOS menu, use L to load ATAMON.
ATAMON displays on screen. Type the command X (eXit).
You return to the DOS menu, but ATAMON remains hidden in memory (from $4000 onwards).
Step 2: Load your game silently
Back in DOS, use L again to load your program, adding /N (no space after the filename) — example: MYPROG.BIN/N.
The game loads into memory and the OS writes its start address to $02E0. Because we used /N, DOS does not jump to that address, does not execute the loaded program, and keeps you in the menu.
Step 3: Wake ATAMON up
In the DOS menu, choose option M (Run at address).
Type 4000 (the address where ATAMON is sleeping) and press Enter.
The ATAMON banner appears — you are back in the monitor!
Step 4: Read the magic address
At the ATAMON prompt, display memory at address $02E0 by typing:
xxxxxxxxxxInput:M 02E0
Press BREAK immediately to stop the scrolling.
Look at the first two bytes of the displayed line.
Attention — Little Endian: the Atari stores addresses "backwards" (low byte first, then high byte).
If ATAMON displays
:02E0 00 50 ..., your program's address is$5000.If it displays
:02E0 00 2A ..., the address is$2A00.
That is it! You just need to use the G command followed by the discovered address (e.g. G 5000) to launch your program, knowing that ATAMON is ready to intercept any crash or breakpoint (BRK).
Warning: ATAMON does not have a guaranteed asynchronous interrupt function.
The only reliable method to hand control back to ATAMON in the middle of a program is to deliberately insert a software breakpoint:
Temporarily replace one byte of your program with the BRK instruction (hex code $00).
When the processor executes that $00, it will trigger a software interrupt.
ATAMON has redirected the system interrupt vector (VBREAK at $0206–$0207) when it loaded.
The processor will jump directly into ATAMON, which will save all your registers (A, X, Y, PC...) and display its prompt with *B to signal that it has intercepted a BRK.
In summary: if your program is an infinite loop with no
BRKinstruction, the BREAK key will not save you (unless your code calls the OS keyboard routines). You will need a Reset! Always remember to place your$00breakpoints strategically before launching your code withG.
The command table is stored from $4F9D. Commands are entered in uppercase. When a command takes multiple arguments, they are separated by commas (no space between arguments). A space is accepted before the first argument.
aaaa = 16-bit address in hexadecimal (e.g. 4000)
bb = 8-bit byte in hexadecimal (e.g. FF)
[...] = optional argument
G [aaaa] — Go (Execute)Starts execution at address aaaa.
Without argument: resumes from the saved program counter (PC register).
Restores all saved CPU registers before performing JMP aaaa.
Warning: G aaaa jumps directly to the given address. If that address does not contain a valid program (e.g. uninitialised RAM), the 6502 will execute garbage and the system will crash. G alone is safe: it resumes from the PC saved at the last BRK.
First use after startup: the saved PC is $400B. Typing G alone re-executes ATAMON startup.
Examples:
xInput:G ; At first launch, re-executes ATAMON startup:*** ATAMON V1.3 - (c) 1983 by ATARI ****CPC SP AC XR YR NV\BDIZC; 400B FD 00 00 00 00110000; After a BRK in your own program, resumes from the saved PCInput:G 4000 ; Starts execution at $4000 — ATAMON disappears while your program runs.; It only regains control if the program executes a BRK (breakpoint).
M aaaa — Memory (Display Memory)Displays memory starting at aaaa, 8 bytes per line in hex. Scrolls indefinitely — press BREAK to stop.
Each line: : + address + 8 hex bytes + ATASCII column on the right.
Example:
xxxxxxxxxxInput:M 4000Result (scrolls endlessly — press BREAK to stop)::4000 A9 40 8D 07 02 A9 0B 8D ©@♦ ©.♦:4008 06 02 A9 43 4C 1C 40 48 . ©CL @H:4010 68 48 8C 3F 4F A8 B9 40 hH♦?O¨¹@; ... (continues automatically)
E ssss[,aaaa[,nn]] — E (Read a Disk Sector)Reads disk sector ssss (hex) from D1: into the buffer at aaaa (default: $0680). Displays OK on success.
Optional arguments:
aaaa: destination buffer address (default: $0680)
nn: number of consecutive sectors to read (default: 01) — buffer advances by $80 (128 bytes) per sector
Caution: ssss is a sector number, not a memory address. A single-density Atari disk (720 sectors) has sector numbers $0001 to $02D0. Exceeding this range causes an I/O error.
To view the loaded sector content: type M 0680 — ATAMON displays the bytes from $0680 scrolling (press BREAK to stop after seeing the first 128 bytes of the sector).
xxxxxxxxxxInput:E 0168 ; Read sector 360 ($0168) = VTOC sector of DOS 2.xResult:OK; ATAMON displays "OK" — the sector is now at $0680Input:E 0001,0700Result:OK; Sector 1 loaded at $0700 instead of default $0680Input:E 0001,0700,02Result:OK; Sector 1 → $0700, sector 2 → $0780Input:E 0001Result on error:I/O ERROR #138
D aaaa[,bbbb] — DisassembleDisassembles 6502 code from aaaa and displays the mnemonics. Uses the internal tables at $4FF9 containing the 56 6502 mnemonics and addressing mode decoding information.
Important: the end address bbbb is syntactically accepted but ignored — ATAMON disassembles indefinitely, wraps $FFFF→$0000. Press BREAK to stop.
xxxxxxxxxxInput:D 4000,400CResult (press BREAK to stop):4000 A9 40 LDA #$404002 8D 07 02 STA $02074005 A9 0B LDA #$0B4007 8D 06 02 STA $0206400A A9 43 LDA #$43400C 4C 1C 40 JMP $401C
Each line shows: address, raw bytes in memory, then the disassembled instruction.
L aaaa,bbbb,xx — Locate Non-Matching (Find Non-Matching Bytes)Scans aaaa–bbbb and displays addresses where the byte is different from xx.
In practice: L identifies bytes that "do not match" a reference value. Useful for quickly spotting differences between two regions after a copy, or finding modified bytes in a program.
L alone → error ? — three arguments required.
Note: ATAMON has no file load command. Programs must be loaded from Atari DOS (option L. BINARY LOAD) before launching ATAMON.
xxxxxxxxxxInput:F 6000,60FF,EA ; (fills $6000–$60FF with $EA): 6000 A9 FF 8D 00 D0 4C 00 40 ; (modifies some bytes at $6000)L 6000,60FF,EA ; Scans $6000–$60FF and displays addresses where byte ≠ $EAResult (7 addresses per line — press BREAK to stop, displays *B):6000 6001 6002 6003 6004 6005 60066007*B; → addresses $6000–$6007 contain bytes ≠ $EA; Addresses where the byte equals exactly $EA are NOT displayed
W ssss[,aaaa[,nn]] — Write (Write a Disk Sector)Writes the buffer at aaaa (default: $0680) to disk sector ssss (hex).
Optional arguments: same as E — source buffer address and sector count.
Caution: overwrites the sector on disk directly — irreversible. ssss is a sector number in hex (1 to $02D0 = 720 for a single-density disk).
E and W work in tandem: E to read a sector, modify with M or :, then W to rewrite.
xxxxxxxxxxInput:E 0001 ; 1. Read sector 1 into buffer ($0680); (optionally modify the buffer with : 0680 A9 FF ...)Input:W 0001 ; 2. Rewrite the buffer to sector 1Result: OK; On error: I/O ERROR #xxx
V aaaa,bbbb,cccc — Verify (Compare Memory)Compares aaaa–bbbb with the region at cccc (memory to memory). Reports addresses where bytes differ. No changes made.
Useful to verify that a copy (made with T) matches the original, or to compare two versions of a program.
xxxxxxxxxxInput:T 6000,600F,6100 ; (copy $6000–$600F to $6100)V 6000,600F,6100 ; Compare $6000–$600F with $6100–$610FResult if all matches:OK; To see differences, modify a byte before comparing:: 6100 FF FF 00 00 00 00 00 00V 6000,600F,6100 ; $6100 and $6101 were modified → ATAMON reports $6000 and $6001Result (7 addresses per line):6000 6001; Only SOURCE addresses where bytes differ are displayed — not the values.
Note: if the destination region is uninitialised (arbitrary RAM), almost all addresses in the range will appear in the result.
F aaaa,bbbb,bb — FillFills aaaa–bbbb with byte bb. Displays OK.
Safe zone: use $6000+ (ATAMON occupies $4000–$52B1).
xxxxxxxxxxInput:F 6000,60FF,EAResult:OK; ATAMON displays "OK" then returns to prompt; $6000 to $60FF (256 bytes) now contains $EA (NOP opcode); Useful to "clean" a region or write NOPs before patching code; To verify the NOPs were written:M 6000Result::6000 EA EA EA EA EA EA EA EA nnnnnnn:6008 EA EA EA EA EA EA EA EA nnnnnnn; ... (press BREAK to stop); Right column = ATASCII of $EA (character 'n' in inverse ATASCII)
T aaaa,bbbb,cccc — Transfer (Copy Memory)Copies aaaa–bbbb to cccc (memory to memory). Performs byte-by-byte verification and reports differences. Displays OK on success.
Overlapping regions: if source and destination overlap, a naive start-to-end copy would overwrite unread source bytes. ATAMON avoids this: if destination is before the source it copies left-to-right; if destination is after the source (right overlap) it copies right-to-left. The result is always correct regardless of configuration.
Safe zone: ATAMON occupies $4000–$52B1. For tests, use a region beyond $5300 (e.g. $6000–$7FFF, free RAM).
xxxxxxxxxxInput:T 6000,60FF,6100Result: OKT 6000,6008,6004Result: (silent — overlap handled)
H aaaa,bbbb,DsearchD — Hunt (Search Memory)Searches aaaa–bbbb for byte sequence DsearchD. Displays addresses found, then OK.
D = any delimiter character. Data: hex pairs or 'string' in single quotes. Without internal single quotes, letters are interpreted as hex digits — AT = A (valid hex) + T (invalid) → corrupted result.
Search only — no replace. Three arguments minimum required (H or H aaaa,bbbb → ?).
xxxxxxxxxxInput:H 4000,52B0,"'ATARI'"Result:4D99 4E4A 4F62OKInput:H 4000,52B0,"4154415249"Result:4D99 4F62OK; Hex form gives 2 results (no self-match); quoted form gives 3 (input buffer match).
S aaaa — Scan (Real-Time Binary Monitor)Displays 4 bytes at aaaa as binary, refreshing the same line continuously. Press BREAK to stop.
S alone → ?. No single-step: insert BRK instructions manually.
Example with a fixed address:
xxxxxxxxxxInput:S 4000Result (same line refreshes continuously):10101001 01000000 10001101 00000111; The 4 bytes at $4000–$4003 displayed and updated continuously; Press BREAK to return to prompt
Example with a real-time hardware register:
xxxxxxxxxxInput:S 0014Result (line changes visibly):00000000 00000001 01001100 0000000000000000 00000001 01001101 00000000; The byte at $0016 increments continuously — proof the Atari is running; Press BREAK to stop
Particularly useful for monitoring hardware registers (POKEY, GTIA, ANTIC, RTCLOK clock) that change in real time, or observing a variable evolve during program execution.
C aaaa,bbbbbbbb — Code (Write Binary Bytes to Memory)Writes bytes at aaaa. Each byte = 8 binary digits (0 or 1), separated by commas.
C alone → ?
Note: Buggy in original V1.3 — writes
$2C(comma) instead of parsing binary arguments and returns?. The examples below describe the behaviour of the patched version only (Atamon - D7 - DXG 5724-Patched Atarinside.atr). See Fixing the C Command Bug.
xxxxxxxxxxInput:C 6000,10110001 ; Writes $B1 (binary 10110001) at $6000Result:; (no display — the Atari cursor reappears); Verification:Input:M 6000Result::6000 B1 xx xx xx xx xx xx xx [...]; → $B1 correctly written at $6000 (BREAK to stop scrolling)
Multiple bytes:
xxxxxxxxxxInput:C 6000,01001100,00000000,01100000 ; Writes $4C $00 $60 at $6000 (= JMP $6000 in machine language)
P [E|T] — Printer (Printer Control)Controls the printer channel (IOCB 6, device P1:).
P alone: closes channel → displays AUS (German: off)
P E: opens echo mode → displays EIN (German: on)
P T: data transfer (untested)
xxxxxxxxxxInput:PResult:P AUS; Printer channel currently closed (AUS = "aus" = "off" in German)Input:P EResult:EIN; Printer echo enabled (EIN = "ein" = "on" in German); All ATAMON output will also be printedInput:P ; (to disable echo)Result:P AUS
Note: this command is useful if you have a printer connected to your Atari. Without a printer,
P Ewill cause an I/O error.
R — Registers (Display Registers)Displays all saved CPU registers. Display only — no modification.
To modify registers, use the ; command (full load) or write directly to memory at $4F3C–$4F42.
xxxxxxxxxxInput:RResult:*RPC SP AC XR YR NV\BDIZC; 4000 FD 00 00 00 00110000
X — eXit (Quit to DOS)Returns to Atari DOS via JMP DOSVEC ($000A).
xxxxxxxxxxInput:X ; ATAMON exits — the Atari DOS screen reappears
Z nn,cc[,aaaa,llll] — IOCB (CIO Channel Operation)Executes an OS I/O operation on a CIO channel.
xxxxxxxxxxYour program| (via IOCB)CIO -- Central I/O <- single entry point| (via ZIOCB)Device handlers| (via DCB)SIO -- Serial I/O
The CIO provides a single entry point ($E456) for all devices: whether writing to the screen, reading the keyboard or accessing a disk, it is always the same OS routine you call. The device handler (a specific ROM or RAM routine) does the real work.
ROM-resident handlers (always available, loaded automatically at startup):
| Name | Device | Access | Note |
|---|---|---|---|
E: | Screen editor — line-by-line input with interactive editing | read+write | Uses K: and S: internally |
K: | Keyboard — direct key reading | read only | Never opened directly — used internally by E: |
S: | Graphics screen — bitmap access, DRAW and FILL commands | read+write | |
P: | Printer — on XL/XE: P1: to P8: depending on model | write only | |
C: | Cassette | read+write | Does not use SIO protocol |
Non-resident handlers (loaded into RAM at boot by DOS or a peripheral ROM):
| Name | Device | Access | Loaded by |
|---|---|---|---|
D: | Disk — DOS file manager (D1:–D8:) | read+write | DOS, at boot |
R: | 850 Interface — RS-232 serial ports (R1:–R4:) | read+write | 850 interface ROM, at boot |
T: | 1030 Modem (XM301, 835) | read+write | Modem ROM, at boot |
These non-resident handlers are added to the handler table ($031A) at startup as soon as the corresponding device is connected and powered on. If the device is absent, the handler is not loaded and any attempt to access D:, R: or T: via CIO returns an "unknown device" error.
| Ch | nn | Address | Assignment |
|---|---|---|---|
| 0 | 00 | $0340–$034F | E: screen editor — opened by the OS at startup, always available |
| 1 | 10 | $0350–$035F | free — available for any program |
| 2 | 20 | $0360–$036F | free — available for any program |
| 3 | 30 | $0370–$037F | free — available for any program |
| 4 | 40 | $0380–$038F | free — available for any program |
| 5 | 50 | $0390–$039F | free — available for any program |
| 6 | 60 | $03A0–$03AF | reserved by Atari BASIC for S: (graphics modes ≠ 0) |
| 7 | 70 | $03B0–$03BF | reserved by Atari BASIC for P:/D:/C: (printer, disk, cassette) |
Channels 1 to 5 have no fixed assignment — by design. Non-resident handlers (D:, R:, T:) do not open on a dedicated channel: the program freely chooses which channel to use for each device. For example, a program can open D:FILE.DAT on channel 1 and R1: (serial port) on channel 2 simultaneously. If BASIC is active, avoid using channels 6 and 7.
A record is a byte sequence terminated by ATASCII $9B. GET RECORD reads up to the next $9B; PUT RECORD writes and appends $9B at the end.
nn = channel offset (2 hex digits): 00 = channel 0, 10 = channel 1… 70 = channel 7
cc = CIO command (2 hex digits)
aaaa = buffer address in memory (4 digits, optional depending on command)
llll = buffer length in bytes (4 digits, optional)
Z alone or Z nn alone → ? — cc is required.
cc | Command |
|---|---|
03 | OPEN |
05 | GET RECORD (read to $9B) |
07 | GET BYTES |
09 | PUT RECORD (write + $9B) |
0B | PUT BYTES |
0C | CLOSE |
0D | STATUS |
Z copies the current channel IOCB, replaces the supplied fields (command, address, length), calls CIO, then rewrites the modified IOCB. No result is displayed — the effect is the output of the CIO operation itself.
Example — display text on screen via PUT RECORD on channel 0 (always open on E:):
xxxxxxxxxx; Step 1: write "ATARI!!" + $9B (ATASCII end-of-line) at $6000Input:: 6000 41 54 41 52 49 21 21 9B; Step 2: send 8 bytes from $6000 to screen via CIOInput:Z 00,09,6000,0008Result:ATARI!!; → text displayed directly, Atari cursor reappears
To inspect the raw content of an IOCB, use M 0340 (channel 0) or M 0350 (channel 1).
I aaaa,DdataD — Input String (Write Text to Memory)Writes text or binary data to memory at aaaa.
D = any delimiter (", /...). No space between comma and delimiter.
Inside the data:
Hex bytes: 2-digit pairs (e.g. 9B)
Literal string: 'text' (single pair of quotes — 'HELLO' not 'H'E'L'L'O')
xxxxxxxxxxInput:I 6000,"'HELLO'9B"M 6000Result::6000 48 45 4C 4C 4F 9B xx xxInput:I 6010,/FF 00 4C 00 40/M 6010Result::6010 FF 00 4C 00 40 xx xx xxInput:I 6020,"'ATARI'9B" ; ATASCII string + end-of-line byteM 6020Result::6020 41 54 41 52 49 9B xx xx
This command is particularly useful for writing error messages, ATASCII strings, or code patches directly into memory.
# — Decimal to Hexadecimal ConversionInterprets the argument as a decimal value and displays its hexadecimal equivalent.
Useful when you know an address or value in decimal and want to know its hex equivalent before using it in another command.
xxxxxxxxxxInput:# 4000Result:# 4000 = 0FA0; 4000 in decimal = $0FA0 in hexadecimalInput:# 255Result:# 255 = 00FF; 255 in decimal = $FF in hexadecimal
$ — Hexadecimal to Decimal ConversionInterprets the argument as a hexadecimal value and displays its decimal equivalent (complement of #).
xxxxxxxxxxInput:$ 1638Result:$ 1638 = 5688; $1638 in hexadecimal = 5688 in decimalInput:$ 00FFResult:$ 00FF = 0255; $FF = 255 in decimal — ATAMON requires exactly 4 hex digits; $ FF (only 2 digits) → error ?
Important: $ requires exactly 4 hexadecimal digits. Use $ 00FF not $ FF.
% — Binary InputEnters a value as 8 binary digits (0 and 1) and displays it in hexadecimal.
Useful for working directly with processor flags or any register whose bits you want to control individually — more readable than a hex byte for this type of manipulation.
xxxxxxxxxxInput:% 11001000Result: C8; $C8 = hexadecimal value of 11001000 binary (= 200 in decimal)Input:% 00110000Result: 30; $30 = flags I+B active (typical state at startup)
; aaaa BB BB BB BB XXXXXXXX — Set (Load All Registers at Once)Loads all CPU registers in a single command line.
Syntax: ; PCHL SP AC XR YR XXXXXXXX
PCHL : 4 hex digits = program counter address (e.g. 5000)
SP : stack pointer (e.g. FD)
AC : accumulator
XR : X register
YR : Y register
XXXXXXXX : 8 binary bits = P flags in order N V \ B D I Z C
Useful for restoring a precise execution context in one operation, e.g. to resume debugging a program exactly in the state it stopped.
Flag bits (left to right):
| Bit | Letter | Name | Set when... |
|---|---|---|---|
| 7 | N | Negative | Bit 7 of last result = 1 |
| 6 | V | oVerflow | Signed overflow |
| 5 | ** | (unused) | Always 1 |
| 4 | B | Break | Entry via BRK |
| 3 | D | Decimal | BCD mode (non-functional on Atari) |
| 2 | I | Interrupt disable | IRQ masked |
| 1 | Z | Zero | Last result = 0 |
| 0 | C | Carry | Carry/borrow |
Example: 00110100 = bits N=0, V=0, \=1, B=1, D=0, I=1, Z=0, C=0 — typical state at startup (I=1: interrupts disabled).
xxxxxxxxxxInput:; 5000 FD 00 00 00 00110100Result: ATAMON overwrites the typed line in place with the `;` format, without adding a new line.The displayed content is identical to the input — the command appears silent.; 5000 FD 00 00 00 00110100Verification:Input:RResult:*RPC SP AC XR YR NV\BDIZC; 5000 FD 00 00 00 00110100; → PC correctly at $5000, flags identical to those entered
! aaaa — Binary (Hexadecimal to Binary Conversion)Converts hex aaaa to 16-bit binary and displays it. Does not read memory — converts the argument itself.
Format: ! aaaa = bbbbbbbb bbbbbbbb (high byte then low byte).
Useful for visualising the individual bits of a value (hardware register read beforehand, operation result, flag value…).
xxxxxxxxxxInput:! 4000Result: ! 4000 = 01000000 00000000Input:! 00FFResult: ! 00FF = 00000000 11111111Input:! D01F ; Convert $D01F (CONSOL register address) — to inspect a hardware register,; read its value with M first, then convert with !Result: ! D01F = 11010000 00011111
* OP aaaa,bbbb — Arithmetic (Calculator)Arithmetic/logical operation on two 16-bit values. Result: RRRR C=b.
Operators: + - A (AND) O (OR) E (EOR/XOR).
* + 4000 alone → ? — both operands required.
xxxxxxxxxxInput:* + 4000,0100Result: * + 4000,0100 = 4100 C= 0Input:* - 5000,0200Result: * - 5000,0200 = 4E00 C= 1; On the 6502, carry is inverted for subtraction: C=1 = no borrow (result ≥ 0), C=0 = borrow (negative result). The opposite of addition.Input:* A FF00,0FF0Result: * A FF00,0FF0 = 0F00Input:* O 4000,0001Result: * O 4000,0001 = 4001
Useful for calculating addresses, offsets or verifying memory ranges without leaving the monitor.
: aaaa bb bb bb bb bb bb bb bb — Enter (Write Bytes to Memory)Writes 8 hex bytes at aaaa, displays the line, proposes next address. BREAK to return.
: aaaa alone → nothing.
xxxxxxxxxxInput:: 6000 A9 FF 8D 00 D0 4C 00 40Result::6000 A9 FF 8D 00 D0 4C 00 40 ) PL @:6008; ATAMON proposes next address — type 8 new bytes or press BREAK to return to prompt
To enter a large region, continue line by line: each ENTER advances 8 bytes. To enter fewer than 8 bytes, use the I command.
Limitation: : verifies each byte after writing by reading the address back. This verification fails for hardware registers (POKEY $D200–$D20F, GTIA $D000–$D01F, ANTIC $D400–$D40F, PIA $D300–$D30F) because read and write addresses map to different physical registers. To write to hardware, store machine code in RAM and run it with G.
, aaaa bb [bb ...] — Enter with DisassemblyWrites hex bytes at aaaa and displays 6502 disassembly. Number of bytes per line is free. BREAK to return.
xxxxxxxxxxInput:, 6000 A9 00Result:, 6000 A9 00 LDA #$00, 6002; ATAMON displays bytes + disassembly, then proposes continuation with `,`; Continue by typing the next bytes, BREAK to return to promptInput:, 6002 4C 00 60Result:, 6002 4C 00 60 JMP $6000, 6005; 3 bytes written — JMP $6000 (jump to $6000); Continue or press BREAK
Ideal for entering assembly code and immediately verifying that the opcodes are correct.
| Address | Local name | Role |
|---|---|---|
$DA-$DB | PTR_START | 16-bit start address (argument 1) |
$DC-$DD | PTR_END | 16-bit end address (argument 2) |
$DE-$DF | PTR_DEST | 16-bit destination address (argument 3) |
$E0 | MODE | 0 = normal mode, ≠0 = reverse mode |
$E4 | TEMP_A | Temporary / accumulator save |
$E5 | TEMP_B | Counter / temporary |
$E7 | COL_CTR | Column counter for hex display (0-6) |
$EB | TEMP_C | Display temporary |
$F2 | BUF_PTR | Pointer into buffer $4E3C |
$F3-$F4 | FP_PTR | Pointer to Math ROM floating-point area |
| Address | Description |
|---|---|
| $401C | SAVE_CPU: Saves 6502 state (accumulator, X, Y, flags, program counter, stack) |
| $4054 | PRINT_HEADER: Displays banner and register header |
| $4064 | CMD_LOOP: Main command read/dispatch loop |
| $40B5 | CLR_RANGE: Clears address registers DA-DF to zero |
| $40C5 | DUMP_8: Displays 8 bytes in hexadecimal |
| $40DB | FILTER_CHAR: Filters an ATASCII character (printable / non-printable) |
| $4127 | ADD16: Adds A to the 16-bit address DA:DB |
| $4133 | DO_CIO: Calls CIO ($E456) with X=IOCB number×16 |
| $41DD | DO_SIO_DISK: Sends an SIO disk command via DSKINV ($E453) |
| $41E8 | FIND_CMD: Searches a character in the command table $4F8D |
| $41F7 | GET_CHAR: Reads the next character from the input buffer |
| $422A | SKIP_SP: Advances buffer pointer skipping spaces |
| $423D | READ_BYTE: Reads 2 hex digits → byte in A |
| $4256 | READ_ADDR: Reads 4 hex digits → 16-bit address in A(hi) Y(lo) |
| $4280 | READ_BIN: Reads 8 binary digits → byte in A |
| $42C7 | SET_FNAME: Sets filename address in IOCB |
| $42CF | SET_BUFADDR: Sets buffer address in IOCB |
| $42E9 | OPEN_EDITOR: Opens channel 0 on E: (screen editor) |
| $4330 | PRINT_HEX8: Displays a byte as 2 hex digits |
| $4349 | NEWLINE_REGS: Newline + register display |
| $4373 | NEWLINE: Sends carriage return ($9B) on E: |
| $4399 | PRINT_CHAR: Displays an ATASCII character via CIO |
| $44A4 | FP_TO_STR: Floating-point to string conversion (Math ROM IFP+FASC) |
| $44C5 | SET_FP_PTR: Initialises floating-point pointer ($4D99) |
ATAMON uses the following OS services:
| Service | Address | Usage |
|---|---|---|
| CIO — Central I/O | $E456 (CIOV) | Screen read/write, files |
| SIO — Serial I/O disk | $E453 (DSKINV) | Disk sector access |
| Math ROM IFP | $D9AA | Integer to floating-point conversion |
| Math ROM FASC | $D8E6 | Floating-point to ASCII conversion |
| VBREAK | $0206-$0207 | BRK vector (monitor entry on breakpoint) |
IOCBs used:
IOCB 0 ($0340): Main channel to E: (editor/screen) for all user I/O.
The IOCB is configured dynamically by SET_FNAME / SET_BUFADDR / DO_CIO.
DCB structure for disk access ($0300-$030B):
$0300 DDEVIC = $31 ('1') — disk device D1:
$0301 DUNIT = 1
$0302 DCOMND = SIO command ('R'=$52 read, 'W'=$57 write)
$0303 DSTATS = direction ($40 = read, $80 = write)
$0304-$0305 DBUFLO/HI = buffer address
$030A-$030B DAUX1/2 = sector number
The string stored in memory is:
xxxxxxxxxx*** ATAMON V1.3 - (c) 1983 by ATARI ***E:P1: PC SP AC XR YR NV\BDIZC<$9B>
ATAMON points to the PC SP AC XR YR NV\BDIZC portion (at $4F71) to display the column header. The screen output (40 columns) is:
xxxxxxxxxx*** ATAMON V1.3 - (c) 1983 by ATARI ***PC SP AC XR YR NV\BDIZC; 4000 FD 00 00 00 00110000
xxxxxxxxxx$4F8D "0123456789ABCDEF" <- hex digits (index 0-15)$4F9D "RX;GM:EWFVTH#$%!I*PCSD,LZ" <- command characters
Byte pairs (address - 1) (lo, hi) for each command handler.
The dispatch uses the RTS trick: the handler address minus 1 is pushed onto the stack (hi first, then lo), then RTS is executed. The 6502 retrieves the address, increments it by 1 and jumps to the handler. This avoids a JMP table and saves a few bytes.
Example: command 'G' (5th character in $4F9D) → ATAMON reads the 2 bytes at $4FC8 + (index × 2), pushes them in reverse order, and does RTS to the handler.
ATAMON has its own mini-disassembler (command D). It uses two internal tables:
6502 Mnemonics: 3-letter encoded entries (56 official mnemonics)
Addressing modes: table of operand sizes and formats for each opcode
These tables cover only the official opcodes of the 6502. Illegal opcodes are not recognised by ATAMON's disassembler and would be displayed incorrectly.
→ See Annex B at the end of this document for the complete list of the 56 recognised mnemonics.
Note: in the ATAMON binary, addresses $4F43–$52B1 contain data (text strings, character tables, vectors). A linear disassembler interprets these bytes as instructions — many of which fall into the category of undocumented 6502 opcodes ($02 *KIL, $03 *SLO, $3A *NOP, etc.). These are false positives due to the nature of the data, not intentional use of illegal opcodes in ATAMON's code.
From Atari DOS, select L. BINARY LOAD then type D:ATAMON.
ATAMON displays its banner (*C + registers) and the Atari cursor waits for your first command.
| Key | Effect in ATAMON |
|---|---|
RETURN | Validates the current command |
BREAK | Interrupts a running program and returns control to ATAMON (via VBREAK/BRK vector) |
RESET | Hardware reset — avoid: clears memory and destroys the loaded program |
DELETE / BACK S. | Deletes the last character typed |
ESC | May cancel current input depending on CIO/editor context |
Note: ATAMON relies on CIO (screen editor E:) to read commands. The standard Atari editor functions (cursor movement, deletion) are available before validating with
RETURN.
xxxxxxxxxx*** ATAMON V1.3 - (c) 1983 by ATARI ****CPC SP AC XR YR NV\BDIZC; 4000 FD 00 00 00 00110000
xxxxxxxxxx; Display memory from $4000M 4000; Modify 8 bytes at $6000 (free zone): 6000 A9 FF 8D 00 D0 4C 00 60; Disassemble from $4000D 4000; Run program at $4000G 4000; Copy $4000-$40FF to $6000, then verifyT 4000,40FF,6000V 4000,40FF,6000; Fill $6000-$60FF with NOPs ($EA) — free zone, outside ATAMONF 6000,60FF,EA; Read disk sector 1 into buffer ($0680)E 0001; Rewrite buffer to sector 1W 0001; Calculate $4000 + $0100* + 4000,0100; Quit to DOSX
ATAMON (abbreviation for ATAri MONitor) is a machine language monitor marketed by ATARI in 1983 in Germany, the same year as the launch of the Atari 800XL.
The DXG 5724 disk also contains:
DOS.SYS: the Atari DOS 2.x disk operating system (39 sectors)
DUP.SYS: the disk management utility (42 sectors)
These three programs make up a complete disk: DOS + utilities + debugging monitor.
Analysis of the disk revealed no particular copy protection. Analysis of all 720 sectors confirmed the absence of phantom sectors (phantom_count = 0 for all sectors), intentional bad CRCs, or any other copy protection mechanism. The original ATAMON disk was not copy-protected.
| Group | Opcodes | Description |
|---|---|---|
*KIL | $02,$12,$22,$32,$42,$52,... | Locks the CPU permanently |
*NOP | $1A,$3A,$5A,$7A,$DA,$FA (1 byte) | Illegal no-operation |
*NOP | $04,$44,$64,$14,$34,$54,... (2-3 bytes) | NOP with ignored operand |
*SLO | $03,$07,$0F,$13,$17,$1B,$1F | ASL memory then ORA A |
*RLA | $23,$27,$2F,$33,$37,$3B,$3F | ROL memory then AND A |
*SRE | $43,$47,$4F,$53,$57,$5B,$5F | LSR memory then EOR A |
*RRA | $63,$67,$6F,$73,$77,$7B,$7F | ROR memory then ADC A |
*LAX | $A3,$A7,$AF,$B3,$B7,$BF | Loads A and X simultaneously |
*SAX | $83,$87,$8F,$97 | Stores A AND X |
*DCP | $C3,$C7,$CF,$D3,$D7,$DB,$DF | DEC memory then CMP A |
*ISC | $E3,$E7,$EF,$F3,$F7,$FB,$FF | INC memory then SBC A |
| Misc | $0B/$2B *ANC, $4B *ALR, $6B *ARR, $CB *SBX, $9B *TAS, $9C *SHY | Various combinations |
The 6502 has a hardware bug: JMP (addr) incorrectly reads the high byte if addr ends in $FF. Example: JMP ($10FF) reads $10FF (lo) and $1000 (hi) instead of $1100. In ATAMON, the two JMP (ind) present ($47BF JMP (DOSVEC) and $47E4 JMP ($4F41)) do not have addresses ending in $FF — no bug in this program. Note: JMP ($4F41) is the execution jump to the saved address (PCL at $4F41, PCH at $4F42).
$00–$FF)The zero page is the first 256-byte block of RAM ($00 to $FF). On the 6502, it is special for two reasons:
Instructions that access it only use one address byte instead of two → they are shorter and faster.
The 6502 can use it as an area of "extra registers": 16-bit pointers, counters, temporary variables.
The Atari OS and ATAMON use it extensively. Here are the most important locations, explained for a beginner:
| Address | OS name | What it is |
|---|---|---|
| $08 | COLCRS | Cursor column (0 to 39). The OS updates this each time you type a character. You can modify it to move the cursor horizontally. |
| $09 | ROWCRS | Cursor row (0 to 24). Same principle for the row. |
$0B–$0C | SAVMSC | Screen memory address (2 bytes). Points to the first byte of the displayed image. Modifying this area directly changes the screen in real time. |
$14–$16 | RTCLOK | Real-time clock (3 bytes). Automatically incremented 60 times per second by the OS (at each vertical interrupt). Useful for measuring time or creating delays. |
$55 | ATRACT | Screen saver. The OS increments this counter each second of keyboard inactivity. When it exceeds 127, colours fade to protect the screen. Reset to 0 to disable the screen saver. |
$6A | RAMTOP | Top of available RAM. Value in pages (×256). Indicates how far RAM is usable by programs. Modifiable to reserve high memory. |
$80–$9F | FR0 | Floating-point register 0 (6 bytes). Math ROM working area for floating-point calculations. Overwritten on every Math ROM call. |
$A0–$A5 | FR1 | Floating-point register 1 (6 bytes). Second floating-point register, used as operand in calculations. |
These addresses are reserved by ATAMON while it is loaded. Modifying them with : or , may disrupt the monitor's operation.
| Address | ATAMON name | What it is |
|---|---|---|
$DA–$DB | PTR_START | Start address of the current operation (argument 1 of M, D, F, T... commands). |
$DC–$DD | PTR_END | End address (argument 2). |
$DE–$DF | PTR_DEST | Destination address (argument 3, used by T). |
| $E0 | MODE | Display mode: 0 = normal, ≠ 0 = reverse (for M). |
| $E4 | TEMP_A | Temporary: accumulator save between two internal operations. |
| $E5 | TEMP_B | Temporary counter (internal loops). |
| $E7 | COL_CTR | Column counter for hex display (0 to 6, to align 8 bytes per line). |
| $F2 | BUF_PTR | Command buffer pointer ($4E3C). Indicates how far the input line reading has progressed. |
Tip: use
M 0000 00FFto display the entire zero page and observe how these values change with different operations.
Unofficial documentation reconstructed from binary XEX analysis and 6502 disassembly. References: De Re Atari (APX 90009), Atari 8-bit FAQ, Altirra 4.40 source, AtariWiki — 6502 Assembly Code, Unused Opcodes, OS ROM listing, Mapping the Atari, Atari Custom Display Lists (Atarimania), Display Lists Simplified (Atari magazines), ANTIC registers (xmission.com/~trevin).
ATAMON's built-in disassembler (command D) recognises exactly these 56 instructions.
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
LDA | Load Accumulator | Loads a value into accumulator A | LDA #$41 → A = 'A' |
LDX | Load X | Loads a value into register X | LDX #$00 → X = 0 (loop init) |
LDY | Load Y | Loads a value into register Y | LDY #$08 → Y = 8 (counter) |
STA | Store Accumulator | Writes A to memory | STA $D800 → sends colour to GTIA |
STX | Store X | Writes X to memory | STX $00 → saves X in zero page |
STY | Store Y | Writes Y to memory | STY $CB → saves Y in a variable |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
TAX | Transfer A to X | Copies A into X | LDA #$10 / TAX → X = 16 |
TAY | Transfer A to Y | Copies A into Y | TAY → Y ← A (to index a table) |
TXA | Transfer X to A | Copies X into A | TXA / CLC / ADC #$40 → address calculation |
TYA | Transfer Y to A | Copies Y into A | TYA / STA $CB → saves Y as value |
TSX | Transfer SP to X | Copies stack pointer SP into X | TSX → X = current stack value |
TXS | Transfer X to SP | Copies X into stack pointer SP | LDX #$FF / TXS → resets stack |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
PHA | Push Accumulator | Pushes A (save) | PHA before JSR, to preserve A |
PLA | Pull Accumulator | Pulls into A (restore) | PLA after JSR, restores A |
PHP | Push Processor status | Pushes status register P | PHP / SEI / … / PLP: critical section |
PLP | Pull Processor status | Pulls into P | PLP restores flags saved by PHP |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
ADC | Add with Carry | A ← A + operand + C | CLC / LDA #$10 / ADC #$05 → A = $15 |
SBC | Subtract with Carry | A ← A − operand − (1−C) | SEC / LDA #$10 / SBC #$03 → A = $0D |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
INC | Increment memory | +1 to a byte in memory | INC $0600 → increments score at $0600 |
INX | Increment X | X ← X + 1 | INX / CPX #$10 / BNE LOOP: 16× loop |
INY | Increment Y | Y ← Y + 1 | INY / LDA (PTR),Y: advances through a table |
DEC | Decrement memory | −1 to a byte in memory | DEC $CB → decrements a lives counter |
DEX | Decrement X | X ← X − 1 | LDX #$08 / LOOP: DEX / BNE LOOP: waits 8× |
DEY | Decrement Y | Y ← Y − 1 | DEY / BNE LOOP: loops while Y ≠ 0 |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
AND | Logical AND | A ← A AND operand (bitwise) | AND #$0F → keeps lower 4 bits (nibble) |
ORA | Logical OR | A ← A OR operand (bitwise) | ORA #$80 → forces bit 7 to 1 (inverse video) |
EOR | Exclusive OR | A ← A XOR operand (bitwise) | EOR #$FF → inverts all bits |
BIT | Bit test | Z ← A AND mem; N,V ← bits 7,6 of mem | BIT $D018 → tests a register without modifying A |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
ASL | Arithmetic Shift Left | Shifts left (× 2); bit 7 → C | ASL A → A × 2 |
LSR | Logical Shift Right | Shifts right (÷ 2); bit 0 → C | LSR A → A ÷ 2 |
ROL | Rotate Left | Left rotation through C | ROL A → × 2 + old C (16-bit multiply) |
ROR | Rotate Right | Right rotation through C | ROR A → ÷ 2 + old C in bit 7 |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
CMP | Compare A | A − operand → N,Z,C (A unchanged) | CMP #$9B / BEQ EOL → ATASCII end of line? |
CPX | Compare X | X − operand → N,Z,C | CPX #$10 / BNE LOOP → end at X=16 |
CPY | Compare Y | Y − operand → N,Z,C | CPY #$00 / BEQ DONE → end at Y=0 |
All branches are relative (signed offset of −128 to +127 bytes from the next instruction).
| Mnemonic | Full name | Condition | Example |
|---|---|---|---|
BCC | Branch if Carry Clear | C = 0 | ADC #$01 / BCC OK → no carry |
BCS | Branch if Carry Set | C = 1 | CMP #$80 / BCS HIGH → A ≥ $80 |
BEQ | Branch if Equal | Z = 1 (zero result) | CMP #$41 / BEQ FOUND → found 'A' |
BNE | Branch if Not Equal | Z = 0 (non-zero result) | DEX / BNE LOOP → classic loop |
BMI | Branch if Minus | N = 1 (bit 7 = 1) | BIT $D40B / BMI VBLANK → VBlank active |
BPL | Branch if Plus | N = 0 (bit 7 = 0) | LDA $CB / BPL OK → positive value |
BVC | Branch if oVerflow Clear | V = 0 | ADC #$01 / BVC OK → no signed overflow |
BVS | Branch if oVerflow Set | V = 1 | ADC #$40 / BVS ERR → signed overflow detected |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
JMP | Jump | Unconditional jump (absolute or indirect) | JMP $4000 → restarts ATAMON |
JSR | Jump to Subroutine | Pushes return address, then jumps | JSR $E456 → calls CIO |
RTS | Return from Subroutine | Pulls return address + 1 and jumps there | RTS → end of subroutine |
RTI | Return from Interrupt | Pulls P then program counter | RTI → end of VBI or DLI handler |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
BRK | Break | Triggers a software interrupt; pushes PC+2 and P, jumps via OS vector ($0206/$0207). This is ATAMON's central breakpoint mechanism. | BRK → immediate return to ATAMON |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
CLC | Clear Carry | C ← 0 | CLC / ADC #$01 → addition without stale carry |
SEC | Set Carry | C ← 1 | SEC / SBC #$01 → correct subtraction |
CLD | Clear Decimal | D ← 0 (disable BCD) | CLD at start of arithmetic routine |
SED | Set Decimal | D ← 1 (enable BCD) | SED → rarely useful on Atari |
CLI | Clear Interrupt disable | I ← 0 (allow IRQ) | CLI → enables VBI, POKEY, etc. |
SEI | Set Interrupt disable | I ← 1 (mask IRQ) | SEI → protects a critical section |
CLV | Clear oVerflow | V ← 0 | CLV → start from known state before signed arithmetic |
| Mnemonic | Full name | Description | Example |
|---|---|---|---|
NOP | No Operation | Does nothing; advances program counter by 1 byte. Used to neutralise an instruction or fill space in memory. | NOP / NOP / NOP to replace a 3-byte JSR $xxxx |
These opcodes do not exist in the official MOS Technology 6502 specification. They result from unintended combinations of the processor's internal logic. Their behaviour is reproduced by the ATAMON disassembler (command D), which displays them preceded by a *.
Warning: most are unstable depending on the processor revision or supply voltage. Avoid in production code; useful for analysing old programs that exploit them.
| Mnemonic | Alt. names | Description | Opcodes |
|---|---|---|---|
*KIL | JAM, HLT | Permanently locks the CPU — the data bus freezes, only a hardware RESET can restart the machine. | $02 $12 $22 $32 $42 $52 $62 $72 $92 $B2 $D2 $F2 |
These opcodes perform two operations in one: a modification in memory (or on the accumulator) then a logical/arithmetic operation with A.
| Mnemonic | Alt. names | Description | Modes | Opcodes |
|---|---|---|---|---|
*SLO | ASO | ASL on memory byte, then ORA with A | inx zp abs iny zpx aby abx | $03 $07 $0F $13 $17 $1B $1F |
*RLA | — | ROL on memory byte, then AND with A | inx zp abs iny zpx aby abx | $23 $27 $2F $33 $37 $3B $3F |
*SRE | LSE | LSR on memory byte, then EOR with A | inx zp abs iny zpx aby abx | $43 $47 $4F $53 $57 $5B $5F |
*RRA | — | ROR on memory byte, then ADC with A | inx zp abs iny zpx aby abx | $63 $67 $6F $73 $77 $7B $7F |
*DCP | DCM | DEC on memory byte, then CMP with A | inx zp abs iny zpx aby abx | $C3 $C7 $CF $D3 $D7 $DB $DF |
*ISC | ISB, INS | INC on memory byte, then SBC with A | inx zp abs iny zpx aby abx | $E3 $E7 $EF $F3 $F7 $FB $FF |
| Mnemonic | Alt. names | Description | Modes | Opcodes |
|---|---|---|---|---|
*LAX | — | Loads the same value simultaneously into A and X — equivalent to LDA + TAX in a single opcode | inx zp abs iny zpy aby | $A3 $A7 $AF $B3 $B7 $BF |
*SAX | AXS | Writes A AND X to memory (without modifying flags) | inx zp abs zpy | $83 $87 $8F $97 |
| Mnemonic | Alt. names | Description | Opcode | Stability |
|---|---|---|---|---|
*ANC | — | Immediate AND on A, then copies bit 7 into Carry flag (like ASL without modifying memory) | $0B $2B | stable |
*ALR | ASR | Immediate AND on A, then LSR on A (logical right shift) | $4B | stable |
*ARR | — | Immediate AND on A, then ROR on A — with complex effects on C and V (not identical to normal ROR) | $6B | stable |
*SBX | AXS | (A AND X) - imm → X, no borrow (affects C, Z, N) | $CB | stable |
*SBC | — | Duplicate of official SBC #imm ($E9) — identical behaviour | $EB | stable |
*ANE | XAA | A = (A OR $EE) AND X AND imm — result depends on processor | $8B | unstable |
*LXA | LAX # | (A OR $EE) AND imm → A and X — result depends on processor | $AB | unstable |
| Mnemonic | Alt. names | Description | Opcode | Stability |
|---|---|---|---|---|
*TAS | XAS, SHS | SP = A AND X; writes SP AND (addr_hi+1) to memory | $9B | unstable |
*SHY | A11Y | Writes Y AND (addr_hi+1) to memory | $9C | unstable |
*SHX | A11X | Writes X AND (addr_hi+1) to memory | $9E | unstable |
*SHA | AXA | Writes A AND X AND (addr_hi+1) to memory | $93 $9F | unstable |
*LAS | LAR | A = X = SP = memory AND SP | $BB | unstable |
These opcodes behave like NOP (do nothing visible) but consume 2 or 3 bytes — the CPU reads and ignores them.
| Mnemonic | Mode | Size | Opcodes |
|---|---|---|---|
*NOP | imp (1 byte) | 1 | $1A $3A $5A $7A $DA $FA |
*NOP | zp (2 bytes) | 2 | $04 $44 $64 |
*NOP | zpx (2 bytes) | 2 | $14 $34 $54 $74 $D4 $F4 |
*NOP | abs (3 bytes) | 3 | $0C |
*NOP | abx (3 bytes) | 3 | $1C $3C $5C $7C $DC $FC |
Symbolic addresses recognised by the ATAMON disassembler (command D). They appear in the disassembly in place of the raw hexadecimal address.
$00–$FF)| Address | Label | Description |
|---|---|---|
$000A–$000B | DOSVEC | DOS vector — jump address to DOS (2 bytes lo/hi). JMP (DOSVEC) exits the program and returns to DOS. |
$000C–$000D | DOSINI | DOS initialisation vector — called on each warm reset. |
$0010 | POKMSK | POKEY interrupt enable mask (IRQ). Each bit enables a type of IRQ (timer, serial...). |
$0012–$0014 | RTCLOK | Real-time clock (3 bytes). Incremented 60×/s by the OS VBI. |
$0020–$002B | ZIOCB | Zero IOCB — working copy of the current channel's IOCB during a CIO call. |
$002C | STATUS | Status code of the last SIO or CIO operation. |
$002E–$002F | BUFRLO/HI | I/O buffer address (2 bytes). Used by SIO DCB and CIO. |
$0030–$0031 | BFENLO/HI | End of buffer address (2 bytes). |
$0036–$0037 | BYTLO/HI | Number of bytes transferred in the last SIO/CIO call (2 bytes). |
$0200–$02FF)| Address | Label | Description |
|---|---|---|
$0200–$0201 | VDSLST | DLI vector (Display List Interrupt) — called on each DLI instruction in the ANTIC display list. |
$0202–$0203 | VPRCED | "Proceed" interrupt vector (serial input). |
$0204–$0205 | VINTER | "Interrupt" interrupt vector (serial input). |
$0206–$0207 | VBREAK | BRK vector — called when the CPU executes BRK. ATAMON installs it to capture breakpoints. |
$0208–$0209 | VKEYBD | Keyboard interrupt vector (key pressed). |
$020A–$020B | VSERIN | Serial receive vector (POKEY). |
$020C–$020D | VSEROR | Serial transmit vector (POKEY). |
$020E–$020F | VSEROC | Serial transmit complete vector. |
$0210–$0211 | VTIMR1 | POKEY timer 1 vector. |
$0212–$0213 | VTIMR2 | POKEY timer 2 vector. |
$0214–$0215 | VTIMR4 | POKEY timer 4 vector. |
$0216–$0217 | VIMIRQ | Generic IRQ vector (not used by OS). |
$0222–$0223 | VVBLKI | Immediate VBI vector — called at start of vertical blanking (~3,800 cycles available). |
$0224–$0225 | VVBLKD | Deferred VBI vector — called after SYSVBV (~20,000 cycles available, safer). |
$0226–$0227 | CDTMA1 | Software timer 1 vector (decremented by VBI). |
$0228–$0229 | CDTMA2 | Software timer 2 vector. |
$022A | CDTMF3 | Software timer 3 flag. |
$022C | CDTMF4 | Software timer 4 flag. |
$022E | BRKKEY | BREAK key flag: $00 if BREAK pressed, otherwise $80. |
$022F | SDMCTL | Shadow DMACTL — copied to $D400 (ANTIC) at each VBI. Controls screen width and DMA enable. |
$0230–$0231 | SDLSTL/H | Shadow display list (lo/hi) — copied to $D402/$D403 at VBI. Points to the active ANTIC display list. |
$023B | ATRACT | Screen saver counter. Incremented by OS each second of keyboard inactivity. Above 127, colours fade. Reset to 0 disables saver. |
$026F | GPRIOR | Shadow PRIOR — copied to $D01B (GTIA) at VBI. Determines player/missile/background display priority. |
$02C0–$02C8 | COLPF0...COLPM0 | Playfield and player colours (shadows of GTIA colour registers). |
$02C8 | COLBK | Background colour. Shadow of GTIA register $D01A. |
$02E0–$02E1 | RUNAD | Run address of loaded program (XEX). OS jumps to this address after loading. |
$02E2–$02E3 | INITAD | Initialisation address — called after each XEX segment marked INITAD. |
$02E4 | RAMSIZ | RAM size in pages (×256). Value read at startup. |
$02E5 | RAMTOP | Top of usable RAM (in pages). |
$02E7–$02E8 | MEMLO | Bottom of free RAM (2 bytes). ATAMON sets it to $5300 to protect its code. |
$02E9–$02EA | MEMTOP | Top of free RAM (2 bytes). |
$02F4 | CHBAS | Shadow CHBASE — copied to $D408 (ANTIC) at VBI. Selects the character font (high page of address). |
$0300–$035F)| Address | Label | Description |
|---|---|---|
$0300 | DDEVIC | SIO device number (e.g. $31 = disk drive D1:). |
$0301 | DUNIT | Unit number (1 to 4 for D1: to D4:). |
$0302 | DCOMND | SIO command (e.g. $52 = Read sector, $57 = Write sector). |
$0303 | DSTATS | Direction: $40 = read, $80 = write, $00 = status only. |
$0304–$0305 | DBUFLO/HI | Data buffer address (2 bytes lo/hi). |
$0306 | DTIMLO | SIO timeout in VBI units (~1/60 s). Typical value: $0F (15 VBIs). |
$0308–$0309 | DBYTLO/HI | Number of bytes to transfer (2 bytes). |
$030A–$030B | DAUX1/2 | Auxiliary parameters (e.g. sector number lo/hi for disk). |
$0340–$034F | IOCB0 | IOCB channel 0 (screen editor E: — used by ATAMON for input). |
$0350–$035F | IOCB1 | IOCB channel 1 (free). |
| Address | Label | Description |
|---|---|---|
$D000 | HPOSP0 | Horizontal position of player 0 (missile). |
$D001 | HPOSP1 | Horizontal position of player 1. |
$D002 | HPOSP2 | Horizontal position of player 2. |
$D003 | HPOSP3 | Horizontal position of player 3. |
$D004 | HPOSM0 | Horizontal position of missile 0. |
$D01B | PRIOR | Player/missile/playfield display priority. Bit 7 = special GTIA mode. |
$D01D | GRACTL | Player/missile DMA enable. |
$D01F | CONSOL | Console keys (read): bits 2-0 = START/SELECT/OPTION (0 = pressed). Write: controls internal speaker. |
| Address | Label | Description |
|---|---|---|
$D200 | AUDF1 | Audio channel 1 frequency (0–255, frequency inversely proportional). |
$D201 | AUDC1 | Channel 1 control: bits 7-4 = volume, bits 3-0 = distortion/waveform. |
$D202 | AUDF2 | Audio channel 2 frequency. |
$D203 | AUDC2 | Channel 2 control. |
$D204 | AUDF3 | Audio channel 3 frequency. |
$D205 | AUDC3 | Channel 3 control. |
$D206 | AUDF4 | Audio channel 4 frequency. |
$D207 | AUDC4 | Channel 4 control. |
$D208 | AUDCTL | Global audio control: clock frequency (64 kHz / 15 kHz / 1.79 MHz), 16-bit channels, high-pass filter. |
$D20A | STIMER | Write: resets POKEY timers to zero. |
$D20E | SEROUT | Serial output (write). |
$D20F | SKCTL | POKEY serial control: reset, sync mode. |
| Address | Label | Description |
|---|---|---|
$D300 | PORTA | Port A: joystick 1 (bits 3-0) and joystick 2 (bits 7-4). Bit = 0 if direction active. |
$D301 | PORTB | Port B: on 800XL, controls OS ROM bank (bit 0) and BASIC (bit 1). |
$D302 | PACTL | Port A control (direction bits, interrupts). |
$D303 | PBCTL | Port B control. |
| Address | Label | Description |
|---|---|---|
$D400 | DMACTL | DMA control: enable/disable DMA, screen width (narrow/normal/wide) and player/missile DMA. |
$D401 | CHACTL | Character control: inverse video, high/low line blanking of a character cell. |
$D402–$D403 | DLISTL/H | Display list address (lo/hi). ANTIC reads this list to build the image. |
$D404 | HSCROL | Fine horizontal scroll (0–15 colour clocks). |
$D405 | VSCROL | Fine vertical scroll (0–15 lines). |
$D407 | PMBASE | Player/missile base (high address byte, 1 KB or 2 KB page depending on DMACTL). |
$D408 | CHBASE | Character font base (high byte; font starts at this page × 256). |
$D409 | WSYNC | Wait for sync — write: CPU stalls until end of current scan line (raster synchronisation). |
$D40B | VCOUNT | Current scan line counter (read only, 0–131). |
$D40E | NMIEN | NMI enable: bit 7 = DLI, bit 6 = VBI. |
$D40F | NMIRES | Write: resets NMI flags. Read: identifies NMI source (bit 7 = DLI, bit 6 = VBI). |
| Address | Label | Description |
|---|---|---|
$E450 | DISKIV | Disk handler initialisation. |
$E453 | DSKINV | SIO disk call — executes the command described in the DCB ($0300+). Used by ATAMON for E and W commands. |
$E456 | CIOV | CIO (Central I/O) — main I/O entry point. Used by ATAMON to read commands and write results. |
$E459 | SIOV | SIO (Serial I/O) — low-level serial transfer. |
$E45C | SETVBV | Installs a VBI handler (immediate or deferred). |
$E45F | SYSVBV | System VBI handler (OS) — call from an immediate VBI to chain the deferred VBI. |
$E462 | XITVBV | VBI exit (end of a VBI handler). |
$E465 | SIOINV | SIO initialisation. |
$E474 | WARMSV | Warm restart — resets OS without clearing RAM. |
$E477 | COLDSV | Cold start — full reset (clears RAM). |
| Address | Label | Description |
|---|---|---|
$D800 | AFP | ASCII → floating-point (string pointed by $F3/$F4 → FR0). |
$D8E6 | FASC | Floating-point → ASCII (FR0 → string pointed by $F3/$F4). |
$D9AA | IFP | 16-bit integer → floating-point (value in $D4/$D5 → FR0). |
$D9D2 | FPI | Floating-point → 16-bit integer (FR0 → $D4/$D5). |
$DA44 | ZFR0 | Clears FR0 to zero. |
$DA60 | FSUB | Floating-point subtract: FR0 ← FR0 − FR1. |
$DA66 | FADD | Floating-point add: FR0 ← FR0 + FR1. |
$DADB | FMUL | Floating-point multiply: FR0 ← FR0 × FR1. |
$DB28 | FDIV | Floating-point divide: FR0 ← FR0 / FR1. |
$DD40 | PLYEVL | Polynomial evaluation (for sin, cos, exp...). |
$DD89 | FLD0R | Load FR0 from address in (X,Y). |
$DD98 | FLD1R | Load FR1 from address in (X,Y). |
$DDA7 | FSTOR | Store FR0 to address in (X,Y). |
$DDB6 | FMOVE | FR1 ← FR0. |
Summary of hardware registers for the four custom chips of the Atari 800XL, with their typical usage in programming.
$D000–$D01F)| Address | Name | R/W | Usage |
|---|---|---|---|
$D000–$D003 | HPOSPn | W | Horizontal position of 4 players (sprites) |
$D004–$D007 | HPOSMn | W | Horizontal position of 4 missiles |
$D008–$D00B | SIZEPn | W | Player size (1×, 2×, 4×) |
$D00C | SIZEM | W | Missile size |
$D00D–$D010 | GRAFPn | W | Player n shape (8 bits = 8 pixels) |
$D011 | GRAFM | W | 4 missile shapes (2 bits each) |
$D012–$D015 | COLPMn | W | Player n colour |
$D016–$D019 | COLPFn | W | Playfield 0–3 colours |
$D01A | COLBK | W | Background colour |
$D01B | PRIOR | W | Player/playfield priority, GTIA mode |
$D01C | VDELAY | W | Vertical delay missile/player (fine resolution) |
$D01D | GRACTL | W | Player/missile DMA enable |
$D01E | HITCLR | W | Write: clears collision registers |
$D01F | CONSOL | R/W | Read: START/SELECT/OPTION keys. Write: speaker |
$D200–$D20F)| Address | Name | R/W | Usage |
|---|---|---|---|
$D200–$D207 | AUDFn / AUDCn | W | Frequency and control of 4 audio channels (AUDF/AUDC pairs) |
$D208 | AUDCTL | W | Global audio control (clock, 16-bit channels, filters) |
$D209 | STIMER | W | Resets timers |
$D20A | SKRES | W | Resets serial status register |
$D20B | POTGO | W | Starts potentiometer (paddle) conversion |
$D200–$D207 | POTn | R | 8 potentiometer (paddle) values |
$D208 | ALLPOT | R | Status of ongoing potentiometer conversions |
$D209 | KBCODE | R | Code of pressed key (internal ATASCII) |
$D20A | RANDOM | R | Random number generator (read only) |
$D20D | SERIN | R | Received serial data |
$D20E | SEROUT | W | Serial data to transmit |
$D20F | SKCTL | W | Serial control (init, sync, two-tone mode) |
$D300–$D303)| Address | Name | R/W | Usage |
|---|---|---|---|
$D300 | PORTA | R/W | Joystick 1 (bits 3-0) and joystick 2 (bits 7-4) |
$D301 | PORTB | R/W | On 800XL: OS ROM bank (bit 0) and BASIC (bit 1) |
$D302 | PACTL | W | Port A direction bits and interrupt control |
$D303 | PBCTL | W | Port B direction bits and interrupt control |
$D400–$D40F)| Address | Name | R/W | Usage |
|---|---|---|---|
$D400 | DMACTL | W | Screen width (40/48 colours), ANTIC/player DMA active |
$D401 | CHACTL | W | Inverse video, character blanking |
$D402–$D403 | DLISTL/H | W | Display list address (lo/hi) |
$D404 | HSCROL | W | Fine horizontal scroll (0–15 colour clocks) |
$D405 | VSCROL | W | Fine vertical scroll (0–15 lines) |
$D407 | PMBASE | W | Player/missile base page |
$D408 | CHBASE | W | Character font base page |
$D409 | WSYNC | W | Wait for end of raster line (CPU/video synchronisation) |
$D40A | VSCROL | W | (read VCOUNT at address $D40B) |
$D40B | VCOUNT | R | Current raster line counter |
$D40C–$D40D | PENH/V | R | Light pen horizontal/vertical position |
$D40E | NMIEN | W | NMI enable: bit 7 = DLI, bit 6 = VBI |
$D40F | NMIRES | R/W | Read: NMI source. Write: reset NMI flags |
The C command in ATAMON V1.3 has been broken since its publication in 1983. Regardless of the syntax used, it always returns ? and writes nothing to memory.
Cause: In ATAMON's code, every command that takes multiple arguments must, between each argument, call routine $43AE (SEPARATOR_CHECK). This routine reads and consumes the comma , that separates arguments in the command line.
The C command handler (at address $4BE9) correctly reads the destination address via JSR $4256, but then calls directly JSR $4280 (READ_BIN, which reads 8 bits) without going through $43AE. Result: READ_BIN reads the comma , (code $2C) instead of the first binary digit 0 or 1. The comma is written to memory ($2C = 44), then the program encounters an error and displays ?.
All other multi-argument commands (F, T, V, L, H, I...) correctly call $43AE between their arguments. C is the only one that omits it.
The fix consists of adding the missing call to $43AE without modifying the existing code. Since ATAMON fully occupies the $4000–$52B1 range, a small intermediate program ("trampoline") is installed in the first free byte after the code: $52B2.
Step 1 — Redirect the READ_BIN call
At address $4BF6, replace:
xxxxxxxxxx20 80 42 JSR $4280 ; READ_BIN (direct call — bug)
with:
xxxxxxxxxx20 B2 52 JSR $52B2 ; -> trampoline
Step 2 — Install the trampoline at $52B2
The inner loop of the C handler (at $4C05) itself calls $43AE to consume the comma between subsequent bytes. The trampoline must therefore only consume the comma on the first call (when counter $E8 is 0), and call READ_BIN directly for subsequent calls.
$52B2: A5 E8 LDA $E8 ; byte counter (0 = first call)$52B4: D0 08 BNE $52BE ; if 2nd byte or later -> READ_BIN directly$52B6: 20 AE 43 JSR $43AE ; 1st byte: consume comma address,data$52B9: 90 03 BCC $52BE ; comma found (C=0) -> READ_BIN$52BB: 4C EC 4B JMP $4BEC ; no comma (C=1) -> error ?$52BE: 4C 80 42 JMP $4280 ; call READ_BIN
Without this test on $E8, the trampoline would try to consume a comma already read by $4C05, would read the first binary bit instead, and would return ? from the second byte onwards.
The file Atamon - D7 - DXG 5724-Patched Atarinside.atr provided with this document contains ATAMON with this fix already applied. Load it in your emulator or copy it to an Atari disk in place of the original.
To regenerate the patch manually from the original XEX, the Python script patch_atamon.py automates both steps above.
MOS Technology 6502 Microprocessor Data Sheet — MOS Technology Inc., 1975. Official reference for the 56 official instructions, addressing modes and registers.
6502.org — Reference: http://www.6502.org/tutorials/6502opcodes.html — detailed description of official opcodes with cycles and flags.
Extra Instructions Of The 65XX Series CPU ("The Illegal Opcodes") — community documentation of undocumented 6502 opcodes, including KIL, SLO, RLA, SRE, RRA, DCP, ISC, LAX, SAX, ANC, ALR, ARR, ANE, LXA, SBX, TAS, SHA, SHY, SHX, LAS. Available at https://www.oxyron.de/html/opcodes02.html.
De Re Atari — Atari Inc., 1982. APX 90009 - By Chris Crawford, Lane Winner, Jim Cox, Amy Chen, Jim Dunion, Kathleen Pitta, Bob Fraser, Gus Makreas - Fundamental technical reference covering hardware architecture, custom chips (GTIA, POKEY, PIA, ANTIC), the operating system and ROM routines. Available on AtariWiki: https://atariwiki.org/wiki/Wiki.jsp?page=De%20Re%20Atari and Atarinside (French): https://www.atarinside.com/blog/index.php/books/de-re-atari-francais/
Mapping the Atari — Ian Chadwick, COMPUTE! Publications, 1983. Exhaustive memory map of the Atari: zero page, page 2, OS ROM, Math ROM, hardware registers. Available on AtariWiki: https://atariwiki.org/wiki/Wiki.jsp?page=Mapping+the+Atari
Atari OS Source Listing — Atari Inc. Atari 400/800/XL OS source with labels and comments. Available at https://archive.org/details/atarioperatingsystemsourcelisting/mode/2up and https://www.atarimania.com/documents/atari-400-800-operating-system-source-listing.pdf
AtariWiki: https://atariwiki.org/wiki/Wiki.jsp?page=6502%20Assembly%20Code — community encyclopaedia covering hardware, OS, DOS, file formats and Atari development tools.
.PRO Disk FormatAtari Preservation Project — PRO Format (P2/P3): https://www.a8preservation.com/#/guides/pro/explorer — documentation of the .PRO disk image format used to preserve Atari floppy disks with their copy protections (phantom sectors, FDC status bytes).
Altirra — Atari 8-bit emulator by Avery Lee. https://www.virtualdub.org/altirra.html — used to empirically verify the behaviour of each ATAMON command described in this document.