diff options
-rw-r--r-- | Makefile | 31 | ||||
-rw-r--r-- | README.txt | 89 | ||||
-rw-r--r-- | col80.atasm | 6 | ||||
-rw-r--r-- | col80.dasm | 10 | ||||
-rw-r--r-- | col80.o | bin | 0 -> 12229 bytes | |||
-rw-r--r-- | col80.s | 19 | ||||
-rw-r--r-- | col80.xex | bin | 0 -> 1429 bytes | |||
-rw-r--r-- | col80_dosini_seg.s | 9 | ||||
-rw-r--r-- | col80_header_seg.atasm | 9 | ||||
-rw-r--r-- | col80_header_seg.s | 6 | ||||
-rw-r--r-- | col80_main.atasm | 898 | ||||
-rw-r--r-- | col80_main.s | 895 | ||||
-rw-r--r-- | col80_orig.xex | bin | 0 -> 1429 bytes | |||
-rw-r--r-- | col80_runad_seg.s | 6 | ||||
-rw-r--r-- | col80_startaddr.s | 2 | ||||
-rwxr-xr-x | dasm2atasm | 362 |
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 + Binary files differ@@ -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 Binary files differnew file mode 100644 index 0000000..f5f9548 --- /dev/null +++ b/col80.xex 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 Binary files differnew file mode 100644 index 0000000..f5f9548 --- /dev/null +++ b/col80_orig.xex 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"); +} |