diff --git a/Makefile b/Makefile index deb43b7..9320ac4 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ LDFLAGS = --config apple2-asm.cfg TARGETS = bye.system.SYS buhbye.system.SYS quit.system.SYS +# For timestamps +MM = $(shell date "+%-m") +DD = $(shell date "+%-d") +YY = $(shell date "+%-y") +DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) + .PHONY: clean all all: $(TARGETS) @@ -14,7 +20,7 @@ clean: rm -f $(TARGETS) %.o: %.s $(HEADERS) - ca65 $(CAFLAGS) --listing $(basename $@).list -o $@ $< + ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $< %.SYS: %.o ld65 $(LDFLAGS) -o $@ $< diff --git a/buhbye.system.s b/buhbye.system.s index ab569e1..14e8662 100644 --- a/buhbye.system.s +++ b/buhbye.system.s @@ -3,10 +3,18 @@ ;;; (so this is Bell's Better Bird's Better Bye - Buh-Bye) ;;; * alpha key advances to next matching filename ;;; * replaced directory enumeration (smaller, per PDTRM) +;;; * installs, then chains to next .SYSTEM file .setcpu "65C02" + .linecont + + .feature string_escapes + .include "apple2.inc" - .include "prodos.inc" + .include "apple2.mac" + + .include "inc/apple2.inc" + .include "inc/macros.inc" + .include "inc/prodos.inc" ;;; Miscellaneous @@ -14,12 +22,6 @@ COL80HPOS := $57B ;;; I/O Soft Switches / Firmware -RAMRDOFF := $C002 ; If 80STORE Off: Read Main Mem $0200-$BFFF -RAMRDON := $C003 ; If 80STORE Off: Read Aux Mem $0200-$BFFF -RAMWRTOFF := $C004 ; If 80STORE Off: Write Main Mem $0200-$BFFF -RAMWRTON := $C005 ; If 80STORE Off: Write Aux Mem $0200-$BFFF -ALTZPOFF := $C008 ; Main Stack and Zero Page -ALTZPON := $C009 ; Aux Stack and Zero Page ROMINNW := $C082 ; Read ROM; no write ROMINWB1 := $C089 ; Read ROM; write RAM bank 1 @@ -31,10 +33,7 @@ SETTXT := $FB39 TABV := $FB5B SETPWRC := $FB6F BELL1 := $FBDD -HOME := $FC58 -COUT := $FDED SETINV := $FE80 -SETNORM := $FE84 ;;; ASCII/Key codes ASCII_TAB := $9 @@ -47,21 +46,10 @@ ASCII_ETB := $17 ; scroll text window down ASCII_EM := $19 ; move cursor to upper left ASCII_ESCAPE := $1B -;;; ------------------------------------------------------------ - -.define HI(char) (char|$80) - -.macro HIASCII arg - .repeat .strlen(arg), i - .byte .strat(arg, i) | $80 - .endrep -.endmacro - -.macro HIASCIIZ arg - HIASCII arg - .byte 0 -.endmacro +;;; ************************************************************ + .include "driver_preamble.inc" +;;; ************************************************************ ;;; ------------------------------------------------------------ @@ -76,11 +64,10 @@ ASCII_ESCAPE := $1B ;;; Installer ;;; ------------------------------------------------------------ - .org $2000 - install_size := $300 ; must fit in $D100...$D3FF = $300 -.proc install +.proc maybe_install_driver + src := install_src end := install_src + install_size dst := $D100 ; Install location in ProDOS (bank 2) @@ -123,28 +110,17 @@ loop: lda (src_ptr) ; *src_ptr = *dst_ptr sta ALTZPOFF sta ROMINWB1 sta ROMINWB1 - ;; fall through -.endproc -.proc quit - MLI_CALL QUIT, params - -.proc params -params: .byte 4 -type: .byte 0 -res1: .word 0 -res2: .byte 0 -res3: .addr 0 + rts .endproc -.endproc - - install_src := * ;;; ------------------------------------------------------------ ;;; Selector ;;; ------------------------------------------------------------ - .org $1000 + install_src := * + + pushorg $1000 .proc selector prefix := $280 ; length-prefixed @@ -259,7 +235,7 @@ fail: lda prefix_depth ; root? ;; Store entry_length (byte), entries_per_block (byte), file_count (word) ldx #3 -: lda read_buffer + DirectoryHeader::entry_length,x +: lda read_buffer + SubdirectoryHeader::entry_length,x sta entry_length,x dex bpl :- @@ -290,9 +266,9 @@ while_loop: ;; Check file type ldy #FileEntry::file_type lda (entry_pointer),y - cmp #FileType::Directory + cmp #FT_DIRECTORY beq store_entry - cmp #FileType::System + cmp #FT_SYSTEM bne done_active_entry store_entry: @@ -693,7 +669,8 @@ cout: jmp COUT text_resources := * .proc help_string - HIASCIIZ "RETURN: Select | TAB: Chg Vol | ESC: Back" + scrcode "RETURN: Select | TAB: Chg Vol | ESC: Back" + .byte 0 .endproc ;; Mousetext sequence: Enable, folder left, folder right, disable @@ -704,43 +681,26 @@ cout: jmp COUT ;;; ------------------------------------------------------------ -.proc open_params -params: .byte 3 -path: .addr prefix -buffer: .addr $1C00 -ref_num:.byte 0 -.endproc + DEFINE_OPEN_PARAMS open_params, prefix, $1C00 open_params_ref_num := open_params::ref_num -.proc close_params -params: .byte 1 -ref_num:.byte 0 -.endproc + DEFINE_CLOSE_PARAMS close_params -.proc on_line_params -params: .byte 2 -unit: .byte $60 -buffer: .addr prefix+1 -.endproc - on_line_params_unit := on_line_params::unit + DEFINE_ON_LINE_PARAMS on_line_params, $60, prefix+1 + on_line_params_unit := on_line_params::unit_num -.proc set_prefix_params -params: .byte 1 -path: .addr prefix -.endproc + DEFINE_SET_PREFIX_PARAMS set_prefix_params, prefix -.proc read_params -params: .byte 4 -ref_num:.byte 1 -buffer: .word read_buffer -request:.word 0 ; This can be beyond $12FF - MARKER -trans: .word 0 -.endproc + DEFINE_READ_PARAMS read_params, read_buffer, 0 read_params_ref_num := read_params::ref_num - read_params_request := read_params::request + read_params_request := read_params::request_count ;;; ------------------------------------------------------------ - .assert read_params::request - selector <= install_size, error, "Must fit in $300 bytes" - .endproc + .assert .sizeof(selector) <= install_size, error, "Must fit in $300 bytes" + poporg + +;;; ************************************************************ + .include "driver_postamble.inc" +;;; ************************************************************ diff --git a/driver_postamble.inc b/driver_postamble.inc new file mode 100644 index 0000000..95e7ebd --- /dev/null +++ b/driver_postamble.inc @@ -0,0 +1,3 @@ + + poporg + reloc_end := * diff --git a/driver_preamble.inc b/driver_preamble.inc new file mode 100644 index 0000000..88ff901 --- /dev/null +++ b/driver_preamble.inc @@ -0,0 +1,458 @@ +;;; ------------------------------------------------------------ + + ;; SYS files load at $2000; relocates self to $1000 + .org SYS_ADDR + dst_addr := $1000 + +;;; ------------------------------------------------------------ + + jmp relocate + + .byte MM, DD, YY ; version date stamp + +;;; ------------------------------------------------------------ +;;; Relocate this code from $2000 (.SYSTEM start location) to $1000 +;;; and start executing there. This is done so that the next .SYSTEM +;;; file can be loaded/run at $2000. + +.proc relocate + src := reloc_start + dst := dst_addr + + ldx #(reloc_end - reloc_start + $FF) / $100 ; pages + ldy #0 +load: lda src,y ; self-modified + load_hi := *-1 + sta dst,y ; self-modified + store_hi := *-1 + iny + bne load + inc load_hi + inc store_hi + dex + bne load + + jmp main +.endproc + +;;; ============================================================ +;;; Start of relocated code + + reloc_start := * + pushorg dst_addr + +;;; ============================================================ +;;; Main routine +;;; ============================================================ + +.proc main + jsr save_chain_info + jsr init_system + jsr maybe_install_driver + jmp launch_next +.endproc + +;;; ============================================================ +;;; Preserve state needed to chain to next file +;;; ============================================================ + +.proc save_chain_info + ;; -------------------------------------------------- + ;; Save most recent device for later, when chaining + ;; to next .SYSTEM file. + lda DEVNUM + sta devnum + + ;; -------------------------------------------------- + ;; Identify the name of this SYS file, which should be present at + ;; $280 with or without a path prefix. Search pathname buffer + ;; backwards for '/', then copy name into |self_name|. + + ;; Find '/' (which may not be present, prefix is optional) + ldx PATHNAME + beq no_name + ldy #0 ; Y = length +: lda PATHNAME,x + and #$7f ; ignore high bit + cmp #'/' + beq copy_name + iny + dex + bne :- + + ;; Copy name into |self_name| buffer +copy_name: + cpy #0 + beq no_name + sty self_name + + ldx PATHNAME +: lda PATHNAME,x + sta self_name,y + dex + dey + bne :- + + ;; Done + rts + +no_name: + lda #0 + sta self_name + rts +.endproc + +devnum: .byte 0 +self_name: .res 16 + +;;; ============================================================ +;;; Init system state +;;; ============================================================ + +;;; Before installing, get the system to a known state. + +.proc init_system + cld + bit ROMIN2 + + ;; Update reset vector - ProDOS QUIT + lda #quit + sta $03F3 + eor #$A5 + sta $03F4 + + ;; Quit 80-column firmware + lda #$95 ; Ctrl+U (quit 80 col firmware) + jsr COUT + + ;; Reset I/O + sta CLR80VID + sta CLRALTCHAR + jsr SETVID + jsr SETKBD + jsr SETNORM + jsr INIT + jsr HOME + + ;; Update System Bit Map + ldx #BITMAP_SIZE-1 + lda #%00000001 ; protect page $BF +: sta BITMAP,x + lda #%00000000 ; nothing else protected until... + dex + bne :- + lda #%11001111 ; ZP ($00), stack ($01), text page 1 ($04-$07) + sta BITMAP + + ;; Determine lowercase support + lda MACHID + and #$88 ; IIe or IIc (or IIgs) ? + bne :+ + lda #$DF + sta lowercase_mask ; lower case to upper case + +: rts +.endproc + +;;; ============================================================ +;;; Find and invoke the next .SYSTEM file +;;; ============================================================ + +online_buf := $1C00 +io_buf := $1C00 +dir_buf := $2000 +block_len = $200 + + DEFINE_ON_LINE_PARAMS on_line_params,,online_buf + DEFINE_OPEN_PARAMS open_params, PATHNAME, io_buf + DEFINE_READ_PARAMS read_params, SYS_ADDR, SYS_LEN + DEFINE_READ_PARAMS read_block_params, dir_buf, block_len + DEFINE_CLOSE_PARAMS close_params + + +.proc launch_next + ;; Read directory and look for .SYSTEM files; find this + ;; one, and invoke the following one. + + ptr := $A5 + num := $A7 + len := $A8 + + ;; -------------------------------------------------- + ;; Own name found? If not, just quit + lda self_name + bne :+ + jmp quit + + ;; -------------------------------------------------- + ;; Find name of boot device, copy into PATHNAME +: lda devnum + sta on_line_params::unit_num + MLI_CALL ON_LINE, on_line_params + bcc :+ + jmp on_error + +: lda #'/' ; Prefix by '/' + sta PATHNAME+1 + lda online_buf + and #$0F ; Mask off length + sta PATHNAME + ldx #0 ; Copy name +: lda online_buf+1,x + sta PATHNAME+2,x + inx + cpx PATHNAME + bne :- + inx ; One more for '/' prefix + stx PATHNAME + + ;; Open directory + MLI_CALL OPEN, open_params + bcc :+ + jmp on_error +: lda open_params::ref_num + sta read_block_params::ref_num + sta close_params::ref_num + + ;; Read first "block" + MLI_CALL READ, read_block_params + bcc :+ + jmp on_error + + ;; Get sizes out of header +: lda dir_buf + VolumeDirectoryHeader::entry_length + sta entry_length_mod + lda dir_buf + VolumeDirectoryHeader::entries_per_block + sta entries_per_block_mod + lda #1 + sta num + + ;; Set up pointers to entry + lda #<(dir_buf + .sizeof(VolumeDirectoryHeader)) + sta ptr + lda #>(dir_buf + .sizeof(VolumeDirectoryHeader)) + sta ptr+1 + + ;; Process directory entry +entry: ldy #FileEntry::file_type ; file_type + lda (ptr),y + cmp #$FF ; type=SYS + bne next + ldy #FileEntry::storage_type_name_length + lda (ptr),y + and #$30 ; regular file (not directory, pascal) + beq next + lda (ptr),y + and #$0F ; name_length + sta len + tay + + ;; Compare suffix - is it .SYSTEM? + ldx suffix +: lda (ptr),y + cmp suffix,x + bne next + dey + dex + bne :- + + ;; Yes; is it *this* .SYSTEM file? + ldy self_name + cpy len + bne handle_sys_file +: lda (ptr),y + cmp self_name,y + bne handle_sys_file + dey + bne :- + sec + ror found_self_flag + + ;; Move to the next entry +next: lda ptr + clc + adc #$27 ; self-modified: entry_length + entry_length_mod := *-1 + sta ptr + bcc :+ + inc ptr+1 +: inc num + lda num + cmp #$0D ; self-modified: entries_per_block + entries_per_block_mod := *-1 + bcc entry + + ;; Read next "block" + MLI_CALL READ, read_block_params + bcs not_found + + ;; Set up pointers to entry + lda #0 + sta num + lda #<(dir_buf + $04) + sta ptr + lda #>(dir_buf + $04) + sta ptr+1 + jmp entry + + ;; -------------------------------------------------- + ;; Found a .SYSTEM file which is not this one; invoke + ;; it if follows this one. +handle_sys_file: + bit found_self_flag + bpl next + + MLI_CALL CLOSE, close_params + + ;; Compose the path to invoke. + ldx PATHNAME + inx + lda #'/' + sta PATHNAME,x + ldy #0 +: iny + inx + lda (ptr),y + sta PATHNAME,x + cpy len + bcc :- + stx PATHNAME + + jmp invoke_system_file + +not_found: + jsr zstrout + scrcode "\r\r* Unable to find next '.SYSTEM' file *\r" + .byte 0 + + bit KBDSTRB +: lda KBD + bpl :- + bit KBDSTRB + jmp quit +.endproc + +;;; ------------------------------------------------------------ +;;; Load/execute the system file in PATHNAME + +.proc invoke_system_file + MLI_CALL OPEN, open_params + bcs on_error + + lda open_params::ref_num + sta read_params::ref_num + sta close_params::ref_num + + MLI_CALL READ, read_params + bcs on_error + + MLI_CALL CLOSE, close_params + bcs on_error + + jmp SYS_ADDR ; Invoke loaded SYSTEM file +.endproc + +;;; ------------------------------------------------------------ +;;; Error handler - invoked if any ProDOS error occurs. + +.proc on_error + pha + jsr zstrout + scrcode "\r\r* Disk Error $" + .byte 0 + + pla + jsr PRBYTE + + jsr zstrout + scrcode " *\r" + .byte 0 + + bit KBDSTRB +: lda KBD + bpl :- + bit KBDSTRB + jmp quit +.endproc + +.proc quit + MLI_CALL QUIT, quit_params + brk ; crash if QUIT fails + + DEFINE_QUIT_PARAMS quit_params +.endproc + +;;; ============================================================ +;;; Data + +suffix: + PASCAL_STRING ".SYSTEM" + +found_self_flag: + .byte 0 + +;;; ============================================================ +;;; Common Routines +;;; ============================================================ + +;;; ------------------------------------------------------------ +;;; Output a high-ascii, null-terminated string. +;;; String immediately follows the JSR. + +.proc zstrout + ptr := $A5 + + pla ; read address from stack + sta ptr + pla + sta ptr+1 + bne skip ; always (since data not on ZP) + +next: cmp #HI('a') ; lower-case? + bcc :+ + and lowercase_mask ; make upper-case if needed +: jsr COUT +skip: inc ptr + bne :+ + inc ptr+1 +: ldy #0 + lda (ptr),y + bne next + + lda ptr+1 ; restore address to stack + pha + lda ptr + pha + rts +.endproc + +lowercase_mask: + .byte $FF ; Set to $DF on systems w/o lower-case + +;;; ------------------------------------------------------------ +;;; COUT a 2-digit number in A + +.proc cout_number + ldx #HI('0') + cmp #10 ; >= 10? + bcc tens + + ;; divide by 10, dividend(+'0') in x remainder in a +: sbc #10 + inx + cmp #10 + bcs :- + +tens: pha + cpx #HI('0') + beq units + txa + jsr COUT + +units: pla + ora #HI('0') + jsr COUT + rts +.endproc diff --git a/inc/apple2.inc b/inc/apple2.inc new file mode 100644 index 0000000..a66c24e --- /dev/null +++ b/inc/apple2.inc @@ -0,0 +1,54 @@ +;;; ============================================================ +;;; +;;; More Apple II Symbols +;;; +;;; ============================================================ + +;;; ============================================================ +;;; Soft Switches +;;; ============================================================ + +RAMRDOFF := $C002 +RAMRDON := $C003 +RAMWRTOFF := $C004 +RAMWRTON := $C005 +ALTZPOFF := $C008 +ALTZPON := $C009 + +CLR80VID := $C00C +SET80VID := $C00D +RDALTZP := $C016 +RD80STORE := $C018 +RDPAGE2 := $C01C + +BANKSEL := $C073 ; Select RamWorks bank + +ROMIN2 := $C082 ; Read ROM; no write +RWRAM1 := $C08B ; Read/write RAM bank 1 + +;;; ============================================================ +;;; I/O Registers (for Slot 2) +;;; ============================================================ + +TDREG := $C088 + $20 ; ACIA Transmit Register (write) +RDREG := $C088 + $20 ; ACIA Receive Register (read) +STATUS := $C089 + $20 ; ACIA Status/Reset Register +COMMAND := $C08A + $20 ; ACIA Command Register (read/write) +CONTROL := $C08B + $20 ; ACIA Control Register (read/write) + +;;; ============================================================ +;;; Monitor ROM routines +;;; ============================================================ + +INIT := $FB2F +HOME := $FC58 +GETLN := $FD6A ; with prompt character +GETLN2 := $FD6F ; no prompt character +CROUT := $FD8E +PRBYTE := $FDDA +COUT := $FDED +SETNORM := $FE84 +SETKBD := $FE89 +SETVID := $FE93 + +INPUT_BUFFER := $200 diff --git a/inc/macros.inc b/inc/macros.inc new file mode 100644 index 0000000..9610987 --- /dev/null +++ b/inc/macros.inc @@ -0,0 +1,123 @@ +;;; ============================================================ +;;; Generic Macros +;;; ============================================================ + +.define _is_immediate(arg) (.match (.mid (0, 1, {arg}), #)) +.define _is_register(arg) (.match ({arg}, x) .or .match ({arg}, y)) +.define _is_y_register(arg) (.match ({arg}, y)) +.define _immediate_value(arg) (.right (.tcount ({arg})-1, {arg})) + +.macro _op_lo op, arg + .if _is_immediate {arg} + op #<_immediate_value {arg} + .else + op arg + .endif +.endmacro + +.macro _op_hi op, arg + .if _is_immediate {arg} + op #>_immediate_value {arg} + .else + op arg+1 + .endif +.endmacro + +;;; ============================================================ +;;; Temporary org change, for relocated routines + +__pushorg_depth__ .set 0 + +.macro pushorg addr + ::__pushorg_depth__ .set ::__pushorg_depth__ + 1 + .ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) := * + .org addr + .ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__)) := * +.endmacro + +.macro poporg + .org .ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) + (* - .ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__))) + ::__pushorg_depth__ .set ::__pushorg_depth__ - 1 +.endmacro + +;;; ============================================================ +;;; Length-prefixed string +;;; +;;; Can include control chars by using: +;;; +;;; PASCAL_STRING {"abc",$0D,"def"} + +.macro PASCAL_STRING str,res + .local data + .local end + .byte end - data +data: .byte str +end: +.if .paramcount > 1 + .res res - (end - data), 0 +.endif +.endmacro + + +;;; ============================================================ +;;; Common patterns + +.macro copy arg1, arg2, arg3, arg4 + .if _is_register {arg2} && _is_register {arg4} + ;; indexed load/indexed store + lda arg1,arg2 + sta arg3,arg4 + .elseif _is_register {arg2} + ;; indexed load variant (arg2 is x or y) + lda arg1,arg2 + sta arg3 + .elseif _is_register {arg3} + ;; indexed store variant (arg3 is x or y) + lda arg1 + sta arg2,arg3 + .else + lda arg1 + sta arg2 + .endif +.endmacro + + + +;;; Copy 16-bit value +;;; copy16 #$1111, $2222 ; immediate, absolute +;;; copy16 $1111, $2222 ; absolute, absolute +;;; copy16 $1111,x, $2222 ; indirect load, absolute store +;;; copy16 $1111, $2222,x ; absolute load, indirect store +;;; copy16 $1111,x $2222,x ; indirect load, indirect store +;;; copy16 #$1111, $2222,x ; immediate load, indirect store +.macro copy16 arg1, arg2, arg3, arg4 + .if _is_register {arg2} && _is_register {arg4} + ;; indexed load/indexed store + lda arg1,arg2 + sta arg3,arg4 + lda arg1+1,arg2 + sta arg3+1,arg4 + .elseif _is_register {arg2} + ;; indexed load variant (arg2 is x or y) + lda arg1,arg2 + sta arg3 + lda arg1+1,arg2 + sta arg3+1 + .elseif _is_register {arg3} + ;; indexed store variant (arg3 is x or y) + _op_lo lda, {arg1} + sta arg2,arg3 + _op_hi lda, {arg1} + sta arg2+1,arg3 + .else + _op_lo lda, {arg1} + sta arg2 + _op_hi lda, {arg1} + sta arg2+1 + .endif +.endmacro + +;;; ============================================================ + +;;; Set the high bit on the passed byte +.define HI(c) ((c)|$80) diff --git a/inc/prodos.inc b/inc/prodos.inc new file mode 100644 index 0000000..637e44e --- /dev/null +++ b/inc/prodos.inc @@ -0,0 +1,477 @@ +;;; ============================================================ +;;; +;;; ProDOS MLI +;;; +;;; ============================================================ + +;;; Entry point / Global Page +MLI := $BF00 ; Entry point +DATETIME := $BF06 ; JMP to clock routine +DEVADR := $BF10 ; Device driver addresses ($BF10-$BF2F) +NODEV := $BF10 ; "No Device Connected" entry (slot 0) +DEVNUM := $BF30 ; Most recent accessed device +DEVCNT := $BF31 ; Number of on-line devices minus 1 +DEVLST := $BF32 ; Up to 14 units ($BF32-$BF3F) +BITMAP := $BF58 ; System memory bitmap +BITMAP_SIZE = $18 ; Bits for pages $00 to $BF +DATELO := $BF90 ; Date lo +DATEHI := $BF91 ; Date hi +TIMELO := $BF92 ; Time lo +TIMEHI := $BF93 ; Time hi +LEVEL := $BF94 ; File level +MACHID := $BF98 ; Machine ID +SLTBYT := $BF99 ; '1' bits indicate rom in slot (bit#) +IVERSION := $BFFD ; Interpreter Version +KVERSION := $BFFF ; ProDOS Kernel Version + +;;; Patch Locations +SELECTOR := $D100 + +BLOCK_SIZE = $200 + +PATHNAME := $280 +SYS_ADDR := $2000 ; Load address for SYS files +SYS_LEN = $BF00 - SYS_ADDR ; Maximum SYS file length + +;;; ============================================================ +;;; MLI Calls +;;; ============================================================ + +;;; Housekeeping Calls +CREATE = $C0 +DESTROY = $C1 +RENAME = $C2 +SET_FILE_INFO = $C3 +GET_FILE_INFO = $C4 +ON_LINE = $C5 +SET_PREFIX = $C6 +GET_PREFIX = $C7 + +;;; Filing Calls +OPEN = $C8 +NEWLINE = $C9 +READ = $CA +WRITE = $CB +CLOSE = $CC +FLUSH = $CD +SET_MARK = $CE +GET_MARK = $CF +SET_EOF = $D0 +GET_EOF = $D1 +SET_BUF = $D2 +GET_BUF = $D3 + +;;; System Calls +GET_TIME = $82 +ALLOC_INTERRUPT = $40 +DEALLOC_INTERRUPT = $41 +QUIT = $65 + +;;; Direct Disk Access Commands +READ_BLOCK = $80 +WRITE_BLOCK = $81 + +;;; ============================================================ +;;; File Types +;;; ============================================================ + +FT_TYPELESS = $00 +FT_BAD = $01 +FT_TEXT = $04 ; ASCII Text File * +FT_BINARY = $06 ; Generic Binary File * +FT_GRAPHICS = $08 ; Graphics File +FT_DIRECTORY = $0F ; Directory * +FT_ADB = $19 ; AppleWorks Database * +FT_AWP = $1A ; AppleWorks Word Processing * +FT_ASP = $1B ; AppleWorks Spreadsheet * +FT_SRC = $B0 ; IIgs system type; re-used? +FT_S16 = $B3 ; IIgs Application Program +FT_PAS = $EF ; Pascal Area * +FT_CMD = $F0 ; ProDOS Command File * +FT_INT = $FA ; Integer BASIC Program * +FT_IVR = $FB ; Integer BASIC Variable File * +FT_BASIC = $FC ; Applesoft BASIC Program * +FT_VAR = $FD ; Applesoft BASIC Variable File * +FT_REL = $FE ; EDASM/Contiki Relocatable File * +FT_SYSTEM = $FF ; ProDOS System File * + +;;; Types marked with * are known to BASIC.SYSTEM and have an +;;; associated three-letter abbreviation. + +;;; ============================================================ +;;; Access +;;; ============================================================ + +ACCESS_DEFAULT = %11000011 +ACCESS_LOCKED = %00100001 + +;;; ============================================================ +;;; Storage Types +;;; ============================================================ + +ST_STANDARD_FILE = $01 +ST_LINKED_DIRECTORY = $0D +ST_VOLUME_DIRECTORY = $0F + +;;; ============================================================ +;;; Errors +;;; ============================================================ + +ERR_DEVICE_NOT_CONNECTED = $28 +ERR_WRITE_PROTECTED = $2B +ERR_INVALID_PATHNAME = $40 +ERR_INVALID_REFERENCE = $43 +ERR_PATH_NOT_FOUND = $44 +ERR_VOL_NOT_FOUND = $45 +ERR_FILE_NOT_FOUND = $46 +ERR_DUPLICATE_FILENAME= $47 +ERR_OVERRUN_ERROR = $48 +ERR_VOLUME_DIR_FULL = $49 +ERR_END_OF_FILE = $4C +ERR_ACCESS_ERROR = $4E +ERR_DUPLICATE_VOLUME = $57 +ERR_NETWORK_ERROR = $88 + +;;; ============================================================ +;;; Directory Structures +;;; ============================================================ + +STORAGE_TYPE_MASK = $F0 +NAME_LENGTH_MASK = $0F + +;;; Volume Directory Header structure +.struct VolumeDirectoryHeader + prev_block .word + next_block .word + storage_type_name_length .byte + file_name .byte 15 + reserved .byte 8 + creation_date .word + creation_time .word + version .byte + min_version .byte + access .byte + entry_length .byte + entries_per_block .byte + file_count .word + ;; same through here --------- + bit_map_pointer .word + total_blocks .word +.endstruct + .assert .sizeof(VolumeDirectoryHeader) = $2B, error, "incorrect struct size" + +;;; Subdirectory Header structure +.struct SubdirectoryHeader + prev_block .word + next_block .word + storage_type_name_length .byte + file_name .byte 15 + reserved .byte 8 + creation_date .word + creation_time .word + version .byte + min_version .byte + access .byte + entry_length .byte + entries_per_block .byte + file_count .word + ;; same through here --------- + parent_pointer .word + parent_entry_number .byte + parent_entry_length .byte +.endstruct + .assert .sizeof(SubdirectoryHeader) = $2B, error, "incorrect struct size" + +;; File Entry structure +.struct FileEntry + storage_type_name_length .byte + file_name .byte 15 + file_type .byte + key_pointer .word + blocks_used .word + eof .faraddr + creation_date .word + creation_time .word + version .byte + min_version .byte + access .byte + aux_type .word + mod_date .word + mod_time .word + header_pointer .word +.endstruct + .assert .sizeof(FileEntry) = $27, error, "incorrect struct size" + +;;; ============================================================ +;;; ProDOS Driver Protocol +;;; ============================================================ + +;;; Addresses for command parameters +DRIVER_COMMAND := $42 +DRIVER_UNIT_NUMBER := $43 +DRIVER_BUFFER := $44 +DRIVER_BLOCK_NUMBER := $46 + +;;; Commands +DRIVER_COMMAND_STATUS = 0 +DRIVER_COMMAND_READ = 1 +DRIVER_COMMAND_WRITE = 2 +DRIVER_COMMAND_FORMAT = 3 + + +;;; ============================================================ +;;; Macros +;;; ============================================================ + +.macro MLI_CALL op, addr + jsr MLI + .byte op + .addr addr +.endmacro + +.macro DEFINE_OPEN_PARAMS name, pn, io, rn + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif + .if .xmatch(.string(io), "io_buffer") + .error "Can't pass 'io_buffer' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 3 +pathname: .addr pn +io_buffer: .addr io + .ifnblank rn +ref_num: .byte rn + .else +ref_num: .byte 0 + .endif +.endproc +.endmacro + +.macro DEFINE_READ_PARAMS name, db, rc +.proc name +param_count: .byte 4 +ref_num: .byte 0 +data_buffer: .addr db +request_count: .word rc +trans_count: .word 0 +.endproc +.endmacro + +.macro DEFINE_WRITE_PARAMS name, db, rc +.proc name +param_count: .byte 4 +ref_num: .byte 0 +data_buffer: .addr db +request_count: .word rc +trans_count: .word 0 +.endproc +.endmacro + +.macro DEFINE_CLOSE_PARAMS name +.proc name +param_count: .byte 1 +ref_num: .byte 0 +.endproc +.endmacro + +.macro DEFINE_FLUSH_PARAMS name +.proc name +param_count: .byte 1 +ref_num: .byte 0 +.endproc +.endmacro + +.macro DEFINE_GET_FILE_INFO_PARAMS name, pn + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte $A +pathname: .addr pn +access: .byte 0 +file_type: .byte 0 +aux_type: .word 0 +storage_type: .byte 0 +blocks_used: .word 0 +mod_date: .word 0 +mod_time: .word 0 +create_date: .word 0 +create_time: .word 0 +.endproc +.endmacro + +.macro DEFINE_SET_MARK_PARAMS name, pos +.proc name +param_count: .byte 2 +ref_num: .byte 0 +position: .faraddr pos +.endproc +.endmacro + +.macro DEFINE_ON_LINE_PARAMS name, un, db +.proc name +param_count: .byte 2 + + .ifnblank un +unit_num: .byte un + .else +unit_num: .byte 0 + .endif + +data_buffer: .addr db +.endproc +.endmacro + +.macro DEFINE_READ_BLOCK_PARAMS name, db, bn +.proc name +param_count: .byte 3 +unit_num: .byte 0 +data_buffer: .addr db +block_num: .word bn +.endproc +.endmacro + + +.macro DEFINE_WRITE_BLOCK_PARAMS name, db, bn +.proc name +param_count: .byte 3 +unit_num: .byte 0 +data_buffer: .addr db +block_num: .word bn +.endproc +.endmacro + +.macro DEFINE_ALLOC_INTERRUPT_PARAMS name, ic +.proc alloc_interrupt_params +param_count: .byte 2 +int_num: .byte 0 +int_code: .addr ic +.endproc +.endmacro + +.macro DEFINE_DEALLOC_INTERRUPT_PARAMS name +.proc dealloc_interrupt_params +param_count: .byte 1 +int_num: .byte 0 +.endproc +.endmacro + +.macro DEFINE_QUIT_PARAMS name, ext, pathname +.proc name +param_count: .byte 4 + .ifnblank ext + .byte ext + .else + .byte 0 + .endif + .ifnblank pathname + .word pathname + .else + .word 0 + .endif + .byte 0 + .word 0 +.endproc +.endmacro + +.macro DEFINE_SET_PREFIX_PARAMS name, pn + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 1 +pathname: .addr pn +.endproc +.endmacro + +.macro DEFINE_GET_PREFIX_PARAMS name, pn + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 1 +pathname: .addr pn +.endproc +.endmacro + +.macro DEFINE_DESTROY_PARAMS name, pn + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 1 +pathname: .addr pn +.endproc +.endmacro + +.macro DEFINE_CREATE_PARAMS name, pn, ac, ft, at, st + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 7 +pathname: .addr pn + + .ifnblank ac +access: .byte ac + .else +access: .byte 0 + .endif + + .ifnblank ft +file_type: .byte ft + .else +file_type: .byte 0 + .endif + + .ifnblank at +aux_type: .word at + .else +aux_type: .word 0 + .endif + + .ifnblank st +storage_type: .byte st + .else +storage_type: .byte 0 + .endif + +create_date: .word 0 +create_time: .word 0 +.endproc +.endmacro + +.macro DEFINE_SET_EOF_PARAMS name, eo +.proc name +param_count: .byte 2 +ref_num: .byte 0 +eof: .faraddr eo +.endproc +.endmacro + +.macro DEFINE_GET_EOF_PARAMS name +.proc name +param_count: .byte 2 +ref_num: .byte 0 +eof: .faraddr 0 +.endproc +.endmacro + +.macro DEFINE_RENAME_PARAMS name, pn, np + .if .xmatch(.string(pn), "pathname") + ;; If 'pathname' is passed then expansion yields a circular reference. + .error "Can't pass 'pathname' label to DEFINE_*_PARAMS" + .endif +.proc name +param_count: .byte 2 +pathname: .addr pn +new_pathname: .addr np +.endproc +.endmacro