aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile31
-rw-r--r--README.txt89
-rw-r--r--col80.atasm6
-rw-r--r--col80.dasm10
-rw-r--r--col80.obin0 -> 12229 bytes
-rw-r--r--col80.s19
-rw-r--r--col80.xexbin0 -> 1429 bytes
-rw-r--r--col80_dosini_seg.s9
-rw-r--r--col80_header_seg.atasm9
-rw-r--r--col80_header_seg.s6
-rw-r--r--col80_main.atasm898
-rw-r--r--col80_main.s895
-rw-r--r--col80_orig.xexbin0 -> 1429 bytes
-rw-r--r--col80_runad_seg.s6
-rw-r--r--col80_startaddr.s2
-rwxr-xr-xdasm2atasm362
16 files changed, 2342 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d5983c2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,31 @@
+
+SRCFILES=col80_dosini_seg.s col80_header_seg.s col80_main.s \
+ col80_runad_seg.s col80_startaddr.s
+
+all: col80.xex check
+
+
+col80.xex:
+ $(MAKE) dasm_build || $(MAKE) ca65_build || $(MAKE) atasm_build
+
+dasm_build: $(SRCFILES) col80.dasm
+ dasm col80.dasm -f3 -ocol80.xex
+
+ca65_build: $(SRCFILES) col80.s
+ ca65 -t atari col80.s
+ ld65 -t atari -o col80.xex col80.o
+
+atasm_build: $(SRCFILES) col80.atasm
+ perl dasm2atasm col80_header_seg.s col80_header_seg.atasm
+ perl dasm2atasm col80_main.s col80_main.atasm
+ atasm -r -ocol80.xex col80.atasm
+
+check: col80.xex
+ @if cmp col80.xex col80_orig.xex; then \
+ echo "OK: New binary is identical to original" ;\
+ else \
+ echo "BAD: New binary differs from original" ;\
+ fi
+
+clean:
+ rm -f *.o col80_header_seg.atasm col80_main.atasm col80.xex
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..720a1cc
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,89 @@
+
+COL80 is a software 80-column driver for the Atari 8-bit computer. It
+uses GRAPHICS 8 with 4x8 pixel character cells, and replaces the OS
+ROM's E: handler.
+
+The file is found in various Atari archives, under various names such
+as COL80.COM, COL80E.COM, COL80HND.COM. The original author and date of
+publication are unknown.
+
+I've disassembled the binary and labelled/commented the source with
+(hopefully) meaningful names. The resulting sources can be reassembled
+with the DASM, ca65, or Atasm assemblers, which will result in a binary
+that compares as identical to the original.
+
+If you have one of the supported assemblers available on your path,
+plus a "make" utility (GNU, BSD, or probably Microsoft's nmake are OK),
+you can use the provided Makefile to rebuild the binary (including your
+own modified version, if you're into that sort of thing).
+
+File list:
+
+README.txt - you're reading it now
+
+Makefile - the usual
+
+col80_main.s - The actual source code for COL80
+
+col80_dosini_seg.s, col80_header_seg.s, col80_runad_seg.s, and
+col80_startaddr.s - Include files, used to build the multi-segment Atari
+binary load format object file.
+
+col80.s, col80.dasm, col80.atasm - Top-level wrappers for the various
+assemblers, which include the other files in the proper order and using
+the proper syntax for the assembler being used.
+
+Modification Ideas:
+
+Implement the missing control character actions. COL80 only does EOL and
+the clear-screen code (125), and the others (arrows, delete/insert/bell)
+are just printed in their graphical form.
+
+The original COL80 loads the driver code at $7A00, so it'll be compatible
+with BASIC, or other cartridge software. I've built a version org'ed at
+$9A00, which works great with disk-only software and gives an extra 8K
+of available RAM (change START_ADDR in col80_startaddr.s).
+
+It should be possible to use 4x7 or 4x6 character cells instead of
+4x8. The font would of course need to be redesigned, and the characters
+would be even smaller than they are now, but this would give you 27 or
+32 rows of text on screen (or more, if you expand the display by a few
+scanlines). With a good green or amber monitor and luma-only output,
+this could be usable.
+
+Instead of inverse video for characters 128-255, could do an expanded
+international character set (ISO Latin-1). Add a UTF-8 parser and you've
+got Unicode on the Atari!
+
+Add a VT100/ANSI escape-sequence parser. Could render actual underlined
+characters, and bold as inverse video. ANSI color codes we can't easily
+do, but could at least strip them out.
+
+Squeeze the driver down to save RAM. Use the standard E: buffer in page 5,
+move the code up so it ends just before the GR.8 display list, eliminate
+the code that installs the handler as X: and checks for the SELECT key
+being held down... get rid of the margin beep. Use RMARGN in zero page
+instead of right_margin at $7C00, move the other COL80 variables to
+page zero. Eliminate the lookup tables, if they can be replaced with
+code that takes up less space and calculates the values on the fly.
+The current driver code is 3 pages long; it might be possible to squish
+it into 2 pages... like, say, page 6 and the cassette buffer, or make it
+auto-relocate itself to MEMLO like Bob-Verter does. Using a 4x6 or 4x7
+font shrinks the font table, too... another thing to do would be to get
+rid of the clear_screen routine (replace with a call to init_graphics_8)
+
+For XL/XE machines, turn COL80 into an OS patch. For modified 400/800
+machines with RAM at $C000-CFFF, move COL80 there. For 130XEs, use an
+extended RAM bank for the driver, and another bank for the screen RAM
+(separate ANTIC/CPU access mode, won't work on most upgraded 800XLs). Just
+keep a tiny stub driver in main RAM, that switches in the driver's bank
+and jumps to it.
+
+Make a COL64 driver (like the SpartaDOS X CON64.SYS). Use 5x8 characters
+for 64 columns (or 5x6 for 64x32). Probably this would count more as
+a rewrite than a modification. The font would have to be stored one
+character per 8 bytes (take up twice as much space), and lots of shifting
+would have to happen when writing to the screen (slow)... Could also
+do 56 columns (7 pixel wide), and actually use the ROM font (just cut
+off the high bit, and for 56x27 also cut off the bottom scanline).
+
diff --git a/col80.atasm b/col80.atasm
new file mode 100644
index 0000000..3fdd27a
--- /dev/null
+++ b/col80.atasm
@@ -0,0 +1,6 @@
+
+ .include "col80_startaddr.s"
+ .include "col80_header_seg.atasm"
+ .include "col80_main.atasm"
+ .include "col80_dosini_seg.s"
+ .include "col80_runad_seg.s"
diff --git a/col80.dasm b/col80.dasm
new file mode 100644
index 0000000..b3a1de2
--- /dev/null
+++ b/col80.dasm
@@ -0,0 +1,10 @@
+
+ processor 6502 ; dasm
+
+ .include col80_startaddr.s
+ .include col80_header_seg.s
+ .include col80_main.s
+ .include col80_dosini_seg.s
+
+ .include col80_runad_seg.s
+
diff --git a/col80.o b/col80.o
new file mode 100644
index 0000000..68b9135
--- /dev/null
+++ b/col80.o
Binary files differ
diff --git a/col80.s b/col80.s
new file mode 100644
index 0000000..9e17272
--- /dev/null
+++ b/col80.s
@@ -0,0 +1,19 @@
+
+; ca65 wrapper for building col80
+
+ .setcpu "6502"
+
+ .segment "EXEHDR"
+ .include "col80_startaddr.s"
+ .include "col80_header_seg.s"
+
+ .segment "CODE"
+ .include "col80_main.s"
+
+ .include "col80_dosini_seg.s"
+
+ .segment "AUTOSTRT"
+ .include "col80_runad_seg.s"
+
+ .segment "ZPSAVE"
+ ; nothing to see here, just shutting up ld65's warning
diff --git a/col80.xex b/col80.xex
new file mode 100644
index 0000000..f5f9548
--- /dev/null
+++ b/col80.xex
Binary files differ
diff --git a/col80_dosini_seg.s b/col80_dosini_seg.s
new file mode 100644
index 0000000..43f35f5
--- /dev/null
+++ b/col80_dosini_seg.s
@@ -0,0 +1,9 @@
+
+; Second segment of the file loads at $0C (aka DOSINI), and just contains
+; the address of dosini_entry_point
+
+ .word $FFFF ; unnecessary, though the original file had it
+ .word $000C ; DOSINI
+ .word $000D
+ .word dosini_entry_point
+
diff --git a/col80_header_seg.atasm b/col80_header_seg.atasm
new file mode 100644
index 0000000..2a42620
--- /dev/null
+++ b/col80_header_seg.atasm
@@ -0,0 +1,9 @@
+;;; Converted from DASM syntax with command:
+; dasm2atasm col80_header_seg.s col80_header_seg.atasm
+
+
+ *= START_ADDRESS-6
+ .word $FFFF
+ .word START_ADDRESS
+ .word END_ADDRESS
+
diff --git a/col80_header_seg.s b/col80_header_seg.s
new file mode 100644
index 0000000..2f96ad9
--- /dev/null
+++ b/col80_header_seg.s
@@ -0,0 +1,6 @@
+
+ .org START_ADDRESS-6
+ .word $FFFF
+ .word START_ADDRESS
+ .word END_ADDRESS
+
diff --git a/col80_main.atasm b/col80_main.atasm
new file mode 100644
index 0000000..080ef8a
--- /dev/null
+++ b/col80_main.atasm
@@ -0,0 +1,898 @@
+;;; Converted from DASM syntax with command:
+; dasm2atasm col80_main.s col80_main.atasm
+
+; COL80.COM, aka COL80E.COM, aka COL80HND.COM
+; (and probably several other names)
+
+; Original author unknown
+; License unknown
+; Disassembly and comments by Urchlay
+
+; This is a widely-distributed software 80-column driver for the Atari
+; 8-bit computers. It replaces the OS's E: driver, and uses GRAPHICS 8
+; for display, with 4x8 pixel character cells.
+
+; Disassembly was done with da65, with many iterations of "edit the
+; .info file, disassemble again", and the results were tweaked by hand
+; into something assemblable by dasm (and fairly compatible with other
+; assemblers).
+
+
+; START_ADDRESS is defined in col80_startaddr.s
+ *= START_ADDRESS
+
+; ----------------------------------------------------------------------------
+; Zero page labels (OS equates)
+
+DOSINI = $000C
+ICAX1Z = $002A
+ICAX2Z = $002B
+TMPCHR = $0050
+LMARGN = $0052
+ROWCRS = $0054
+COLCRS = $0055
+DINDEX = $0057
+SAVMSC = $0058
+BUFCNT = $006B
+
+; ----------------------------------------------------------------------------
+; Zero page labels (COL80 equates)
+
+screen_ptr_lo = $00CB
+screen_ptr_hi = $00CC
+font_ptr_lo = $00CD
+font_ptr_hi = $00CE
+
+; ----------------------------------------------------------------------------
+; Non-zeropage RAM labels (OS equates)
+
+COLOR1 = $02C5
+COLOR2 = $02C6
+RUNAD = $02E0
+MEMTOP = $02E5
+SSFLAG = $02FF
+HATABS = $031A
+ICCOM = $0342
+ICBAL = $0344
+ICBAH = $0345
+
+; ----------------------------------------------------------------------------
+; Hardware (memory-mapped I/O, OS equates)
+
+CONSOL = $D01F
+AUDF1 = $D200
+AUDC1 = $D201
+
+; ----------------------------------------------------------------------------
+; OS ROM labels
+
+s_dev_open_lo = $E410 ; (not named in OS sources)
+s_dev_open_hi = $E411 ; ""
+k_dev_get_lo = $E424 ; ""
+k_dev_get_hi = $E425 ; ""
+CIOV = $E456 ; Central Input/Output entry point
+
+; ----------------------------------------------------------------------------
+; Start of COL80. The font is stored in packed form. Each group of 8 bytes
+; defines two glyphs: the upper 4 bits of the 8 bytes, taken together,
+; define the bitmap for the first glyph, and the lower 4 bits are the second.
+; Note that the bits that make up a single character are spread across 8
+; bytes, so it's hard to visualize these even if you're used to reading hex
+; dumps.
+
+; The first 2 characters look like:
+
+; .... .O.. ; $04
+; .... .O.. ; $04
+; O.O. .O.. ; $A4
+; OOO. .O.. ; $E4
+; OOO. .OOO ; $E7
+; .O.. .O.. ; $44
+; .... .O.. ; $04
+; .... .O.. ; $04
+
+; These are the ATASCII heart symbol (character code 0) and the ATASCII
+; control-A line-drawing symbol (code 1).
+
+; Note: unlike the ROM font, this font is stored in ATASCII order instead
+; of the standard Atari character order imposed by the hardware. Like
+; the ROM font, inverse characters are not stored here (the bitmaps get
+; inverted by the driver)
+
+font_data
+ ; Low ATASCII graphics symbols (code 0-31)
+ .byte $04,$04,$A4,$E4,$E7,$44,$04,$04 ; 7A00
+ .byte $14,$14,$14,$14,$1C,$10,$10,$10 ; 7A08
+ .byte $40,$40,$40,$40,$CC,$44,$44,$44 ; 7A10
+ .byte $18,$18,$24,$24,$42,$42,$81,$81 ; 7A18
+ .byte $10,$10,$30,$30,$73,$73,$F3,$F3 ; 7A20
+ .byte $83,$83,$C3,$C3,$E0,$E0,$F0,$F0 ; 7A28
+ .byte $CF,$CF,$C0,$C0,$00,$00,$00,$00 ; 7A30
+ .byte $00,$00,$00,$00,$0C,$0C,$FC,$FC ; 7A38
+ .byte $00,$00,$00,$40,$A7,$44,$E4,$04 ; 7A40
+ .byte $04,$04,$04,$04,$FF,$04,$04,$04 ; 7A48
+ .byte $00,$00,$60,$F0,$FF,$6F,$0F,$0F ; 7A50
+ .byte $80,$80,$80,$80,$8F,$84,$84,$84 ; 7A58
+ .byte $4C,$4C,$4C,$4C,$FC,$0C,$0C,$0C ; 7A60
+ .byte $40,$4C,$48,$4C,$78,$0C,$06,$00 ; 7A68
+ .byte $00,$44,$E4,$44,$4E,$44,$00,$00 ; 7A70
+ .byte $00,$24,$42,$FF,$42,$24,$00,$00 ; 7A78
+
+ ; Space ! " # etc (codes 32-63)
+ .byte $00,$04,$04,$04,$04,$00,$04,$00 ; 7A80
+ .byte $00,$A0,$AA,$AE,$0A,$0E,$0A,$00 ; 7A88
+ .byte $00,$40,$68,$82,$44,$28,$C2,$40 ; 7A90
+ .byte $00,$C4,$64,$E4,$60,$C0,$40,$00 ; 7A98
+ .byte $00,$44,$82,$82,$82,$82,$82,$44 ; 7AA0
+ .byte $00,$04,$A4,$4E,$E4,$44,$A0,$00 ; 7AA8
+ .byte $00,$00,$00,$0E,$00,$40,$40,$80 ; 7AB0
+ .byte $00,$02,$02,$04,$04,$08,$48,$00 ; 7AB8
+ .byte $00,$E4,$AC,$A4,$A4,$A4,$EE,$00 ; 7AC0
+ .byte $00,$EE,$22,$22,$EE,$82,$EE,$00 ; 7AC8
+ .byte $00,$AE,$A8,$AE,$E2,$22,$2E,$00 ; 7AD0
+ .byte $00,$EE,$82,$E2,$A4,$A4,$E4,$00 ; 7AD8
+ .byte $00,$EE,$AA,$EA,$AE,$A2,$EE,$00 ; 7AE0
+ .byte $00,$00,$00,$44,$00,$44,$04,$08 ; 7AE8
+ .byte $00,$20,$4E,$80,$4E,$20,$00,$00 ; 7AF0
+ .byte $00,$8C,$42,$22,$44,$80,$04,$00 ; 7AF8
+
+ ; @ A B C etc (codes 64-95)
+ .byte $00,$6E,$9A,$BA,$BE,$8A,$6A,$00 ; 7B00
+ .byte $00,$C6,$A8,$C8,$A8,$A8,$C6,$00 ; 7B08
+ .byte $00,$CE,$A8,$AC,$A8,$A8,$CE,$00 ; 7B10
+ .byte $00,$E6,$88,$C8,$8A,$8A,$86,$00 ; 7B18
+ .byte $00,$AE,$A4,$E4,$A4,$A4,$AE,$00 ; 7B20
+ .byte $00,$2A,$2A,$2C,$2A,$2A,$CA,$00 ; 7B28
+ .byte $00,$8A,$8E,$8E,$8A,$8A,$EA,$00 ; 7B30
+ .byte $00,$C4,$AA,$AA,$AA,$AA,$A4,$00 ; 7B38
+ .byte $00,$EE,$AA,$EA,$8A,$8A,$8E,$03 ; 7B40
+ .byte $00,$C6,$A8,$AC,$C2,$A2,$AC,$00 ; 7B48
+ .byte $00,$EA,$4A,$4A,$4A,$4A,$4E,$00 ; 7B50
+ .byte $00,$AA,$AA,$AA,$AE,$AE,$4A,$00 ; 7B58
+ .byte $00,$AA,$4A,$4E,$44,$44,$A4,$00 ; 7B60
+ .byte $00,$EE,$28,$48,$88,$88,$E8,$0E ; 7B68
+ .byte $00,$8E,$82,$42,$42,$22,$22,$0E ; 7B70
+ .byte $00,$00,$40,$A0,$00,$00,$00,$0F ; 7B78
+
+ ; diamond, lowercase letters, control codes (codes 96-127)
+ .byte $00,$00,$00,$46,$E2,$4E,$0E,$00 ; 7B80
+ .byte $00,$80,$80,$C6,$A8,$A8,$C6,$00 ; 7B88
+ .byte $00,$20,$20,$6E,$AE,$A8,$6E,$00 ; 7B90
+ .byte $00,$00,$C0,$86,$CA,$8E,$82,$0C ; 7B98
+ .byte $00,$80,$84,$80,$C4,$A4,$A4,$00 ; 7BA0
+ .byte $00,$08,$28,$0A,$2C,$2A,$2A,$C0 ; 7BA8
+ .byte $00,$40,$40,$4A,$4E,$4A,$4A,$00 ; 7BB0
+ .byte $00,$00,$00,$CE,$AA,$AA,$AE,$00 ; 7BB8
+ .byte $00,$00,$00,$C6,$AA,$C6,$82,$82 ; 7BC0
+ .byte $00,$00,$00,$6E,$88,$86,$8E,$00 ; 7BC8
+ .byte $00,$00,$40,$EA,$4A,$4A,$6E,$00 ; 7BD0
+ .byte $00,$00,$00,$AA,$AA,$AE,$4A,$00 ; 7BD8
+ .byte $00,$00,$00,$AA,$4A,$A6,$A2,$0C ; 7BE0
+ .byte $00,$00,$04,$EE,$4E,$84,$EE,$00 ; 7BE8
+ .byte $40,$4E,$4C,$4E,$4A,$42,$42,$40 ; 7BF0
+ .byte $00,$28,$6C,$EE,$6C,$28,$00,$00 ; 7BF8
+
+right_margin
+ ; Default value is 79 decimal. Unsure why the author didn't use RMARGN at $53
+ .byte $4F ; 7C00 4F
+
+; ----------------------------------------------------------------------------
+; Start of COL80 code.
+
+; Callback for CIO OPEN command.
+
+col80_open
+ jsr init_graphics_8 ; 7C01 20 14 7C
+ lda #$00 ; 7C04 A9 00
+ sta ROWCRS ; 7C06 85 54
+ sta COLCRS ; 7C08 85 55
+ nop ; 7C0A EA
+ nop ; 7C0B EA
+ sta BUFCNT ; 7C0C 85 6B
+ lda #$4F ; 7C0E A9 4F
+ sta right_margin ; 7C10 8D 00 7C
+ rts ; 7C13 60
+
+; ----------------------------------------------------------------------------
+; Assembly version of GRAPHICS 8+16 command.
+
+init_graphics_8
+ lda #$08 ; 7C14 A9 08
+ sta ICAX2Z ; 7C16 85 2B
+ lda #$0C ; 7C18 A9 0C
+ sta ICAX1Z ; 7C1A 85 2A
+ jsr open_s_dev ; 7C1C 20 37 7C
+
+ ; Set COL80's default colors
+ lda #$08 ; 7C1F A9 08
+ sta COLOR2 ; 7C21 8D C6 02
+ nop ; 7C24 EA
+ nop ; 7C25 EA
+ nop ; 7C26 EA
+ lda #$00 ; 7C27 A9 00
+ sta COLOR1 ; 7C29 8D C5 02
+
+ ; Protect ourselves from BASIC and the OS
+ lda #<START_ADDRESS ; 7C2C A9 00
+ sta MEMTOP ; 7C2E 8D E5 02
+ lda #>START_ADDRESS ; 7C31 A9 7A
+ sta MEMTOP+1 ; 7C33 8D E6 02
+ rts ; 7C36 60
+
+; ----------------------------------------------------------------------------
+; Call the OPEN vector for the S: device, using the ROM vector table
+; at $E410. The table stores address-minus-one of each routine, which is
+; meant to actually be called via the RTS instruction (standard 6502
+; technique, but confusing the first time you encounter it)
+
+open_s_dev
+ lda s_dev_open_hi ; 7C37 AD 11 E4
+ pha ; 7C3A 48
+ lda s_dev_open_lo ; 7C3B AD 10 E4
+ pha ; 7C3E 48
+ rts ; 7C3F 60
+
+; ----------------------------------------------------------------------------
+; Callback for CIO CLOSE command. Note that the routine does nothing, really
+; (the OS will mark the E: device as being closed, but COL80 doesn't do any
+; cleanup).
+; The SPECIAL and GET STATUS callbacks in col80_vector_tab also point here.
+
+col80_close
+ jmp return_success
+
+; ----------------------------------------------------------------------------
+; Callback for the internal put-one-byte, used by the OS to implement the
+; CIO PUT RECORD and PUT BYTES commands. This routine's one argument is
+; the byte in the accumulator (the character to print).
+
+; First, the routine checks for the cursor control characters it supports.
+; COL80 only handles the EOL and clear-screen codes; trying to print
+; backspaces, arrows, deletes, inserts, etc just causes their ATASCII
+; graphics character to print instead.
+
+col80_putbyte
+ ; EOL (decimal 155)?
+ cmp #$9B ; 7C43 C9 9B
+ bne check_clear ; 7C45 D0 08
+ lda right_margin ; 7C47 AD 00 7C
+ sta COLCRS ; 7C4A 85 55
+ jmp skip_write ; 7C4C 4C 7C 7C
+
+check_clear
+ ; Clear (decimal 125)?
+ cmp #$7D ; 7C4F C9 7D
+ bne regular_char ; 7C51 D0 03
+ jmp clear_screen ; 7C53 4C 0B 7D
+
+ ; See if this is an inverse video char (code >= 128)
+regular_char
+ tax ; 7C56 AA
+ bpl not_inverse ; 7C57 10 07
+ lda #$FF ; 7C59 A9 FF
+ sta inverse_mask ; 7C5B 8D 49 7F
+ bne skip_ninv ; 7C5E D0 05
+
+not_inverse
+ lda #$00 ; 7C60 A9 00
+ sta inverse_mask ; 7C62 8D 49 7F
+
+skip_ninv
+ txa ; 7C65 8A
+ and #$7F ; 7C66 29 7F
+ sta TMPCHR ; 7C68 85 50
+ lda DINDEX ; 7C6A A5 57
+ cmp #$08 ; 7C6C C9 08
+ beq graphics_ok ; 7C6E F0 03
+ ; If we're not in GRAPHICS 8 mode, reinitialize ourselves
+ jsr col80_open ; 7C70 20 01 7C
+
+graphics_ok
+ ; Call the routines that actually print the character
+ jsr setup_font_ptr ; 7C73 20 C9 7C
+ jsr setup_screen_ptr ; 7C76 20 34 7D
+ jsr write_font_data ; 7C79 20 82 7D
+
+skip_write
+ ; Move the cursor 1 space to the right. This will
+ ; advance us to the next line if we're at the margin,
+ ; and scroll the screen if needed
+ jsr advance_cursor ; 7C7C 20 EE 7C
+
+check_ssflag
+ ; The OS keyboard interrupt handler will toggle SSFLAG (start/stop fla
+ ; any time the user presses ctrl-1
+ lda SSFLAG ; 7C7F AD FF 02
+ bne check_ssflag ; 7C82 D0 FB
+ jmp return_success ; 7C84 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; Scroll the screen up one line (8 scanlines). This has to move almost 8K of
+; data, so it's noticeably slower than scrolling the GR.0 text screen.
+
+scroll_screen
+ lda SAVMSC ; 7C87 A5 58
+ sta screen_ptr_lo ; 7C89 85 CB
+ clc ; 7C8B 18
+ adc #$40 ; 7C8C 69 40
+ ; font_ptr_lo is actually being used here as a second pointer into
+ ; screen RAM, instead of its usual use as a pointer into the
+ ; font_data table
+ sta font_ptr_lo ; 7C8E 85 CD
+ lda SAVMSC+1 ; 7C90 A5 59
+ sta screen_ptr_hi ; 7C92 85 CC
+ adc #$01 ; 7C94 69 01
+ sta font_ptr_hi ; 7C96 85 CE
+ ldx #$1D ; 7C98 A2 1D
+ ldy #$00 ; 7C9A A0 00
+
+scroll_line_loop
+ lda (font_ptr_lo),y ; 7C9C B1 CD
+ sta (screen_ptr_lo),y ; 7C9E 91 CB
+ dey ; 7CA0 88
+ bne scroll_line_loop ; 7CA1 D0 F9
+ inc font_ptr_hi ; 7CA3 E6 CE
+ inc screen_ptr_hi ; 7CA5 E6 CC
+ dex ; 7CA7 CA
+ bne scroll_line_loop ; 7CA8 D0 F2
+
+blank_bottom_row
+ lda SAVMSC ; 7CAA A5 58
+ clc ; 7CAC 18
+ adc #$C0 ; 7CAD 69 C0
+ sta screen_ptr_lo ; 7CAF 85 CB
+ lda SAVMSC+1 ; 7CB1 A5 59
+ adc #$1C ; 7CB3 69 1C
+ sta screen_ptr_hi ; 7CB5 85 CC
+ lda #$00 ; 7CB7 A9 00
+ tay ; 7CB9 A8
+
+blank_loop
+ sta (screen_ptr_lo),y ; 7CBA 91 CB
+ dey ; 7CBC 88
+ bne blank_loop ; 7CBD D0 FB
+ inc screen_ptr_hi ; 7CBF E6 CC
+ ldy #$40 ; 7CC1 A0 40
+
+blank_tail
+ sta (screen_ptr_lo),y ; 7CC3 91 CB
+ dey ; 7CC5 88
+ bpl blank_tail ; 7CC6 10 FB
+ rts ; 7CC8 60
+
+; ----------------------------------------------------------------------------
+; Set up font_ptr_lo/hi to point to the font_data bitmap for the character in
+; TMPCHR. Also sets lo_nybble_flag to let the caller know whether the
+; bitmap is in the upper or lower 4 bits of the bytes pointed to.
+
+setup_font_ptr
+ lda #$00 ; 7CC9 A9 00
+ sta font_ptr_hi ; 7CCB 85 CE
+ sta lo_nybble_flag ; 7CCD 8D 48 7F
+ lda TMPCHR ; 7CD0 A5 50
+ clc ; 7CD2 18
+ ror ; 7CD3 6A
+ bcc font_hi_nybble ; 7CD4 90 05
+ ldx #$FF ; 7CD6 A2 FF
+ stx lo_nybble_flag ; 7CD8 8E 48 7F
+
+font_hi_nybble
+ clc ; 7CDB 18
+ rol ; 7CDC 2A
+ rol ; 7CDD 2A
+ rol font_ptr_hi ; 7CDE 26 CE
+ rol ; 7CE0 2A
+ rol font_ptr_hi ; 7CE1 26 CE
+ adc #<font_data ; 7CE3 69 00
+ sta font_ptr_lo ; 7CE5 85 CD
+ lda #>font_data ; 7CE7 A9 7A
+ adc font_ptr_hi ; 7CE9 65 CE
+ sta font_ptr_hi ; 7CEB 85 CE
+ rts ; 7CED 60
+
+; ----------------------------------------------------------------------------
+; Move the cursor one space to the right (to the next line if at the margin,
+; and scroll screen if on the last row)
+
+advance_cursor
+ inc COLCRS ; 7CEE E6 55
+ lda right_margin ; 7CF0 AD 00 7C
+ cmp COLCRS ; 7CF3 C5 55
+ bcs same_line ; 7CF5 B0 13
+ lda LMARGN ; 7CF7 A5 52
+ sta COLCRS ; 7CF9 85 55
+ lda ROWCRS ; 7CFB A5 54
+ ; $17 is 25 decimal, one row below the lowest on the screen
+ cmp #$17 ; 7CFD C9 17
+ bcc no_scroll ; 7CFF 90 07
+ jsr scroll_screen ; 7D01 20 87 7C
+ ; Move to row 24 after scrolling
+ lda #$16 ; 7D04 A9 16
+ sta ROWCRS ; 7D06 85 54
+
+no_scroll
+ inc ROWCRS ; 7D08 E6 54
+
+same_line
+ rts ; 7D0A 60
+
+; ----------------------------------------------------------------------------
+; Clear the screen by setting all screen RAM bytes to zero. Slow, but not
+; as slow as scrolling.
+
+clear_screen
+ lda SAVMSC ; 7D0B A5 58
+ sta screen_ptr_lo ; 7D0D 85 CB
+ lda SAVMSC+1 ; 7D0F A5 59
+ sta screen_ptr_hi ; 7D11 85 CC
+ ldy #$00 ; 7D13 A0 00
+ ldx #$1D ; 7D15 A2 1D
+ lda #$00 ; 7D17 A9 00
+
+cls_loop
+ sta (screen_ptr_lo),y ; 7D19 91 CB
+ dey ; 7D1B 88
+ bne cls_loop ; 7D1C D0 FB
+ inc screen_ptr_hi ; 7D1E E6 CC
+ dex ; 7D20 CA
+ bne cls_loop ; 7D21 D0 F6
+ jsr blank_bottom_row ; 7D23 20 AA 7C
+ lda LMARGN ; 7D26 A5 52
+ sta COLCRS ; 7D28 85 55
+ lda #$00 ; 7D2A A9 00
+ sta ROWCRS ; 7D2C 85 54
+ ; redundant JMP
+ jmp return_success ; 7D2E 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; CIO expects the Y register to contain a status code.
+; 1 means success (no error). Lots of COL80's routines
+; jump here.
+
+return_success
+ ldy #$01 ; 7D31 A0 01
+ rts ; 7D33 60
+
+; ----------------------------------------------------------------------------
+; Set screen_ptr_lo/hi to point to the address of the first byte of graphics
+; data at the current cursor position.
+
+setup_screen_ptr
+ ldy ROWCRS ; 7D34 A4 54
+ lda SAVMSC ; 7D36 A5 58
+ clc ; 7D38 18
+ adc row_low_offset_tab,y ; 7D39 79 52 7D
+ sta screen_ptr_lo ; 7D3C 85 CB
+ lda SAVMSC+1 ; 7D3E A5 59
+ adc row_high_offset_tab,y ; 7D40 79 6A 7D
+ sta screen_ptr_hi ; 7D43 85 CC
+ lda COLCRS ; 7D45 A5 55
+ lsr ; 7D47 4A
+ clc ; 7D48 18
+ adc screen_ptr_lo ; 7D49 65 CB
+ bcc hi_byte_ok ; 7D4B 90 02
+ inc screen_ptr_hi ; 7D4D E6 CC
+
+hi_byte_ok
+ sta screen_ptr_lo ; 7D4F 85 CB
+ rts ; 7D51 60
+
+; ----------------------------------------------------------------------------
+; Tables of offsets for setup_screen_ptr, to avoid doing multiplication at
+; runtime (the 6502 lacks a MUL instruction, so it's slow...)
+
+row_low_offset_tab
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D52
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D5A
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D62
+
+row_high_offset_tab
+ .byte $00,$01,$02,$03,$05,$06,$07,$08 ; 7D6A
+ .byte $0A,$0B,$0C,$0D,$0F,$10,$11,$12 ; 7D72
+ .byte $14,$15,$16,$17,$19,$1A,$1B,$1C ; 7D7A
+
+; ----------------------------------------------------------------------------
+; Copy pixel data from the font table to screen RAM.
+; font_ptr_lo/hi must point to the correct character, and screen_ptr_lo/hi
+; must point to the correct screen address for the current cursor position.
+; This routine has separate execution paths for even- and odd-numbered
+; cursor positions, since each byte of screen RAM holds data for two
+; adjacent characters (and when printing to one of them, the other needs
+; to be left undisturbed!)
+
+write_font_data
+ lda COLCRS ; 7D82 A5 55
+ clc ; 7D84 18
+ ror ; 7D85 6A
+ bcc write_font_data_even ; 7D86 90 31
+ ldx #$00 ; 7D88 A2 00
+ ldy #$00 ; 7D8A A0 00
+
+get_font_nybble_odd
+ lda (font_ptr_lo),y ; 7D8C B1 CD
+ bit lo_nybble_flag ; 7D8E 2C 48 7F
+ bne lo_nybble_odd ; 7D91 D0 04
+ ; glyph we want is stored in top 4 bits of font byte,
+ ; shift it down to the bottom 4 bits
+ lsr ; 7D93 4A
+ lsr ; 7D94 4A
+ lsr ; 7D95 4A
+ lsr ; 7D96 4A
+
+lo_nybble_odd
+ eor inverse_mask ; 7D97 4D 49 7F
+ and #$0F ; 7D9A 29 0F
+ sta TMPCHR ; 7D9C 85 50
+ ldy scanline_offset_tab,x ; 7D9E BC EA 7D
+ lda (screen_ptr_lo),y ; 7DA1 B1 CB
+ and #$F0 ; 7DA3 29 F0
+ ora TMPCHR ; 7DA5 05 50
+ sta (screen_ptr_lo),y ; 7DA7 91 CB
+ inx ; 7DA9 E8
+ cpx #$07 ; 7DAA E0 07
+ bne screen_ptr_ok_odd ; 7DAC D0 02
+ inc screen_ptr_hi ; 7DAE E6 CC
+
+screen_ptr_ok_odd
+ cpx #$08 ; 7DB0 E0 08
+ beq write_font_done_odd ; 7DB2 F0 04
+ txa ; 7DB4 8A
+ tay ; 7DB5 A8
+ bne get_font_nybble_odd ; 7DB6 D0 D4
+
+write_font_done_odd
+ rts ; 7DB8 60
+
+; ----------------------------------------------------------------------------
+; Write data to even-numbered columns, very similar to the above
+
+write_font_data_even
+ ldx #$00 ; 7DB9 A2 00
+ ldy #$00 ; 7DBB A0 00
+
+get_font_nybble_even
+ lda (font_ptr_lo),y ; 7DBD B1 CD
+ bit lo_nybble_flag ; 7DBF 2C 48 7F
+ beq hi_nybble_even ; 7DC2 F0 04
+ asl ; 7DC4 0A
+ asl ; 7DC5 0A
+ asl ; 7DC6 0A
+ asl ; 7DC7 0A
+
+hi_nybble_even
+ eor inverse_mask ; 7DC8 4D 49 7F
+ and #$F0 ; 7DCB 29 F0
+ sta TMPCHR ; 7DCD 85 50
+ ldy scanline_offset_tab,x ; 7DCF BC EA 7D
+ lda (screen_ptr_lo),y ; 7DD2 B1 CB
+ and #$0F ; 7DD4 29 0F
+ ora TMPCHR ; 7DD6 05 50
+ sta (screen_ptr_lo),y ; 7DD8 91 CB
+ inx ; 7DDA E8
+ cpx #$07 ; 7DDB E0 07
+ bne screen_ptr_ok_even ; 7DDD D0 02
+ inc screen_ptr_hi ; 7DDF E6 CC
+
+screen_ptr_ok_even
+ cpx #$08 ; 7DE1 E0 08
+ beq write_font_done_even ; 7DE3 F0 04
+ txa ; 7DE5 8A
+ tay ; 7DE6 A8
+ bne get_font_nybble_even ; 7DE7 D0 D4
+
+write_font_done_even
+ rts ; 7DE9 60
+
+; ----------------------------------------------------------------------------
+
+scanline_offset_tab
+ .byte $00,$28,$50,$78,$A0,$C8,$F0,$18 ; 7DEA
+
+; ----------------------------------------------------------------------------
+; Callback for the internal get-one-byte, used by the OS to implement the
+; CIO GET RECORD and GET BYTES commands. This routine takes no arguments,
+; and returns the read byte in the accumulator.
+
+; Internally, COL80 maintains a line buffer. Each time col80_getbyte is
+; called, it returns the next character in the buffer. If the buffer's
+; empty (or if the last call returned the last character), a new line
+; of input is read from the user (and the first character is returned).
+; This is exactly how the OS E: device works.
+
+col80_getbyte
+ lda BUFCNT ; 7DF2 A5 6B
+ beq get_line ; 7DF4 F0 0E
+
+get_next_byte
+ ldx line_buffer_index ; 7DF6 AE 4A 7F
+ lda line_buffer,x ; 7DF9 BD 4B 7F
+ dec BUFCNT ; 7DFC C6 6B
+ inc line_buffer_index ; 7DFE EE 4A 7F
+ jmp return_success ; 7E01 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; Get a line of input from the user, terminated by the Return key.
+
+get_line
+ lda #$00 ; 7E04 A9 00
+ sta BUFCNT ; 7E06 85 6B
+ sta line_buffer_index ; 7E08 8D 4A 7F
+
+show_cursor
+ lda #$20 ; 7E0B A9 20
+ sta TMPCHR ; 7E0D 85 50
+ lda #$FF ; 7E0F A9 FF
+ sta inverse_mask ; 7E11 8D 49 7F
+ jsr setup_font_ptr ; 7E14 20 C9 7C
+ jsr setup_screen_ptr ; 7E17 20 34 7D
+ jsr write_font_data ; 7E1A 20 82 7D
+ jsr get_keystroke ; 7E1D 20 B7 7E
+ cpy #$01 ; 7E20 C0 01
+ beq keystroke_ok ; 7E22 F0 07
+ ldy #$00 ; 7E24 A0 00
+ sty line_buffer_index ; 7E26 8C 4A 7F
+ sty BUFCNT ; 7E29 84 6B
+
+keystroke_ok
+ cmp #$9B ; 7E2B C9 9B
+ bne check_backs_key ; 7E2D D0 03
+ jmp return_key_hit ; 7E2F 4C 52 7E
+
+check_backs_key
+ cmp #$7E ; 7E32 C9 7E
+ bne check_clear_key ; 7E34 D0 03
+ jmp backs_key_hit ; 7E36 4C 71 7E
+
+check_clear_key
+ cmp #$7D ; 7E39 C9 7D
+ bne normal_key_hit ; 7E3B D0 03
+ jmp clear_key_hit ; 7E3D 4C 64 7E
+
+normal_key_hit
+ ldx BUFCNT ; 7E40 A6 6B
+ bpl buffer_character ; 7E42 10 03
+ jmp beep ; 7E44 4C 8F 7E
+
+buffer_character
+ sta line_buffer,x ; 7E47 9D 4B 7F
+ jsr col80_putbyte ; 7E4A 20 43 7C
+ inc BUFCNT ; 7E4D E6 6B
+ jmp show_cursor ; 7E4F 4C 0B 7E
+
+return_key_hit
+ jsr print_space ; 7E52 20 A4 7E
+ lda #$9B ; 7E55 A9 9B
+ ldx BUFCNT ; 7E57 A6 6B
+ sta line_buffer,x ; 7E59 9D 4B 7F
+ inc BUFCNT ; 7E5C E6 6B
+ jsr col80_putbyte ; 7E5E 20 43 7C
+ jmp get_next_byte ; 7E61 4C F6 7D
+
+clear_key_hit
+ jsr clear_screen ; 7E64 20 0B 7D
+ lda #$00 ; 7E67 A9 00
+ sta line_buffer_index ; 7E69 8D 4A 7F
+ sta BUFCNT ; 7E6C 85 6B
+ jmp get_line ; 7E6E 4C 04 7E
+
+backs_key_hit
+ jsr print_space ; 7E71 20 A4 7E
+ lda BUFCNT ; 7E74 A5 6B
+ beq backs_key_done ; 7E76 F0 14
+ dec COLCRS ; 7E78 C6 55
+ lda COLCRS ; 7E7A A5 55
+ clc ; 7E7C 18
+ adc #$01 ; 7E7D 69 01
+ cmp LMARGN ; 7E7F C5 52
+ bne backs_same_line ; 7E81 D0 07
+ lda right_margin ; 7E83 AD 00 7C
+ sta COLCRS ; 7E86 85 55
+ dec ROWCRS ; 7E88 C6 54
+
+backs_same_line
+ dec BUFCNT ; 7E8A C6 6B
+
+backs_key_done
+ jmp show_cursor ; 7E8C 4C 0B 7E
+
+; ----------------------------------------------------------------------------
+; Ring the margin bell. COL80 doesn't implement the ctrl-2 bell (character
+; 253), and instead of using the GTIA keyclick speaker, it uses POKEY to
+; make a beep
+
+beep ldy #$00 ; 7E8F A0 00
+ ldx #$AF ; 7E91 A2 AF
+
+beep_delay_x
+ stx AUDF1 ; 7E93 8E 00 D2
+ stx AUDC1 ; 7E96 8E 01 D2
+
+beep_delay_y
+ dey ; 7E99 88
+ bne beep_delay_y ; 7E9A D0 FD
+ dex ; 7E9C CA
+ cpx #$9F ; 7E9D E0 9F
+ bne beep_delay_x ; 7E9F D0 F2
+ jmp show_cursor ; 7EA1 4C 0B 7E
+
+; ----------------------------------------------------------------------------
+; Print a space character at the current cursor position. Does not
+; update the cursor position.
+print_space
+ lda #$00 ; 7EA4 A9 00
+ sta inverse_mask ; 7EA6 8D 49 7F
+ lda #$20 ; 7EA9 A9 20
+ sta TMPCHR ; 7EAB 85 50
+ jsr setup_font_ptr ; 7EAD 20 C9 7C
+ jsr setup_screen_ptr ; 7EB0 20 34 7D
+ jsr write_font_data ; 7EB3 20 82 7D
+ rts ; 7EB6 60
+
+; ----------------------------------------------------------------------------
+; Get a keystroke (blocking). Just calls the OS K: get-one-byte routine
+; (call by pushing address-minus-one then doing an RTS)
+get_keystroke
+ lda k_dev_get_hi ; 7EB7 AD 25 E4
+ pha ; 7EBA 48
+ lda k_dev_get_lo ; 7EBB AD 24 E4
+ pha ; 7EBE 48
+ rts ; 7EBF 60
+
+; ----------------------------------------------------------------------------
+; Initialization callback. The OS will call this on coldstart (or would do,
+; if the driver were in ROM), and also on warmstart (because we stole the
+; DOSINI vector).
+; This routine is also the first thing that gets called by the mainline
+; init code. Its job is to install COL80 in the handler table at HATABS.
+; Actually the handler is first installed as X:, then the main init code
+; fixes this up to E: unless the user is holding down SELECT. This allows
+; the user to toggle between the 40-column ROM E: and COL80 without doing
+; a full reboot. No idea if this was a documented feature or something the
+; author used for development/debugging.
+
+col80_init
+ ldy #$00 ; 7EC0 A0 00
+
+next_hatab_slot
+ lda HATABS,y ; 7EC2 B9 1A 03
+ beq register_x_handler ; 7EC5 F0 0A
+ iny ; 7EC7 C8
+ iny ; 7EC8 C8
+ iny ; 7EC9 C8
+ cpy #$20 ; 7ECA C0 20
+ bcc next_hatab_slot ; 7ECC 90 F4
+ jmp return_success ; 7ECE 4C 31 7D
+
+register_x_handler
+ lda #$58 ; 7ED1 A9 58
+ sta HATABS,y ; 7ED3 99 1A 03
+ lda #<col80_vector_tab ; 7ED6 A9 E5
+ iny ; 7ED8 C8
+ sta HATABS,y ; 7ED9 99 1A 03
+ lda #>col80_vector_tab ; 7EDC A9 7E
+ iny ; 7EDE C8
+ sta HATABS,y ; 7EDF 99 1A 03
+ jmp return_success ; 7EE2 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; COL80 vector table, in the format required by the OS. Our HATABS entry
+; will point to this table, and the OS will call the routines listed here
+; via the "call by RTS" method (which is why they're address-minus-one).
+
+; See the entry on HATABS in "Mapping the Atari" or the OS manual.
+
+col80_vector_tab
+ .word col80_open-1 ; 7EE5 00 7C
+ .word col80_close-1 ; 7EE7 3F 7C
+ .word col80_getbyte-1 ; 7EE9 F1 7D
+ .word col80_putbyte-1 ; 7EEB 42 7C
+ .word col80_close-1 ; 7EED 3F 7C
+ .word col80_close-1 ; 7EEF 3F 7C
+ jmp col80_init ; 7EF1 4C C0 7E
+
+; ----------------------------------------------------------------------------
+; The OS jumps here on warmstart (also, this is the run address in our
+; binary load file)
+
+dosini_entry_point
+ nop ; 7EF4 EA
+ nop ; 7EF5 EA
+ nop ; 7EF6 EA
+
+main_entry_point
+ jsr col80_init ; 7EF7 20 C0 7E
+ lda CONSOL ; 7EFA AD 1F D0
+ and #$04 ; 7EFD 29 04
+ beq no_e_handler ; 7EFF F0 2F
+ lda #$0C ; 7F01 A9 0C
+ sta ICCOM ; 7F03 8D 42 03
+ ldx #$00 ; 7F06 A2 00
+ jsr CIOV ; 7F08 20 56 E4
+ lda #$58 ; 7F0B A9 58
+ sta font_ptr_lo ; 7F0D 85 CD
+ lda #$03 ; 7F0F A9 03
+ sta ICCOM ; 7F11 8D 42 03
+ lda #$CD ; 7F14 A9 CD
+ sta ICBAL ; 7F16 8D 44 03
+ lda #$00 ; 7F19 A9 00
+ sta ICBAH ; 7F1B 8D 45 03
+ ldx #$00 ; 7F1E A2 00
+ jsr CIOV ; 7F20 20 56 E4
+ ldy #$07 ; 7F23 A0 07
+ lda #<col80_vector_tab ; 7F25 A9 E5
+ sta HATABS,y ; 7F27 99 1A 03
+ lda #>col80_vector_tab ; 7F2A A9 7E
+ iny ; 7F2C C8
+ sta HATABS,y ; 7F2D 99 1A 03
+no_e_handler
+ lda #<START_ADDRESS ; 7F30 A9 00
+ sta MEMTOP ; 7F32 8D E5 02
+ lda #>START_ADDRESS ; 7F35 A9 7A
+ sta MEMTOP+1 ; 7F37 8D E6 02
+ jmp return_success ; 7F3A 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; (when does this actually get called? da65 can't find any references
+; to it, and it's not a run or init address in the binary load file)
+ lda #<dosini_entry_point ; 7F3D A9 F4
+ sta DOSINI ; 7F3F 85 0C
+ lda #>dosini_entry_point ; 7F41 A9 7E
+ sta DOSINI+1 ; 7F43 85 0D
+ jmp main_entry_point ; 7F45 4C F7 7E
+
+; ----------------------------------------------------------------------------
+; Various bits of runtime state here. It's unclear to me why the standard
+; OS buffer location couldn't have been used instead (normally the top
+; half of page 5), or why the other stuff couldn't have been stored in
+; zero page, in locations used by the ROM E: handler (thus unused when
+; it's replaced with COL80). line_buffer_index needs to be preserved
+; across calls to col80_getbyte, but lo_nybble_flag and inverse_mask are
+; freshly calculated every time they're used, so they could be almost
+; anywhere.
+
+lo_nybble_flag
+ .byte $00 ; 7F48 00
+
+inverse_mask
+ .byte $00 ; 7F49 00
+
+line_buffer_index
+ .byte $12 ; 7F4A 12
+
+; ----------------------------------------------------------------------------
+; There's absolutely no reason why this data needs to be included in the
+; binary load file: the line buffer's initial contents are meaningless, they
+; will be blown away the first time anything reads from the E: device.
+
+; Notice the author was running his debugger in COL80 when he built the
+; binary (ASCII "S COL80 7A00 7F80" command still in the buffer).
+
+line_buffer
+ .byte $53,$20,$43,$4F,$4C,$38,$30,$20 ; 7F4B
+ .byte $37,$41,$30,$30,$20,$37,$46,$38 ; 7F53
+ .byte $30,$9B,$20,$20,$20,$20,$9B,$27 ; 7F5B
+ .byte $40,$40,$40,$40,$28,$28,$28,$28 ; 7F63
+ .byte $40,$40,$40,$40,$40,$40,$40,$40 ; 7F6B
+ .byte $40,$40,$40,$40,$40,$40,$40,$40 ; 7F73
+ .byte $9B,$FD,$FD,$FD,$FD,$9B ; 7F7B
+
+END_ADDRESS = *-1
+
+; I've found a variant (modified version?) of this code, that doesn't
+; include the line_buffer in the file (no reason for it to be there),
+; or the $0C segment, and that has another segment, loaded at $6000,
+; with the run address changed to $6000. The code looks like:
+
+; .org $6000
+; jsr dosini_entry_point
+; lda #$50
+; sta RMARGN
+; lda #$00
+; sta COLOR2
+
+; also, the default colors have been changed in init_graphics_8.
+
+; There are at least two binaries floating around that contain
+; extra (garbage) bytes at the end, presumably from being transferred
+; over XMODEM or similar. They are otherwise identical.
+
diff --git a/col80_main.s b/col80_main.s
new file mode 100644
index 0000000..0b56ee3
--- /dev/null
+++ b/col80_main.s
@@ -0,0 +1,895 @@
+; COL80.COM, aka COL80E.COM, aka COL80HND.COM
+; (and probably several other names)
+
+; Original author unknown
+; License unknown
+; Disassembly and comments by Urchlay
+
+; This is a widely-distributed software 80-column driver for the Atari
+; 8-bit computers. It replaces the OS's E: driver, and uses GRAPHICS 8
+; for display, with 4x8 pixel character cells.
+
+; Disassembly was done with da65, with many iterations of "edit the
+; .info file, disassemble again", and the results were tweaked by hand
+; into something assemblable by dasm (and fairly compatible with other
+; assemblers).
+
+
+; START_ADDRESS is defined in col80_startaddr.s
+ .org START_ADDRESS
+
+; ----------------------------------------------------------------------------
+; Zero page labels (OS equates)
+
+DOSINI = $000C
+ICAX1Z = $002A
+ICAX2Z = $002B
+TMPCHR = $0050
+LMARGN = $0052
+ROWCRS = $0054
+COLCRS = $0055
+DINDEX = $0057
+SAVMSC = $0058
+BUFCNT = $006B
+
+; ----------------------------------------------------------------------------
+; Zero page labels (COL80 equates)
+
+screen_ptr_lo = $00CB
+screen_ptr_hi = $00CC
+font_ptr_lo = $00CD
+font_ptr_hi = $00CE
+
+; ----------------------------------------------------------------------------
+; Non-zeropage RAM labels (OS equates)
+
+COLOR1 = $02C5
+COLOR2 = $02C6
+RUNAD = $02E0
+MEMTOP = $02E5
+SSFLAG = $02FF
+HATABS = $031A
+ICCOM = $0342
+ICBAL = $0344
+ICBAH = $0345
+
+; ----------------------------------------------------------------------------
+; Hardware (memory-mapped I/O, OS equates)
+
+CONSOL = $D01F
+AUDF1 = $D200
+AUDC1 = $D201
+
+; ----------------------------------------------------------------------------
+; OS ROM labels
+
+s_dev_open_lo = $E410 ; (not named in OS sources)
+s_dev_open_hi = $E411 ; ""
+k_dev_get_lo = $E424 ; ""
+k_dev_get_hi = $E425 ; ""
+CIOV = $E456 ; Central Input/Output entry point
+
+; ----------------------------------------------------------------------------
+; Start of COL80. The font is stored in packed form. Each group of 8 bytes
+; defines two glyphs: the upper 4 bits of the 8 bytes, taken together,
+; define the bitmap for the first glyph, and the lower 4 bits are the second.
+; Note that the bits that make up a single character are spread across 8
+; bytes, so it's hard to visualize these even if you're used to reading hex
+; dumps.
+
+; The first 2 characters look like:
+
+; .... .O.. ; $04
+; .... .O.. ; $04
+; O.O. .O.. ; $A4
+; OOO. .O.. ; $E4
+; OOO. .OOO ; $E7
+; .O.. .O.. ; $44
+; .... .O.. ; $04
+; .... .O.. ; $04
+
+; These are the ATASCII heart symbol (character code 0) and the ATASCII
+; control-A line-drawing symbol (code 1).
+
+; Note: unlike the ROM font, this font is stored in ATASCII order instead
+; of the standard Atari character order imposed by the hardware. Like
+; the ROM font, inverse characters are not stored here (the bitmaps get
+; inverted by the driver)
+
+font_data:
+ ; Low ATASCII graphics symbols (code 0-31)
+ .byte $04,$04,$A4,$E4,$E7,$44,$04,$04 ; 7A00
+ .byte $14,$14,$14,$14,$1C,$10,$10,$10 ; 7A08
+ .byte $40,$40,$40,$40,$CC,$44,$44,$44 ; 7A10
+ .byte $18,$18,$24,$24,$42,$42,$81,$81 ; 7A18
+ .byte $10,$10,$30,$30,$73,$73,$F3,$F3 ; 7A20
+ .byte $83,$83,$C3,$C3,$E0,$E0,$F0,$F0 ; 7A28
+ .byte $CF,$CF,$C0,$C0,$00,$00,$00,$00 ; 7A30
+ .byte $00,$00,$00,$00,$0C,$0C,$FC,$FC ; 7A38
+ .byte $00,$00,$00,$40,$A7,$44,$E4,$04 ; 7A40
+ .byte $04,$04,$04,$04,$FF,$04,$04,$04 ; 7A48
+ .byte $00,$00,$60,$F0,$FF,$6F,$0F,$0F ; 7A50
+ .byte $80,$80,$80,$80,$8F,$84,$84,$84 ; 7A58
+ .byte $4C,$4C,$4C,$4C,$FC,$0C,$0C,$0C ; 7A60
+ .byte $40,$4C,$48,$4C,$78,$0C,$06,$00 ; 7A68
+ .byte $00,$44,$E4,$44,$4E,$44,$00,$00 ; 7A70
+ .byte $00,$24,$42,$FF,$42,$24,$00,$00 ; 7A78
+
+ ; Space ! " # etc (codes 32-63)
+ .byte $00,$04,$04,$04,$04,$00,$04,$00 ; 7A80
+ .byte $00,$A0,$AA,$AE,$0A,$0E,$0A,$00 ; 7A88
+ .byte $00,$40,$68,$82,$44,$28,$C2,$40 ; 7A90
+ .byte $00,$C4,$64,$E4,$60,$C0,$40,$00 ; 7A98
+ .byte $00,$44,$82,$82,$82,$82,$82,$44 ; 7AA0
+ .byte $00,$04,$A4,$4E,$E4,$44,$A0,$00 ; 7AA8
+ .byte $00,$00,$00,$0E,$00,$40,$40,$80 ; 7AB0
+ .byte $00,$02,$02,$04,$04,$08,$48,$00 ; 7AB8
+ .byte $00,$E4,$AC,$A4,$A4,$A4,$EE,$00 ; 7AC0
+ .byte $00,$EE,$22,$22,$EE,$82,$EE,$00 ; 7AC8
+ .byte $00,$AE,$A8,$AE,$E2,$22,$2E,$00 ; 7AD0
+ .byte $00,$EE,$82,$E2,$A4,$A4,$E4,$00 ; 7AD8
+ .byte $00,$EE,$AA,$EA,$AE,$A2,$EE,$00 ; 7AE0
+ .byte $00,$00,$00,$44,$00,$44,$04,$08 ; 7AE8
+ .byte $00,$20,$4E,$80,$4E,$20,$00,$00 ; 7AF0
+ .byte $00,$8C,$42,$22,$44,$80,$04,$00 ; 7AF8
+
+ ; @ A B C etc (codes 64-95)
+ .byte $00,$6E,$9A,$BA,$BE,$8A,$6A,$00 ; 7B00
+ .byte $00,$C6,$A8,$C8,$A8,$A8,$C6,$00 ; 7B08
+ .byte $00,$CE,$A8,$AC,$A8,$A8,$CE,$00 ; 7B10
+ .byte $00,$E6,$88,$C8,$8A,$8A,$86,$00 ; 7B18
+ .byte $00,$AE,$A4,$E4,$A4,$A4,$AE,$00 ; 7B20
+ .byte $00,$2A,$2A,$2C,$2A,$2A,$CA,$00 ; 7B28
+ .byte $00,$8A,$8E,$8E,$8A,$8A,$EA,$00 ; 7B30
+ .byte $00,$C4,$AA,$AA,$AA,$AA,$A4,$00 ; 7B38
+ .byte $00,$EE,$AA,$EA,$8A,$8A,$8E,$03 ; 7B40
+ .byte $00,$C6,$A8,$AC,$C2,$A2,$AC,$00 ; 7B48
+ .byte $00,$EA,$4A,$4A,$4A,$4A,$4E,$00 ; 7B50
+ .byte $00,$AA,$AA,$AA,$AE,$AE,$4A,$00 ; 7B58
+ .byte $00,$AA,$4A,$4E,$44,$44,$A4,$00 ; 7B60
+ .byte $00,$EE,$28,$48,$88,$88,$E8,$0E ; 7B68
+ .byte $00,$8E,$82,$42,$42,$22,$22,$0E ; 7B70
+ .byte $00,$00,$40,$A0,$00,$00,$00,$0F ; 7B78
+
+ ; diamond, lowercase letters, control codes (codes 96-127)
+ .byte $00,$00,$00,$46,$E2,$4E,$0E,$00 ; 7B80
+ .byte $00,$80,$80,$C6,$A8,$A8,$C6,$00 ; 7B88
+ .byte $00,$20,$20,$6E,$AE,$A8,$6E,$00 ; 7B90
+ .byte $00,$00,$C0,$86,$CA,$8E,$82,$0C ; 7B98
+ .byte $00,$80,$84,$80,$C4,$A4,$A4,$00 ; 7BA0
+ .byte $00,$08,$28,$0A,$2C,$2A,$2A,$C0 ; 7BA8
+ .byte $00,$40,$40,$4A,$4E,$4A,$4A,$00 ; 7BB0
+ .byte $00,$00,$00,$CE,$AA,$AA,$AE,$00 ; 7BB8
+ .byte $00,$00,$00,$C6,$AA,$C6,$82,$82 ; 7BC0
+ .byte $00,$00,$00,$6E,$88,$86,$8E,$00 ; 7BC8
+ .byte $00,$00,$40,$EA,$4A,$4A,$6E,$00 ; 7BD0
+ .byte $00,$00,$00,$AA,$AA,$AE,$4A,$00 ; 7BD8
+ .byte $00,$00,$00,$AA,$4A,$A6,$A2,$0C ; 7BE0
+ .byte $00,$00,$04,$EE,$4E,$84,$EE,$00 ; 7BE8
+ .byte $40,$4E,$4C,$4E,$4A,$42,$42,$40 ; 7BF0
+ .byte $00,$28,$6C,$EE,$6C,$28,$00,$00 ; 7BF8
+
+right_margin:
+ ; Default value is 79 decimal. Unsure why the author didn't use RMARGN at $53
+ .byte $4F ; 7C00 4F
+
+; ----------------------------------------------------------------------------
+; Start of COL80 code.
+
+; Callback for CIO OPEN command.
+
+col80_open:
+ jsr init_graphics_8 ; 7C01 20 14 7C
+ lda #$00 ; 7C04 A9 00
+ sta ROWCRS ; 7C06 85 54
+ sta COLCRS ; 7C08 85 55
+ nop ; 7C0A EA
+ nop ; 7C0B EA
+ sta BUFCNT ; 7C0C 85 6B
+ lda #$4F ; 7C0E A9 4F
+ sta right_margin ; 7C10 8D 00 7C
+ rts ; 7C13 60
+
+; ----------------------------------------------------------------------------
+; Assembly version of GRAPHICS 8+16 command.
+
+init_graphics_8:
+ lda #$08 ; 7C14 A9 08
+ sta ICAX2Z ; 7C16 85 2B
+ lda #$0C ; 7C18 A9 0C
+ sta ICAX1Z ; 7C1A 85 2A
+ jsr open_s_dev ; 7C1C 20 37 7C
+
+ ; Set COL80's default colors
+ lda #$08 ; 7C1F A9 08
+ sta COLOR2 ; 7C21 8D C6 02
+ nop ; 7C24 EA
+ nop ; 7C25 EA
+ nop ; 7C26 EA
+ lda #$00 ; 7C27 A9 00
+ sta COLOR1 ; 7C29 8D C5 02
+
+ ; Protect ourselves from BASIC and the OS
+ lda #<START_ADDRESS ; 7C2C A9 00
+ sta MEMTOP ; 7C2E 8D E5 02
+ lda #>START_ADDRESS ; 7C31 A9 7A
+ sta MEMTOP+1 ; 7C33 8D E6 02
+ rts ; 7C36 60
+
+; ----------------------------------------------------------------------------
+; Call the OPEN vector for the S: device, using the ROM vector table
+; at $E410. The table stores address-minus-one of each routine, which is
+; meant to actually be called via the RTS instruction (standard 6502
+; technique, but confusing the first time you encounter it)
+
+open_s_dev:
+ lda s_dev_open_hi ; 7C37 AD 11 E4
+ pha ; 7C3A 48
+ lda s_dev_open_lo ; 7C3B AD 10 E4
+ pha ; 7C3E 48
+ rts ; 7C3F 60
+
+; ----------------------------------------------------------------------------
+; Callback for CIO CLOSE command. Note that the routine does nothing, really
+; (the OS will mark the E: device as being closed, but COL80 doesn't do any
+; cleanup).
+; The SPECIAL and GET STATUS callbacks in col80_vector_tab also point here.
+
+col80_close:
+ jmp return_success
+
+; ----------------------------------------------------------------------------
+; Callback for the internal put-one-byte, used by the OS to implement the
+; CIO PUT RECORD and PUT BYTES commands. This routine's one argument is
+; the byte in the accumulator (the character to print).
+
+; First, the routine checks for the cursor control characters it supports.
+; COL80 only handles the EOL and clear-screen codes; trying to print
+; backspaces, arrows, deletes, inserts, etc just causes their ATASCII
+; graphics character to print instead.
+
+col80_putbyte:
+ ; EOL (decimal 155)?
+ cmp #$9B ; 7C43 C9 9B
+ bne check_clear ; 7C45 D0 08
+ lda right_margin ; 7C47 AD 00 7C
+ sta COLCRS ; 7C4A 85 55
+ jmp skip_write ; 7C4C 4C 7C 7C
+
+check_clear:
+ ; Clear (decimal 125)?
+ cmp #$7D ; 7C4F C9 7D
+ bne regular_char ; 7C51 D0 03
+ jmp clear_screen ; 7C53 4C 0B 7D
+
+ ; See if this is an inverse video char (code >= 128)
+regular_char:
+ tax ; 7C56 AA
+ bpl not_inverse ; 7C57 10 07
+ lda #$FF ; 7C59 A9 FF
+ sta inverse_mask ; 7C5B 8D 49 7F
+ bne skip_ninv ; 7C5E D0 05
+
+not_inverse:
+ lda #$00 ; 7C60 A9 00
+ sta inverse_mask ; 7C62 8D 49 7F
+
+skip_ninv:
+ txa ; 7C65 8A
+ and #$7F ; 7C66 29 7F
+ sta TMPCHR ; 7C68 85 50
+ lda DINDEX ; 7C6A A5 57
+ cmp #$08 ; 7C6C C9 08
+ beq graphics_ok ; 7C6E F0 03
+ ; If we're not in GRAPHICS 8 mode, reinitialize ourselves
+ jsr col80_open ; 7C70 20 01 7C
+
+graphics_ok:
+ ; Call the routines that actually print the character
+ jsr setup_font_ptr ; 7C73 20 C9 7C
+ jsr setup_screen_ptr ; 7C76 20 34 7D
+ jsr write_font_data ; 7C79 20 82 7D
+
+skip_write:
+ ; Move the cursor 1 space to the right. This will
+ ; advance us to the next line if we're at the margin,
+ ; and scroll the screen if needed
+ jsr advance_cursor ; 7C7C 20 EE 7C
+
+check_ssflag:
+ ; The OS keyboard interrupt handler will toggle SSFLAG (start/stop fla
+ ; any time the user presses ctrl-1
+ lda SSFLAG ; 7C7F AD FF 02
+ bne check_ssflag ; 7C82 D0 FB
+ jmp return_success ; 7C84 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; Scroll the screen up one line (8 scanlines). This has to move almost 8K of
+; data, so it's noticeably slower than scrolling the GR.0 text screen.
+
+scroll_screen:
+ lda SAVMSC ; 7C87 A5 58
+ sta screen_ptr_lo ; 7C89 85 CB
+ clc ; 7C8B 18
+ adc #$40 ; 7C8C 69 40
+ ; font_ptr_lo is actually being used here as a second pointer into
+ ; screen RAM, instead of its usual use as a pointer into the
+ ; font_data table
+ sta font_ptr_lo ; 7C8E 85 CD
+ lda SAVMSC+1 ; 7C90 A5 59
+ sta screen_ptr_hi ; 7C92 85 CC
+ adc #$01 ; 7C94 69 01
+ sta font_ptr_hi ; 7C96 85 CE
+ ldx #$1D ; 7C98 A2 1D
+ ldy #$00 ; 7C9A A0 00
+
+scroll_line_loop:
+ lda (font_ptr_lo),y ; 7C9C B1 CD
+ sta (screen_ptr_lo),y ; 7C9E 91 CB
+ dey ; 7CA0 88
+ bne scroll_line_loop ; 7CA1 D0 F9
+ inc font_ptr_hi ; 7CA3 E6 CE
+ inc screen_ptr_hi ; 7CA5 E6 CC
+ dex ; 7CA7 CA
+ bne scroll_line_loop ; 7CA8 D0 F2
+
+blank_bottom_row:
+ lda SAVMSC ; 7CAA A5 58
+ clc ; 7CAC 18
+ adc #$C0 ; 7CAD 69 C0
+ sta screen_ptr_lo ; 7CAF 85 CB
+ lda SAVMSC+1 ; 7CB1 A5 59
+ adc #$1C ; 7CB3 69 1C
+ sta screen_ptr_hi ; 7CB5 85 CC
+ lda #$00 ; 7CB7 A9 00
+ tay ; 7CB9 A8
+
+blank_loop:
+ sta (screen_ptr_lo),y ; 7CBA 91 CB
+ dey ; 7CBC 88
+ bne blank_loop ; 7CBD D0 FB
+ inc screen_ptr_hi ; 7CBF E6 CC
+ ldy #$40 ; 7CC1 A0 40
+
+blank_tail:
+ sta (screen_ptr_lo),y ; 7CC3 91 CB
+ dey ; 7CC5 88
+ bpl blank_tail ; 7CC6 10 FB
+ rts ; 7CC8 60
+
+; ----------------------------------------------------------------------------
+; Set up font_ptr_lo/hi to point to the font_data bitmap for the character in
+; TMPCHR. Also sets lo_nybble_flag to let the caller know whether the
+; bitmap is in the upper or lower 4 bits of the bytes pointed to.
+
+setup_font_ptr:
+ lda #$00 ; 7CC9 A9 00
+ sta font_ptr_hi ; 7CCB 85 CE
+ sta lo_nybble_flag ; 7CCD 8D 48 7F
+ lda TMPCHR ; 7CD0 A5 50
+ clc ; 7CD2 18
+ ror ; 7CD3 6A
+ bcc font_hi_nybble ; 7CD4 90 05
+ ldx #$FF ; 7CD6 A2 FF
+ stx lo_nybble_flag ; 7CD8 8E 48 7F
+
+font_hi_nybble:
+ clc ; 7CDB 18
+ rol ; 7CDC 2A
+ rol ; 7CDD 2A
+ rol font_ptr_hi ; 7CDE 26 CE
+ rol ; 7CE0 2A
+ rol font_ptr_hi ; 7CE1 26 CE
+ adc #<font_data ; 7CE3 69 00
+ sta font_ptr_lo ; 7CE5 85 CD
+ lda #>font_data ; 7CE7 A9 7A
+ adc font_ptr_hi ; 7CE9 65 CE
+ sta font_ptr_hi ; 7CEB 85 CE
+ rts ; 7CED 60
+
+; ----------------------------------------------------------------------------
+; Move the cursor one space to the right (to the next line if at the margin,
+; and scroll screen if on the last row)
+
+advance_cursor:
+ inc COLCRS ; 7CEE E6 55
+ lda right_margin ; 7CF0 AD 00 7C
+ cmp COLCRS ; 7CF3 C5 55
+ bcs same_line ; 7CF5 B0 13
+ lda LMARGN ; 7CF7 A5 52
+ sta COLCRS ; 7CF9 85 55
+ lda ROWCRS ; 7CFB A5 54
+ ; $17 is 25 decimal, one row below the lowest on the screen
+ cmp #$17 ; 7CFD C9 17
+ bcc no_scroll ; 7CFF 90 07
+ jsr scroll_screen ; 7D01 20 87 7C
+ ; Move to row 24 after scrolling
+ lda #$16 ; 7D04 A9 16
+ sta ROWCRS ; 7D06 85 54
+
+no_scroll:
+ inc ROWCRS ; 7D08 E6 54
+
+same_line:
+ rts ; 7D0A 60
+
+; ----------------------------------------------------------------------------
+; Clear the screen by setting all screen RAM bytes to zero. Slow, but not
+; as slow as scrolling.
+
+clear_screen:
+ lda SAVMSC ; 7D0B A5 58
+ sta screen_ptr_lo ; 7D0D 85 CB
+ lda SAVMSC+1 ; 7D0F A5 59
+ sta screen_ptr_hi ; 7D11 85 CC
+ ldy #$00 ; 7D13 A0 00
+ ldx #$1D ; 7D15 A2 1D
+ lda #$00 ; 7D17 A9 00
+
+cls_loop:
+ sta (screen_ptr_lo),y ; 7D19 91 CB
+ dey ; 7D1B 88
+ bne cls_loop ; 7D1C D0 FB
+ inc screen_ptr_hi ; 7D1E E6 CC
+ dex ; 7D20 CA
+ bne cls_loop ; 7D21 D0 F6
+ jsr blank_bottom_row ; 7D23 20 AA 7C
+ lda LMARGN ; 7D26 A5 52
+ sta COLCRS ; 7D28 85 55
+ lda #$00 ; 7D2A A9 00
+ sta ROWCRS ; 7D2C 85 54
+ ; redundant JMP
+ jmp return_success ; 7D2E 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; CIO expects the Y register to contain a status code.
+; 1 means success (no error). Lots of COL80's routines
+; jump here.
+
+return_success:
+ ldy #$01 ; 7D31 A0 01
+ rts ; 7D33 60
+
+; ----------------------------------------------------------------------------
+; Set screen_ptr_lo/hi to point to the address of the first byte of graphics
+; data at the current cursor position.
+
+setup_screen_ptr:
+ ldy ROWCRS ; 7D34 A4 54
+ lda SAVMSC ; 7D36 A5 58
+ clc ; 7D38 18
+ adc row_low_offset_tab,y ; 7D39 79 52 7D
+ sta screen_ptr_lo ; 7D3C 85 CB
+ lda SAVMSC+1 ; 7D3E A5 59
+ adc row_high_offset_tab,y ; 7D40 79 6A 7D
+ sta screen_ptr_hi ; 7D43 85 CC
+ lda COLCRS ; 7D45 A5 55
+ lsr ; 7D47 4A
+ clc ; 7D48 18
+ adc screen_ptr_lo ; 7D49 65 CB
+ bcc hi_byte_ok ; 7D4B 90 02
+ inc screen_ptr_hi ; 7D4D E6 CC
+
+hi_byte_ok:
+ sta screen_ptr_lo ; 7D4F 85 CB
+ rts ; 7D51 60
+
+; ----------------------------------------------------------------------------
+; Tables of offsets for setup_screen_ptr, to avoid doing multiplication at
+; runtime (the 6502 lacks a MUL instruction, so it's slow...)
+
+row_low_offset_tab:
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D52
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D5A
+ .byte $00,$40,$80,$C0,$00,$40,$80,$C0 ; 7D62
+
+row_high_offset_tab:
+ .byte $00,$01,$02,$03,$05,$06,$07,$08 ; 7D6A
+ .byte $0A,$0B,$0C,$0D,$0F,$10,$11,$12 ; 7D72
+ .byte $14,$15,$16,$17,$19,$1A,$1B,$1C ; 7D7A
+
+; ----------------------------------------------------------------------------
+; Copy pixel data from the font table to screen RAM.
+; font_ptr_lo/hi must point to the correct character, and screen_ptr_lo/hi
+; must point to the correct screen address for the current cursor position.
+; This routine has separate execution paths for even- and odd-numbered
+; cursor positions, since each byte of screen RAM holds data for two
+; adjacent characters (and when printing to one of them, the other needs
+; to be left undisturbed!)
+
+write_font_data:
+ lda COLCRS ; 7D82 A5 55
+ clc ; 7D84 18
+ ror ; 7D85 6A
+ bcc write_font_data_even ; 7D86 90 31
+ ldx #$00 ; 7D88 A2 00
+ ldy #$00 ; 7D8A A0 00
+
+get_font_nybble_odd:
+ lda (font_ptr_lo),y ; 7D8C B1 CD
+ bit lo_nybble_flag ; 7D8E 2C 48 7F
+ bne lo_nybble_odd ; 7D91 D0 04
+ ; glyph we want is stored in top 4 bits of font byte,
+ ; shift it down to the bottom 4 bits
+ lsr ; 7D93 4A
+ lsr ; 7D94 4A
+ lsr ; 7D95 4A
+ lsr ; 7D96 4A
+
+lo_nybble_odd:
+ eor inverse_mask ; 7D97 4D 49 7F
+ and #$0F ; 7D9A 29 0F
+ sta TMPCHR ; 7D9C 85 50
+ ldy scanline_offset_tab,x ; 7D9E BC EA 7D
+ lda (screen_ptr_lo),y ; 7DA1 B1 CB
+ and #$F0 ; 7DA3 29 F0
+ ora TMPCHR ; 7DA5 05 50
+ sta (screen_ptr_lo),y ; 7DA7 91 CB
+ inx ; 7DA9 E8
+ cpx #$07 ; 7DAA E0 07
+ bne screen_ptr_ok_odd ; 7DAC D0 02
+ inc screen_ptr_hi ; 7DAE E6 CC
+
+screen_ptr_ok_odd:
+ cpx #$08 ; 7DB0 E0 08
+ beq write_font_done_odd ; 7DB2 F0 04
+ txa ; 7DB4 8A
+ tay ; 7DB5 A8
+ bne get_font_nybble_odd ; 7DB6 D0 D4
+
+write_font_done_odd:
+ rts ; 7DB8 60
+
+; ----------------------------------------------------------------------------
+; Write data to even-numbered columns, very similar to the above
+
+write_font_data_even:
+ ldx #$00 ; 7DB9 A2 00
+ ldy #$00 ; 7DBB A0 00
+
+get_font_nybble_even:
+ lda (font_ptr_lo),y ; 7DBD B1 CD
+ bit lo_nybble_flag ; 7DBF 2C 48 7F
+ beq hi_nybble_even ; 7DC2 F0 04
+ asl ; 7DC4 0A
+ asl ; 7DC5 0A
+ asl ; 7DC6 0A
+ asl ; 7DC7 0A
+
+hi_nybble_even:
+ eor inverse_mask ; 7DC8 4D 49 7F
+ and #$F0 ; 7DCB 29 F0
+ sta TMPCHR ; 7DCD 85 50
+ ldy scanline_offset_tab,x ; 7DCF BC EA 7D
+ lda (screen_ptr_lo),y ; 7DD2 B1 CB
+ and #$0F ; 7DD4 29 0F
+ ora TMPCHR ; 7DD6 05 50
+ sta (screen_ptr_lo),y ; 7DD8 91 CB
+ inx ; 7DDA E8
+ cpx #$07 ; 7DDB E0 07
+ bne screen_ptr_ok_even ; 7DDD D0 02
+ inc screen_ptr_hi ; 7DDF E6 CC
+
+screen_ptr_ok_even:
+ cpx #$08 ; 7DE1 E0 08
+ beq write_font_done_even ; 7DE3 F0 04
+ txa ; 7DE5 8A
+ tay ; 7DE6 A8
+ bne get_font_nybble_even ; 7DE7 D0 D4
+
+write_font_done_even:
+ rts ; 7DE9 60
+
+; ----------------------------------------------------------------------------
+
+scanline_offset_tab:
+ .byte $00,$28,$50,$78,$A0,$C8,$F0,$18 ; 7DEA
+
+; ----------------------------------------------------------------------------
+; Callback for the internal get-one-byte, used by the OS to implement the
+; CIO GET RECORD and GET BYTES commands. This routine takes no arguments,
+; and returns the read byte in the accumulator.
+
+; Internally, COL80 maintains a line buffer. Each time col80_getbyte is
+; called, it returns the next character in the buffer. If the buffer's
+; empty (or if the last call returned the last character), a new line
+; of input is read from the user (and the first character is returned).
+; This is exactly how the OS E: device works.
+
+col80_getbyte:
+ lda BUFCNT ; 7DF2 A5 6B
+ beq get_line ; 7DF4 F0 0E
+
+get_next_byte:
+ ldx line_buffer_index ; 7DF6 AE 4A 7F
+ lda line_buffer,x ; 7DF9 BD 4B 7F
+ dec BUFCNT ; 7DFC C6 6B
+ inc line_buffer_index ; 7DFE EE 4A 7F
+ jmp return_success ; 7E01 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; Get a line of input from the user, terminated by the Return key.
+
+get_line:
+ lda #$00 ; 7E04 A9 00
+ sta BUFCNT ; 7E06 85 6B
+ sta line_buffer_index ; 7E08 8D 4A 7F
+
+show_cursor:
+ lda #$20 ; 7E0B A9 20
+ sta TMPCHR ; 7E0D 85 50
+ lda #$FF ; 7E0F A9 FF
+ sta inverse_mask ; 7E11 8D 49 7F
+ jsr setup_font_ptr ; 7E14 20 C9 7C
+ jsr setup_screen_ptr ; 7E17 20 34 7D
+ jsr write_font_data ; 7E1A 20 82 7D
+ jsr get_keystroke ; 7E1D 20 B7 7E
+ cpy #$01 ; 7E20 C0 01
+ beq keystroke_ok ; 7E22 F0 07
+ ldy #$00 ; 7E24 A0 00
+ sty line_buffer_index ; 7E26 8C 4A 7F
+ sty BUFCNT ; 7E29 84 6B
+
+keystroke_ok:
+ cmp #$9B ; 7E2B C9 9B
+ bne check_backs_key ; 7E2D D0 03
+ jmp return_key_hit ; 7E2F 4C 52 7E
+
+check_backs_key:
+ cmp #$7E ; 7E32 C9 7E
+ bne check_clear_key ; 7E34 D0 03
+ jmp backs_key_hit ; 7E36 4C 71 7E
+
+check_clear_key:
+ cmp #$7D ; 7E39 C9 7D
+ bne normal_key_hit ; 7E3B D0 03
+ jmp clear_key_hit ; 7E3D 4C 64 7E
+
+normal_key_hit:
+ ldx BUFCNT ; 7E40 A6 6B
+ bpl buffer_character ; 7E42 10 03
+ jmp beep ; 7E44 4C 8F 7E
+
+buffer_character:
+ sta line_buffer,x ; 7E47 9D 4B 7F
+ jsr col80_putbyte ; 7E4A 20 43 7C
+ inc BUFCNT ; 7E4D E6 6B
+ jmp show_cursor ; 7E4F 4C 0B 7E
+
+return_key_hit:
+ jsr print_space ; 7E52 20 A4 7E
+ lda #$9B ; 7E55 A9 9B
+ ldx BUFCNT ; 7E57 A6 6B
+ sta line_buffer,x ; 7E59 9D 4B 7F
+ inc BUFCNT ; 7E5C E6 6B
+ jsr col80_putbyte ; 7E5E 20 43 7C
+ jmp get_next_byte ; 7E61 4C F6 7D
+
+clear_key_hit:
+ jsr clear_screen ; 7E64 20 0B 7D
+ lda #$00 ; 7E67 A9 00
+ sta line_buffer_index ; 7E69 8D 4A 7F
+ sta BUFCNT ; 7E6C 85 6B
+ jmp get_line ; 7E6E 4C 04 7E
+
+backs_key_hit:
+ jsr print_space ; 7E71 20 A4 7E
+ lda BUFCNT ; 7E74 A5 6B
+ beq backs_key_done ; 7E76 F0 14
+ dec COLCRS ; 7E78 C6 55
+ lda COLCRS ; 7E7A A5 55
+ clc ; 7E7C 18
+ adc #$01 ; 7E7D 69 01
+ cmp LMARGN ; 7E7F C5 52
+ bne backs_same_line ; 7E81 D0 07
+ lda right_margin ; 7E83 AD 00 7C
+ sta COLCRS ; 7E86 85 55
+ dec ROWCRS ; 7E88 C6 54
+
+backs_same_line:
+ dec BUFCNT ; 7E8A C6 6B
+
+backs_key_done:
+ jmp show_cursor ; 7E8C 4C 0B 7E
+
+; ----------------------------------------------------------------------------
+; Ring the margin bell. COL80 doesn't implement the ctrl-2 bell (character
+; 253), and instead of using the GTIA keyclick speaker, it uses POKEY to
+; make a beep
+
+beep: ldy #$00 ; 7E8F A0 00
+ ldx #$AF ; 7E91 A2 AF
+
+beep_delay_x:
+ stx AUDF1 ; 7E93 8E 00 D2
+ stx AUDC1 ; 7E96 8E 01 D2
+
+beep_delay_y:
+ dey ; 7E99 88
+ bne beep_delay_y ; 7E9A D0 FD
+ dex ; 7E9C CA
+ cpx #$9F ; 7E9D E0 9F
+ bne beep_delay_x ; 7E9F D0 F2
+ jmp show_cursor ; 7EA1 4C 0B 7E
+
+; ----------------------------------------------------------------------------
+; Print a space character at the current cursor position. Does not
+; update the cursor position.
+print_space:
+ lda #$00 ; 7EA4 A9 00
+ sta inverse_mask ; 7EA6 8D 49 7F
+ lda #$20 ; 7EA9 A9 20
+ sta TMPCHR ; 7EAB 85 50
+ jsr setup_font_ptr ; 7EAD 20 C9 7C
+ jsr setup_screen_ptr ; 7EB0 20 34 7D
+ jsr write_font_data ; 7EB3 20 82 7D
+ rts ; 7EB6 60
+
+; ----------------------------------------------------------------------------
+; Get a keystroke (blocking). Just calls the OS K: get-one-byte routine
+; (call by pushing address-minus-one then doing an RTS)
+get_keystroke:
+ lda k_dev_get_hi ; 7EB7 AD 25 E4
+ pha ; 7EBA 48
+ lda k_dev_get_lo ; 7EBB AD 24 E4
+ pha ; 7EBE 48
+ rts ; 7EBF 60
+
+; ----------------------------------------------------------------------------
+; Initialization callback. The OS will call this on coldstart (or would do,
+; if the driver were in ROM), and also on warmstart (because we stole the
+; DOSINI vector).
+; This routine is also the first thing that gets called by the mainline
+; init code. Its job is to install COL80 in the handler table at HATABS.
+; Actually the handler is first installed as X:, then the main init code
+; fixes this up to E: unless the user is holding down SELECT. This allows
+; the user to toggle between the 40-column ROM E: and COL80 without doing
+; a full reboot. No idea if this was a documented feature or something the
+; author used for development/debugging.
+
+col80_init:
+ ldy #$00 ; 7EC0 A0 00
+
+next_hatab_slot:
+ lda HATABS,y ; 7EC2 B9 1A 03
+ beq register_x_handler ; 7EC5 F0 0A
+ iny ; 7EC7 C8
+ iny ; 7EC8 C8
+ iny ; 7EC9 C8
+ cpy #$20 ; 7ECA C0 20
+ bcc next_hatab_slot ; 7ECC 90 F4
+ jmp return_success ; 7ECE 4C 31 7D
+
+register_x_handler:
+ lda #$58 ; 7ED1 A9 58
+ sta HATABS,y ; 7ED3 99 1A 03
+ lda #<col80_vector_tab ; 7ED6 A9 E5
+ iny ; 7ED8 C8
+ sta HATABS,y ; 7ED9 99 1A 03
+ lda #>col80_vector_tab ; 7EDC A9 7E
+ iny ; 7EDE C8
+ sta HATABS,y ; 7EDF 99 1A 03
+ jmp return_success ; 7EE2 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; COL80 vector table, in the format required by the OS. Our HATABS entry
+; will point to this table, and the OS will call the routines listed here
+; via the "call by RTS" method (which is why they're address-minus-one).
+
+; See the entry on HATABS in "Mapping the Atari" or the OS manual.
+
+col80_vector_tab:
+ .word col80_open-1 ; 7EE5 00 7C
+ .word col80_close-1 ; 7EE7 3F 7C
+ .word col80_getbyte-1 ; 7EE9 F1 7D
+ .word col80_putbyte-1 ; 7EEB 42 7C
+ .word col80_close-1 ; 7EED 3F 7C
+ .word col80_close-1 ; 7EEF 3F 7C
+ jmp col80_init ; 7EF1 4C C0 7E
+
+; ----------------------------------------------------------------------------
+; The OS jumps here on warmstart (also, this is the run address in our
+; binary load file)
+
+dosini_entry_point:
+ nop ; 7EF4 EA
+ nop ; 7EF5 EA
+ nop ; 7EF6 EA
+
+main_entry_point:
+ jsr col80_init ; 7EF7 20 C0 7E
+ lda CONSOL ; 7EFA AD 1F D0
+ and #$04 ; 7EFD 29 04
+ beq no_e_handler ; 7EFF F0 2F
+ lda #$0C ; 7F01 A9 0C
+ sta ICCOM ; 7F03 8D 42 03
+ ldx #$00 ; 7F06 A2 00
+ jsr CIOV ; 7F08 20 56 E4
+ lda #$58 ; 7F0B A9 58
+ sta font_ptr_lo ; 7F0D 85 CD
+ lda #$03 ; 7F0F A9 03
+ sta ICCOM ; 7F11 8D 42 03
+ lda #$CD ; 7F14 A9 CD
+ sta ICBAL ; 7F16 8D 44 03
+ lda #$00 ; 7F19 A9 00
+ sta ICBAH ; 7F1B 8D 45 03
+ ldx #$00 ; 7F1E A2 00
+ jsr CIOV ; 7F20 20 56 E4
+ ldy #$07 ; 7F23 A0 07
+ lda #<col80_vector_tab ; 7F25 A9 E5
+ sta HATABS,y ; 7F27 99 1A 03
+ lda #>col80_vector_tab ; 7F2A A9 7E
+ iny ; 7F2C C8
+ sta HATABS,y ; 7F2D 99 1A 03
+no_e_handler:
+ lda #<START_ADDRESS ; 7F30 A9 00
+ sta MEMTOP ; 7F32 8D E5 02
+ lda #>START_ADDRESS ; 7F35 A9 7A
+ sta MEMTOP+1 ; 7F37 8D E6 02
+ jmp return_success ; 7F3A 4C 31 7D
+
+; ----------------------------------------------------------------------------
+; (when does this actually get called? da65 can't find any references
+; to it, and it's not a run or init address in the binary load file)
+ lda #<dosini_entry_point ; 7F3D A9 F4
+ sta DOSINI ; 7F3F 85 0C
+ lda #>dosini_entry_point ; 7F41 A9 7E
+ sta DOSINI+1 ; 7F43 85 0D
+ jmp main_entry_point ; 7F45 4C F7 7E
+
+; ----------------------------------------------------------------------------
+; Various bits of runtime state here. It's unclear to me why the standard
+; OS buffer location couldn't have been used instead (normally the top
+; half of page 5), or why the other stuff couldn't have been stored in
+; zero page, in locations used by the ROM E: handler (thus unused when
+; it's replaced with COL80). line_buffer_index needs to be preserved
+; across calls to col80_getbyte, but lo_nybble_flag and inverse_mask are
+; freshly calculated every time they're used, so they could be almost
+; anywhere.
+
+lo_nybble_flag:
+ .byte $00 ; 7F48 00
+
+inverse_mask:
+ .byte $00 ; 7F49 00
+
+line_buffer_index:
+ .byte $12 ; 7F4A 12
+
+; ----------------------------------------------------------------------------
+; There's absolutely no reason why this data needs to be included in the
+; binary load file: the line buffer's initial contents are meaningless, they
+; will be blown away the first time anything reads from the E: device.
+
+; Notice the author was running his debugger in COL80 when he built the
+; binary (ASCII "S COL80 7A00 7F80" command still in the buffer).
+
+line_buffer:
+ .byte $53,$20,$43,$4F,$4C,$38,$30,$20 ; 7F4B
+ .byte $37,$41,$30,$30,$20,$37,$46,$38 ; 7F53
+ .byte $30,$9B,$20,$20,$20,$20,$9B,$27 ; 7F5B
+ .byte $40,$40,$40,$40,$28,$28,$28,$28 ; 7F63
+ .byte $40,$40,$40,$40,$40,$40,$40,$40 ; 7F6B
+ .byte $40,$40,$40,$40,$40,$40,$40,$40 ; 7F73
+ .byte $9B,$FD,$FD,$FD,$FD,$9B ; 7F7B
+
+END_ADDRESS = *-1
+
+; I've found a variant (modified version?) of this code, that doesn't
+; include the line_buffer in the file (no reason for it to be there),
+; or the $0C segment, and that has another segment, loaded at $6000,
+; with the run address changed to $6000. The code looks like:
+
+; .org $6000
+; jsr dosini_entry_point
+; lda #$50
+; sta RMARGN
+; lda #$00
+; sta COLOR2
+
+; also, the default colors have been changed in init_graphics_8.
+
+; There are at least two binaries floating around that contain
+; extra (garbage) bytes at the end, presumably from being transferred
+; over XMODEM or similar. They are otherwise identical.
+
diff --git a/col80_orig.xex b/col80_orig.xex
new file mode 100644
index 0000000..f5f9548
--- /dev/null
+++ b/col80_orig.xex
Binary files differ
diff --git a/col80_runad_seg.s b/col80_runad_seg.s
new file mode 100644
index 0000000..ea84019
--- /dev/null
+++ b/col80_runad_seg.s
@@ -0,0 +1,6 @@
+
+; Third segment is the run address
+
+ .word RUNAD
+ .word RUNAD+1
+ .word dosini_entry_point
diff --git a/col80_startaddr.s b/col80_startaddr.s
new file mode 100644
index 0000000..4ad7c09
--- /dev/null
+++ b/col80_startaddr.s
@@ -0,0 +1,2 @@
+
+START_ADDRESS = $7A00
diff --git a/dasm2atasm b/dasm2atasm
new file mode 100755
index 0000000..b7ebe66
--- /dev/null
+++ b/dasm2atasm
@@ -0,0 +1,362 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dasm2atasm - converts 6502 assembly in DASM syntax to ATASM (or MAC/65) format.
+
+=head1 SYNOPSIS
+
+ dasm2atasm mycode.asm
+
+Writes output to I<mycode.m65>
+
+ dasm2atasm stuff.asm other.m65
+
+Reads from I<stuff.asm>, writes to I<other.m65>
+
+=head1 DESCRIPTION
+
+B<dasm2atasm> tries its best to convert DASM's syntax into something
+that B<ATASM> can use. Since B<ATASM>'s syntax is 99% compatible with
+that of B<MAC/65>, B<dasm2atasm> can be used for that as well.
+
+=head1 CAVEATS
+
+There are a few B<DASM> directives that aren't quite supported by
+B<ATASM>.
+
+=over 4
+
+=item echo
+
+In B<DASM> syntax, I<echo> can interpolate values, like so:
+
+ echo $100-*, " bytes of zero page left"
+
+B<ATASM>'s closest equivalent to I<echo> is I<.warn>, but it doesn't
+allow multiple arguments or interpolation. For now, B<dasm2atasm> just
+comments out the line with the I<echo> directive.
+
+=item seg and seg.u
+
+B<ATASM> just plain doesn't support segments. These directives will
+just be commented out. This I<shouldn't> have any effect on the
+object code.
+
+=item sta.w, sty.w, stx.w
+
+B<ATASM> doesn't provide a way to force word addressing, when the operand
+of a store instruction will allow zero page addressing to be used. You'll
+run into this a lot in Atari 2600 code, or any other 6502 code that has to
+maintain sync with an external piece of hardware (using word addressing
+causes the 6502 to use an extra CPU cycle, which is the only way to cause
+a 1-cycle delay).
+
+For now, we're just converting any I<st?.w> instructions to the appropriate
+I<.byte> directives, like so:
+
+ ;;;;; dasm2atasm: was `sta.w COLUPF', using .byte to generate opcode
+ .byte $8d, COLUPF, $00
+
+This works fine if I<COLUPF> is a zero-page label. It's possible, though
+unlikely, that you'll run across code where the programmer has used I<sta.w>
+with a label that would already cause absolute word addressing to be used,
+in which case the extra I<$00> will break our code (literally: I<$00> is
+the I<BRK> instruction!)
+
+This isn't likely to be fixed by I<dasm2atasm>. The correct fix will be to
+support I<sta.w> and friends in B<ATASM> itself, which may happen in the
+future.
+
+=item . (dot)
+
+B<DASM> allows the use of I<.> or I<*> to represent the current program counter
+in expressions. B<ATASM> only allows I<*>, and unless I want to include a
+full expression-parser in B<dasm2atasm>, I can't reliably translate this.
+
+For now, you'll have to fix this yourself. Future versions will at least
+make an attempt, but this one doesn't.
+
+=back
+
+=head1 AUTHOR
+
+B. Watson I<< <urchlay@urchlay.com> >>. Comments & constructive criticism
+welcome, or just a friendly `hello'. Spam will be redirected to /dev/null
+and so will the spammer's entire domain.
+
+=cut
+
+sub usage {
+ print <<EOF;
+Usage: $0 -[aclmr] infile.asm [outfile.m65]
+
+EOF
+ exit 1;
+}
+
+sub get_mac_sub {
+ my $rex = shift;
+ my $code = "sub { s/($rex)/\\U\$1/gio };";
+ #warn "code is $code";
+ return eval "$code";
+}
+
+sub unhex {
+ # makes a proper $xx, $xx, $xx list of bytes
+ # from a list of hex digits, spaces optional.
+ my $bytes = shift;
+ my $ret = "";
+
+ $bytes =~ s/\s//g;
+
+ #warn "unhex: bytes is $bytes";
+
+ for($bytes =~ /(..)/g) {
+ #warn "unhex: found $_";
+ $ret .= "\$$_, ";
+ }
+
+ chop $ret;
+ chop $ret;
+
+ return $ret;
+}
+
+sub fix_include {
+ my $inc = shift;
+ my $old = $inc;
+ $inc =~ s/\.(\w+)("?)$/.m65$2/;
+
+ if($recursive) {
+ system("$cmd $old $inc");
+ } else {
+ warn "Don't forget to convert included file `$old' to .m65 format!\n";
+ }
+ return $inc;
+}
+
+sub do_subs {
+ # Do the dirty work of the substitutions. Only reason we have this
+ # as a subroutine of its own is for profiling purposes (and we do
+ # spend a *lot* of time here!)
+ my $line = shift;
+
+ for($line) {
+ s/^(\@?\w+):/$1/; # no colons after labels, in atasm
+ s/%/~/g; # binary constant
+ s/!=/<>/g; # inequality
+
+ s/^(\s+)\.?echo(.*)/;;;;;$1.warn$2/i &&
+ do { warn "$in, line $.:\n\t`.warn' not fully compatible with dasm's `echo', commented out\n" }
+ && next;
+
+ # This is supposed to change e.g. `bpl .label' to `bpl @label'
+ s/^(\s+)([a-z]{3})(\s+)\.(\w+)/$1$2$3\@$4/i
+ && next;
+
+
+ s/{(\d)}/%$1/g; # macro arg (gotta do this *after* bin. constants!)
+
+# atasm doesn't support shifts, emulate with multiply/divide
+ s/\s*<<\s*(\d+)/"*" . 2**$1/eg;
+ s/\s*>>\s*(\d+)/"\/" . 2**$1/eg;
+
+# atasm chokes sometimes when there's whitespace around an operator
+# unfortunately, a construct like `bne *-1' can't afford to lose the
+# space before the *... why, oh why, does * have to be both multiply and
+# program counter? *sigh*
+
+# s/\s*([-!|\/+*&])\s*/$1/g;
+
+# ARGH. Why does dasm allow `byte #1, #2, #3'... and why do people *use* it?!
+ s/^(\s+)\.?byte(\s+)/$1.byte$2/i && do { s/#//g } && next;
+ s/^(\s+)\.?word(\s+)/$1.word$2/i && do { s/#//g } && next;
+ s/^(\s+)\.?dc\.w(\s+)/$1.word$2/i && do { s/#//g } && next;
+ s/^(\s+)\.?dc(?:\.b)?(\s+)/$1.byte$2/i && do { s/#//g } && next;
+
+# 20070529 bkw: turn ".DS foo" into ".DC foo 0"
+ s/^(\s+)\.?ds(\s+)(\S+)/$1.dc $3 0 /i && do { s/#//g } && next;
+
+# I really want to add `hex' to atasm. 'til then though, fake with .byte
+ s/^(\s+)\.?hex\s+(.*)/$1 . '.byte ' .
+ unhex($2)/ie && next;
+
+ s/^(\s+)\.?subroutine(.*)/$1.local$2/i && next;
+ s/^(\s+)\.?include(\s+)(.*)/$1 . '.include' . $2 . fix_include($3)/gie
+ && next;
+ s/^(\s+)\.?equ\b/$1=/i && next;
+ s/^(\s+)\.?repeat\b/$1.rept/i && next;
+ s/^(\s+)\.?repend\b/$1.endr/i && next;
+ s/^(\s+)\.?endm\b/$1.endm/i && next;
+ s/^(\s+)\.?org(\s+)([^,]*).*$/$1*=$2$3/i && next;
+ s/^(\s+)\.?incbin\b/$1\.incbin/i && next;
+ s/^(\s+)\.?err(.*)/$1.error$2/i && next; # TODO: see if atasm allows `.error' with no message.
+ s/^(\s+)\.?ifconst\s+(.*)/$1.if .def $2/i
+ && next; # TODO: test this!
+ s/^(\s+)\.?else/$1.else/i && next;
+ s/^(\s+)\.?endif/$1.endif/i && next;
+ s/^(\s+)\.?if\s+(.*)/$1.if $2/i && next;
+
+ # stuff that doesn't work:
+ s/^(\s+)(\.?seg(\..)?\s.*)/;;;;; dasm2atasm: `seg' not supported by atasm\n;;;;;$1$2/i
+ && next;
+ s/^(\s+)(\.?processor\s.*)/;;;;; dasm2atasm: `processor' not supported by atasm\n;;;;;$1$2/i
+ && next;
+
+ s/^(\s+)sta\.w(\s+)(.*)/;;;;; dasm2atasm: was `sta.w $3', using .byte to generate opcode\n$1.byte \$8d, <$3, >$3/i
+ && next;
+
+ s/^(\s+)stx\.w(\s+)(.*)/;;;;; dasm2atasm: was `stx.w $3', using .byte to generate opcode\n$1.byte \$8e, <$3, >$3/i
+ && next;
+
+ s/^(\s+)sta\.w(\s+)(.*)/;;;;; dasm2atasm: was `sty.w $3', using .byte to generate opcode\n$1.byte \$8c, <$3, >$3/i
+ && next;
+
+ # atasm lacks `align', so make up for it with a macro
+ if(s/(\s)\.?align(\s+)(.*)/$1ALIGN$2$3/i) {
+ if(!$align_defined) { # only gotta define it if not already defined.
+ for($align_macro) {
+ $_ =~ s/^/($linenum += 10) . " "/gme if $linenum;
+ $_ =~ s/\n/\x9b/g if $a8eol;
+ }
+
+ print OUT $align_macro; # no, I wouldn't use these globals in a CS class assignment.
+ $align_defined++;
+ }
+ next;
+ }
+
+ # macros. This is by far the biggest pain in the ass yet.
+ s/(\s)\.?mac\b/$1.macro/i;
+ if(/(\s)\.macro(\s+)(\w+)/) {
+ $mac_regex .= "|\\b$3\\b";
+ $mac_sub = get_mac_sub($mac_regex);
+ }
+
+ if(ref $mac_sub) { # if we've found at least one macro so far...
+ &$mac_sub; # CAPITALIZE everything matching a macro name
+ } # note: this code assumes macros are *always* defined before they're
+ # used. atasm requires this, but does dasm?
+
+ }
+ return $line;
+}
+
+## main() ##
+
+$ca65 = 0;
+$a8eol = 0;
+$linenum = 0;
+$recursive = 0;
+
+$cmd = $0;
+
+while($ARGV[0] =~ /^-/i) {
+ my $opt = shift;
+ $cmd .= " $opt";
+
+ if($opt eq "-c") {
+ $ca65++;
+ } elsif($opt eq "-a") {
+ $a8eol++;
+ } elsif($opt eq "-l") {
+ $linenum = 1000;
+ } elsif($opt eq "-m") {
+ $a8eol++;
+ $linenum = 1000;
+ } elsif($opt eq "-r") {
+ $recursive++;
+ } elsif($opt eq "--") {
+ last;
+ } else {
+ warn "Unknown option '$opt'\n";
+ usage;
+ }
+}
+
+if($ca65 && ($linenum || $a8eol)) {
+ die "Can't use line numbers and/or Atari EOLs with ca65 output\n";
+}
+
+$align_macro = <<EOF;
+;;;;;; ALIGN macro defined by dasm2atasm
+ .macro ALIGN
+ *= [[*/%1]+1] * %1
+ .endm
+EOF
+
+$align_defined = 0; # we only need to emit the macro definition once.
+
+$in = shift || usage;
+$out = shift;
+
+($out = $in) =~ s/(\.\w+)?$/.m65/ unless $out;
+
+die "$0: can't use $in for both input and output\n" if $out eq $in;
+
+open IN, "<$in" or die "Can't read $in: $!\n";
+open OUT, ">$out" or die "Can't write to $out: $!\n";
+
+$hdr = <<EOF;
+;;; Converted from DASM syntax with command:
+; $cmd $in $out
+
+EOF
+
+for($hdr) {
+ $_ =~ s/^/($linenum += 10) . " "/gme if $linenum;
+ $_ =~ s/\n/\x9b/g if $a8eol;
+}
+
+print OUT $hdr;
+
+if($ca65) {
+ print OUT <<EOF;
+;;; ca65 features enabled by dasm2atasm
+; To build with ca65:
+; ca65 -o foo.o -t none foo.asm
+; ld65 -o foo.bin -t none foo.o
+.FEATURE pc_assignment
+.FEATURE labels_without_colons
+
+EOF
+}
+
+$mac_regex = "!THIS_ISNT_SUPPOSED_TO_MATCH";
+$mac_sub = ""; # this will be the code ref we call to match $mac_regex
+
+while(<IN>) {
+ chomp;
+ s/\r//; # you might not want this on dos/win, not sure if it matters.
+ $label = "";
+
+ if(/^(\w+)\s*=\s*\1/i) {
+ print OUT ";;;;; dasm2atasm: labels are case-insensitive in atasm\n";
+ $line = ";;;;; $_ ; This assignment is an error in atasm";
+ next;
+ }
+
+# do this before we split out the label:
+ s/^\./\@/; # local label (dot in dasm, @ in atasm)
+
+ if(s/^([^:;\s]*):?(\s+)/$2/) {
+ $label = $1;
+ }
+
+ ($line, $comment) = split /;/, $_, 2;
+ next unless $line;
+
+ $line = do_subs($line);
+
+} continue {
+ if($linenum) {
+ print OUT "$linenum ";
+ $linenum += 10;
+ }
+
+ print OUT $label if $label;
+ print OUT $line if $line;
+ print OUT ";$comment" if $comment;
+ print OUT ($a8eol ? "\x9b" : "\n");
+}