diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce49713 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Build directory +out + +# Directory mounted in Virtual ][ as a ProDOS volume +mount + +# OS-specific files +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5650818 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +sudo: enabled +os: osx +language: c + +install: +- git clone https://github.com/cc65/cc65 /tmp/cc65 && + sudo make -C /tmp/cc65 ca65 ld65 avail && + ca65 --version + +script: + - make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..06dfd81 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ + +CAFLAGS = --target apple2enh --list-bytes 0 +LDFLAGS = --config apple2-asm.cfg + +OUTDIR = out + +HEADERS = $(wildcard *.inc) $(wildcard inc/*.inc) + +TARGETS = $(OUTDIR)/ram.drv.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: $(OUTDIR) $(TARGETS) + +$(OUTDIR): + mkdir -p $(OUTDIR) + +clean: + rm -f $(OUTDIR)/*.o + rm -f $(OUTDIR)/*.list + rm -f $(TARGETS) + +$(OUTDIR)/%.o: %.s $(HEADERS) + ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $< + +# System Files .SYS +$(OUTDIR)/%.SYS: $(OUTDIR)/%.o + ld65 $(LDFLAGS) -o '$@' $< + xattr -wx prodos.AuxType '00 20' $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..196de3d --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Disassembly of Glen E. Bredon's `RAM.DRV.SYSTEM` for Apple II ProDOS + + +[![Build Status](https://travis-ci.org/a2stuff/ram.drv.system.svg?branch=master)](https://travis-ci.org/a2stuff/ram.drv.system) + + +This was started before realizing what the origin of the `RAM.SYSTEM` +found on a MouseDesk 2.0 disk image file was. + +There is a more complete diassembly with commentary at: + +http://boutillon.free.fr/Underground/Outils/Ram_Drv_System/Ram_Drv_System.html + +## Project Details + +* The `orig` branch compiles to to match the original. +* The `master` branch has additions, including: + * Chains to next `.SYSTEM` file in dir order (not hard coded) + * Chains to next `.SYSTEM` file on non-block devices (e.g. file shares) 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 diff --git a/orig/RAM.SYSTEM.SYS b/orig/RAM.SYSTEM.SYS new file mode 100644 index 0000000..7e86261 Binary files /dev/null and b/orig/RAM.SYSTEM.SYS differ diff --git a/ram.drv.system.s b/ram.drv.system.s new file mode 100644 index 0000000..a5841ed --- /dev/null +++ b/ram.drv.system.s @@ -0,0 +1,704 @@ +;;; Disassembly of "RAM.SYSTEM" found on Mouse Desk 2.0 images +;;; Based on Glen E. Bredon's "RAM.DRV.SYSTEM" +;;; Some details c/o http://boutillon.free.fr/Underground/Outils/Ram_Drv_System/Ram_Drv_System.html +;;; +;;; Modifications: +;;; * Chain to next .SYSTEM file dynamically + + .setcpu "6502" + .linecont + + .feature string_escapes + + .include "apple2.inc" + .include "apple2.mac" + .include "opcodes.inc" + + .include "inc/apple2.inc" + .include "inc/macros.inc" + .include "inc/prodos.inc" + +;;; ************************************************************ + .include "driver_preamble.inc" +;;; ************************************************************ + +;;; ============================================================ +;;; +;;; Driver Installer +;;; +;;; ============================================================ + +;;; ============================================================ +;;; Configuration Parameters + + .define PRODUCT "RAMWorks RAM Disk" + +zp_sig_addr := $06 + +zpproc_addr := $B0 +zpproc_relay_addr := $2D0 + +data_buf := $1C00 ; I/O when chaining to next SYS file +driver_target := $FF00 ; Install location in ProDOS + + +kMaxUsableBanks = 24 ; Why is this capped so low??? + ; (driver has room for another ~20?) + +banks_to_reserve: .byte 0 ; banks to reserve (e.g. for AppleWorks) +unitnum: .byte $03 ; S3D1; could be $B for S3D2 + +;;; ============================================================ +;;; Install the driver + +.proc maybe_install_driver + + sta CLR80COL + ldy #0 + sty BANKSEL + sta ALTZPON ; Use ZP to probe banks + + ;; Clear map1 / map2 (256 bytes) to $FF + lda #$FF +: sta map1,y + iny + bne :- + + ;; Stash first two bytes of each bank (128 possible banks) +: sty BANKSEL + lda $00 + sta stash_00,y + lda $01 + sta stash_01,y + iny + bpl :- + dey + + ;; Write bank num/complement at $0/$1 +: sty BANKSEL + sty $00 + tya + eor #$FF + sta $01 + dey + bne :- + + ;; Y = 0 + + ;; Reset signature bytes on main/aux banks + sty BANKSEL + sty $00 + sty $01 + sta ALTZPOFF + sty $00 + sty $01 + sta ALTZPON + + lda banks_to_reserve + sta reserved_banks + +;;; ============================================================ + + ;; Copy into every bank + ldy #1 +bank_loop: + ;; Check bank for signature bytes (bank num/complement at $0/$1) + sty BANKSEL + cpy $00 + bne next_bank + tya + eor #$FF + eor $01 + bne next_bank + cpy $00 ; Bank 0 (aux) is reserved for 128k apps + bne next_bank + + ;; Flag as available in map2 + ;; (map2,N = N if available, $FF otherwise) + tya + sta map2,y + + ;; Skip over reserved banks, then start storing them in the map + ldx reserved_banks + bne :+ + sta first_used_bank +: dec reserved_banks + bpl next_bank + sta map1,y + ;; (map1,N = N if available, $FF otherwise - also???) + + ;; Copy helper proc into bank's ZP + ldx #sizeof_zpproc +: lda zpproc-1,x + sta zpproc_addr-1,x + dex + bne :- + +next_bank: + iny + bpl bank_loop + +;;; ============================================================ + + ;; Y = $80 + + ;; Restore stashed $0/$1 bytes of back + ;; (except first, in first_used_bank ???) +loop0: lda map2-1,y + bmi :+ + cmp first_used_bank + beq :+ + sta BANKSEL + lda stash_00-1,y + sta $00 + lda stash_01-1,y + sta $01 +: dey + bne loop0 + + ;; Y = 0 + sty BANKSEL + sty $00 + + ;; Count number of available banks, and populate + ;; driver_bank_list with list of banks. + ldx #$FF +loop1: inx + cpx #kMaxUsableBanks + bcs break +loop2: iny + bmi break + lda map1,y + bmi loop2 + sta driver_bank_list,x + bpl loop1 +break: + ;; Patch driver with block-specific data + ;; X = number of available banks + + ;; Compute number of blocks + txa + lsr a + sta vol_dir_header+VolumeDirectoryHeader::total_blocks+1 + ror vol_dir_header+VolumeDirectoryHeader::total_blocks + + stx driver_block_x ; num banks + dex ; -1 + stx num_banks_minus_one + + bmi fail ; 0 banks? give up. + + lda vol_dir_header+VolumeDirectoryHeader::total_blocks + sec + sbc driver_block_x + and #$F8 + sta vol_dir_header+VolumeDirectoryHeader::total_blocks + sta driver_blocks_lo + bcs :+ + dec vol_dir_header+VolumeDirectoryHeader::total_blocks+1 +: lda vol_dir_header+VolumeDirectoryHeader::total_blocks+1 + sta driver_blocks_hi + + lda driver_bank_list + sta BANKSEL + lda $00 + beq fail + + ;; Check for ZP signature - if not found, set it and install. + ldx #sig_len-1 +: lda sig,x + cmp zp_sig_addr,x + bne set_sig + dex + bpl :- + + bit BUTN1 ; escape hatch in case of loop ??? + bmi L21F0 + jmp do_install + +fail: jmp install_failure + +sloop: lda sig,x +set_sig: + sta zp_sig_addr,x + dex + bpl sloop + +;;; ============================================================ + +;;; Prepare key blocks in + +L21F0: sta ALTZPOFF + + ;; Stamp current date/time into vol_dir_header + ldy #3 +: lda DATELO,y + sta vol_dir_header+VolumeDirectoryHeader::creation_date,y + dey + bpl :- + + ;; Fill pages $06-$0F with 00-FF + sta RAMWRTON + iny + tya +: sta $0600,y ; Block 2 - volume dir + sta $0700,y + sta $0800,y ; Block 3 - volume dir + sta $0900,y + sta $0A00,y ; Block 4 - volume dir + sta $0B00,y + sta $0C00,y ; Block 5 - volume dir + sta $0D00,y + sta $0E00,y ; Block 6 - volume bitmap + sta $0F00,y + iny + bne :- + + ;; Copy vol_dir_header into page $06 + ldy #.sizeof(VolumeDirectoryHeader)-1 +: lda vol_dir_header,y + sta $0600,y + dey + bpl :- + + ldy #$02 + sty $0800 + iny + sty $0A00 + iny + sty $0C00 + sty $0802 + iny + sty $0A02 + + ptr := $3C + lda vol_dir_header+VolumeDirectoryHeader::total_blocks + sta ptr + lda vol_dir_header+VolumeDirectoryHeader::total_blocks+1 + lsr a + ror ptr + lsr a + ror ptr + lsr a + ror ptr + clc + adc #$0E + sta ptr+1 + + ldy #0 + tya +: sta (ptr),y + lda ptr + sec + sbc #1 + sta ptr + lda #$FF + bcs :- + dec ptr+1 + ldx ptr+1 + cpx #$0E + bcs :- + lda #$01 + sta $0E00 + +;;; ============================================================ + +do_install: + lda #0 + sta RAMWRTOFF + sta ALTZPOFF + sta BANKSEL + bit LCBANK1 + bit LCBANK1 + + lda #OPC_CLD ; signature + cmp driver_target + beq copy_driver + sta ALTZPON ; Maybe in AUX? + cmp driver_target + beq copy_driver + cmp $DE00 ; ??? + beq copy_driver + sta ALTZPOFF + + ;; Copy driver into place +copy_driver: + ldy #0 +: lda driver_src,y + sta driver_target,y + iny + cpy #sizeof_driver + bcc :- + + ;; Check if unitnum already has a device + ldy DEVCNT +: lda DEVLST,y + lsr a + lsr a + lsr a + lsr a + cmp unitnum + beq install_device + dey + bpl :- + + ;; Shift devices up by one + inc DEVCNT + ldy DEVCNT +: lda DEVLST-1,y + sta DEVLST,y + dey + bne :- + + ;; Install device in ProDOS via DEVLST/DEVADR. + ;; (Y has index in DEVLST) +install_device: + lda unitnum + asl a + tax + asl a + asl a + asl a + sta on_line_params+1 ; unit_number + ora #$0E ; $3E - signature byte used by DeskTop + sta DEVLST,y + copy16 #(driver_target+1), DEVADR,x + + ;; Did we install into S3D2? + lda unitnum + cmp #$0B ; Slot 3 Drive 2 + beq finish + + ;; No, so uninstall S3D2 (regular /RAM) + ldy DEVCNT +: lda DEVLST,y + and #$F0 + cmp #$B0 ; Slot 3 drive 2 i.e. normal /RAM + beq found + dey + bpl :- + bmi finish ; always + + ;; Actually remove from DEVLST + slot3d2_devadr := DEVADR + $10 + 3*2 +found: ldx slot3d2_devadr + 1 + inx + bne finish +: copy DEVLST+1,y, DEVLST,y + iny + cpy DEVCNT + bcc :- + beq :- + dec DEVCNT + copy16 NODEV, slot3d2_devadr ; clear driver + +finish: bit ROMIN2 + MLI_CALL ON_LINE, on_line_params + ldx #$00 + lda on_line_params_buffer + ora L239F + bne install_success + bcc install_success + copy #$FF, L239F + sta ALTZPON + copy driver_bank_list, BANKSEL + stx $06 + stx BANKSEL + stx vol_dir_header+VolumeDirectoryHeader::total_blocks + jmp maybe_install_driver ; retry??? + +install_success: + sta ALTZPOFF + + jsr HOME + jsr zstrout + scrcode "\r\r\r", PRODUCT, " - Installed" + .byte 0 + + rts + +install_failure: + sta ALTZPOFF + + jsr HOME + jsr zstrout + scrcode "\r\r\r", PRODUCT, " - Not Installed" + .byte 0 + + rts + +;;; ============================================================ +;;; Installed on zero page of each bank at $B0 + +.proc zpproc + pushorg ::zpproc_addr + + sta $E0 ; dst1 hi + bcs :+ + sty $E0 ; dst1 hi + tay +: lda #$00 + sta RAMWRTON + bcc :+ + txa + ldx #$00 + sta RAMWRTOFF + sta RAMRDON + + ;; One block = two pages +: sty $DD ; src1 hi + iny + sty $E3 ; src2 hi + + sta $DF ; dst1 lo + sta $E5 ; dst2 lo + + stx $DC ; src1 lo + stx $E2 ; src2 lo + + ldy $E0 ; dst1 hi + iny + sty $E6 ; dst2 hi + + ldy #$00 +: lda $1000,y ; src1 + sta $1000,y ; dst1 + lda $1000,y ; src2 + sta $1000,y ; dst2 + iny + bne :- + + sta RAMWRTOFF + sta RAMRDOFF + clc + bit $02E4 + rts + + poporg +.endproc + sizeof_zpproc := .sizeof(zpproc) + +;;; ============================================================ + + on_line_params_buffer := $220 + DEFINE_ON_LINE_PARAMS on_line_params, $30, on_line_params_buffer + +num_banks_minus_one: + .byte 0 + +L239F: .byte 0 + +sig: scrcode "GEB" ; signature sequence - Glen E. Bredon + sig_len = * - sig + + ;; Volume Directory Header +.proc vol_dir_header + .word 0 ; preceding block number + .word $03 ; succeeding block number + .byte ST_VOLUME_DIRECTORY << 4 | 3 ; storage type / name length + .byte "RAM" ; name field is 15 bytes + .res 15-3 + .res 8, 0 ; reserved (8 bytes) + .word 0, 0 ; creation date/time + .byte 1 ; version (1 = ProDOS 2.0) + .byte 0 ; min_version + .byte ACCESS_DEFAULT ; access + .byte $27 ; entry_length + .byte $D ; entries_per_block + .word 0 ; file_count + .word 6 ; bit_map_pointer +blocks: .word 0 ; total_blocks +.endproc + .assert .sizeof(vol_dir_header) = .sizeof(VolumeDirectoryHeader), error, "Size mismatch" + +.endproc + +;;; ============================================================ +;;; Ram Disk Driver - installed at $FF00 +;;; ============================================================ + +.proc driver_src + pushorg ::driver_target + driver_start := * + +start: cld ; used as a signature + + lda DRIVER_COMMAND + bne not_status + driver_blocks_lo := *+1 + ldx #0 ; self-modified - blocks low + driver_blocks_hi := *+1 + ldy #0 ; self-modified - blocks high +LFF09: clc + bcc LFF83 ; always + +not_status: + cmp #DRIVER_COMMAND_FORMAT + beq LFF09 + + ;; COMMAND_READ or COMMAND_WRITE +LFF10: lda #$27 + bcs rts1 + + lda RD80STORE + pha + sta CLR80COL + + ;; Save $40/41 + lda $40 + pha + lda $41 + pha + + lda DRIVER_BUFFER + sta $40 + ldx DRIVER_BUFFER+1 + inx + stx $41 + + jsr install_zpproc_relay + + zpproc_relay_patch1_offset := $04 + stx zpproc_relay_addr + zpproc_relay_patch1_offset + lda RDALTZP + + zpproc_relay_patch2_offset := $14 + sta zpproc_relay_addr + zpproc_relay_patch2_offset + lda DRIVER_BLOCK_NUMBER+1 + pha + tax + lda DRIVER_BLOCK_NUMBER +LFF3C: sec +: iny + sbc #$7F + bcs :- + dex + bpl LFF3C + + tya + adc DRIVER_BLOCK_NUMBER + bcc :+ + inc DRIVER_BLOCK_NUMBER+1 +: asl a + tay + lda DRIVER_BLOCK_NUMBER+1 + rol a + tax + pla + sta DRIVER_BLOCK_NUMBER+1 + driver_block_x := *+1 + cpx #$0 ; self-modified - ??? + bcs LFF74 + + tya + sbc #191 + cmp #16 + bcs :+ + adc #208 + + tay + bit LCBANK2 +: lda DRIVER_COMMAND + lsr a ; carry set = READ, clear = WRITE + lda bank_list,x + ldx DRIVER_BUFFER + jsr zpproc_relay_addr + bit LCBANK1 + +LFF74: jsr install_zpproc_relay + + ;; Restore $40/41 + pla + sta $41 + pla + sta $40 + + pla + bpl LFF83 + sta SET80COL +LFF83: lda #$00 + bcs LFF10 + +rts1: rts + +install_zpproc_relay: + ldy #sizeof_zpproc_relay+1 +: ldx zpproc_relay-1,y + lda zpproc_relay_addr-1,y + sta zpproc_relay-1,y + txa + sta zpproc_relay_addr-1,y + dey + bne :- + + ldx DRIVER_BUFFER+1 + bpl done + bit DRIVER_BUFFER+1 + bvc done + +: ldx $8000,y + lda (DRIVER_BUFFER),y + sta $8000,y + txa + sta (DRIVER_BUFFER),y + ldx $8100,y + lda ($40),y + sta $8100,y + txa + sta ($40),y + iny + bne :- + + ldx #$80 +done: rts + +bank_list: + .res ::kMaxUsableBanks, 0 + +.proc zpproc_relay + sta BANKSEL + + patch_loc1 := *+1 + lda #$00 + sta ALTZPON + jsr zpproc_addr + sty BANKSEL + bmi :+ + sta ALTZPOFF +: rts + + patch_loc2 := * +.endproc + sizeof_zpproc_relay := .sizeof(zpproc_relay) + patch_loc1_offset := zpproc_relay::patch_loc1 - zpproc_relay + patch_loc2_offset := zpproc_relay::patch_loc2 - zpproc_relay + ;; These offsets can't be used directly due to ca65 addressing mode + ;; assumptions, so just verify they are correct. + .assert zpproc_relay_patch1_offset = patch_loc1_offset, error, "Offset mismatch" + .assert zpproc_relay_patch2_offset = patch_loc2_offset, error, "Offset mismatch" + + .byte 0 + + poporg +.endproc + sizeof_driver := .sizeof(driver_src) + + driver_blocks_lo := driver_src + driver_src::driver_blocks_lo - driver_src::driver_start + driver_blocks_hi := driver_src + driver_src::driver_blocks_hi - driver_src::driver_start + driver_block_x := driver_src + driver_src::driver_block_x - driver_src::driver_start + driver_bank_list := driver_src + driver_src::bank_list - driver_src::driver_start + +;;; ============================================================ +;;; Scratch space beyond code used during driver install + +reserved_banks := * +first_used_bank := *+1 +map1 := *+2 ; len: $80 +map2 := *+2+$80 ; len: $80 +stash_00 := *+2+$100 ; len: $80 +stash_01 := *+2+$180 ; len: $80 + + .assert stash_01+$80 < data_buf, error, "Too long" + +;;; ************************************************************ + .include "driver_postamble.inc" +;;; ************************************************************ diff --git a/res/go.sh b/res/go.sh new file mode 100755 index 0000000..0aaa01d --- /dev/null +++ b/res/go.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Run this from the ram.drv.system directory + +set -e +source "res/util.sh" + +#do_make clean +do_make all diff --git a/res/util.sh b/res/util.sh new file mode 100644 index 0000000..a2de088 --- /dev/null +++ b/res/util.sh @@ -0,0 +1,15 @@ +function cecho { + case $1 in + red) tput setaf 1 ; shift ;; + green) tput setaf 2 ; shift ;; + yellow) tput setaf 3 ; shift ;; + esac + echo -e "$@" + tput sgr0 +} + +function do_make { + make $MAKE_FLAGS "$1" \ + && (cecho green "make $1 good") \ + || (tput blink ; cecho red "MAKE $1 BAD" ; return 1) +}