; SETMAC.SYSTEM ; Sets the MAC address on an Uthernet-II, ; then launches next .SYSTEM file on drive (if any) ; ; Based on ... ; Fully disassembled and analyzed source to SMT ; NS.CLOCK.SYSTEM by M.G. - 04/20/2017 ; other notes: ; * uses direct block access to read volume directory, ; so won't launch from an AppleShare volume. ; Build instructions: ; ca65 setmac.s -l setmac.lst ; ld65 -t none -o setmac.system setmac.o ; put setmac.system as a SYS file on a ProDOS disk. .setcpu "6502" ; ---------------------------------------------------------------------------- ; Zero page POINTER := $A5 ; generic pointer used everywhere ENTNUM := $A7 ; current file entry # in block (zero-based) LENGTH := $A8 ; generic length byte used everywhere ; entry points PRODOS := $BF00 INIT := $FB2F HOME := $FC58 CROUT := $FD8E PRBYTE := $FDDA COUT := $FDED SETNORM := $FE84 SETKBD := $FE89 SETVID := $FE93 ; buffers & other spaces INBUF := $0200 ; input buffer PATHBUF := $0280 ; path buffer RELOCT := $1000 ; relocation target BLOCKBUF := $1800 ; block & I/O buffer SYSEXEC := $2000 ; location of SYS executables SOFTEV := $03F2 ; RESET vector ; global Page entries CLKENTRY := $BF06 ; clock routine entry point DEVNUM := $BF30 ; most recent accessed device MEMTABL := $BF58 ; system bitmap DATELO := $BF90 DATEHI := $BF91 TIMELO := $BF92 TIMEHI := $BF93 MACHID := $BF98 ; machine ID ; I/O and hardware ROMIn2 := $C082 ; access to read ROM/no write LC RAM LCBank1 := $C08B ; Access twice to write LC bank 1 KBD := $C000 ; keyboard INTCXROMOFF := $C006 ; Disable internal $C100-$CFFF ROM INTCXROMON := $C007 ; Enable interal $C100-$CFFF ROM KBDSTR := $C010 ; keyboard strobe INTCXROM := $C015 ; Read state of $C100-$CFFF soft switch CLR80VID := $C00C ; Turn off 80-column mode CLRALTCHAR := $C00E ; Turn off alt charset SLOT3ROM := $C300 ; SLOT 3 ROM C8OFF := $CFFF ; C8xx Slot ROM off IOMINUSONE := $BFFF ; For IOMINUSONE,y addressing ; Misc CLKCODEMAX := $7D ; Macro to define ASCII string with high bit set. .macro hasc Arg .repeat .strlen(Arg), I .byte .strat(Arg, I) | $80 .endrep .endmacro ; ---------------------------------------------------------------------------- ; relocate ourself from SYSEXEC to RELOCT ; note that we .org the whole thing, including this routine, at the target ; address and jump to it after the first page is relocated. .org RELOCT ; note code initially runs at SYSEXEC .proc Relocate sec bcs :+ ; skip version info .byte $04, $21, $91 ; version date in BCD : ldx #$05 ; page counter, do $0500 bytes ldy #$00 ; byte counter from: lda SYSEXEC,y ; start location to: sta RELOCT,y ; end location iny ; next byte offset bne from ; if not zero, copy byte inc from+2 ; otherwise increment source address high byte inc to+2 ; and destination address high byte dex ; dec page counter beq Main ; when done start main code jmp from ; live jump... into relocated code after first page loop .endproc ; ---------------------------------------------------------------------------- .proc Main ; figure out length of our name and stick in LENGTH lda #$00 sta LENGTH ; zero length ldx PATHBUF ; length pf path beq Main1 ; skip if length = 0 (no path) : inc LENGTH ; length += 1 for each non-/ dex ; previous char in path beq CopyNm ; nothing left? Copy our name. lda PATHBUF,x ; get character ; check for /... kinda wtf, as cmp #$2f would work... eor #$2F ; roundabout check for '/' asl a ; upper/lower case bne :- ; keep examining if not '/' ; now save our name (assuming we weren't lied to) CopyNm: ldy #$00 ; init destination offset : iny ; increment destination offset inx ; inc source offset lda PATHBUF,x ; get source char sta MyName,y ; write to save location cpy LENGTH ; got it all? bcc :- ; nope, copy more sty MyName ; save length ; done moving stuff Main1: cld bit ROMIn2 ; make sure ROM enabled ; letter of the law with RESET vector lda #Main1 sta SOFTEV+1 eor #$A5 sta SOFTEV+2 lda #$95 ; control code jsr COUT ; to quit 80-column firmware ldx #$FF ; reset txs ; stack pointer ; get video & keyboard I/O to known state sta CLR80VID sta CLRALTCHAR jsr SETVID jsr SETKBD jsr SETNORM jsr INIT ; initialize memory map ldx #$17 ; there are $18 bytes total lda #$01 ; last byte gets $01 (protect global page) : sta MEMTABL,x lda #$00 ; all but first byte get $00 (no protection) dex bne :- lda #$CF ; first byte protect ZP, stack, text page 1 sta MEMTABL lda MACHID and #$88 ; mask in bits indicating machine with lower case bne :+ ; has lower case, skip over next few instructions lda #$DF ; mask value sta CaseCv ; to make print routine convert to upper case : jsr HOME jsr iprint .byte $8D ; CR hasc "Uthernet-II SETMAC Utility" .byte $8D,$00 ; CR, done jsr loadsettings lda UWSlot ; Load UW slot number jsr setmac lda Default ; See if we used the defaults cmp #$ff bne nxtsys jsr iprint .byte $8D,$8D ; CR, CR hasc "" .byte $8D,$00 ; CR, done ; wait for keyboard bit KBDSTR : lda KBD bpl :- bit KBDSTR nxtsys: jmp NextSys .endproc .proc loadsettings ; Load slot number and MAC from 'SETMAC.CFG' ldx #$00 : lda CfgName,x sta PATHBUF,x inx cpx #$0B ; copied all the chars? bcc :- ; nope jsr PRODOS .byte $C8 ; OPEN .word PL_OPEN bcs CantOpen lda PL_OPEN+$05 ; copy ref number sta PL_RDCFG+$01 ; into READ parameter list jsr PRODOS .byte $CA ; READ .word PL_RDCFG ; 22 bytes -> CfgBuf bcs CantRead lda CfgBuf ; Slot number char sec sbc #'0' ; TODO NO VALIDATION! sta UWSlot jsr parsemac jsr iprint .byte $8D ; CR hasc "Settings from SETMAC.CFG" .byte $8D,$00 ; CR, done jsr PRODOS .byte $CC ; CLOSE .word PL_CLOSE lda #$00 ; Record that we opened file sta Default rts CantRead: jsr PRODOS .byte $CC ; CLOSE .word PL_CLOSE CantOpen: jsr iprint .byte $8D ; CR hasc "Can't Read SETMAC.CFG - Using Defaults" .byte $8D,$00 ; CR, done lda #$05 ; Default slot 5 sta UWSlot rts .endproc .proc parsemac ; Parse the ASCII MAC in CfgBuf 's xx:xx:xx:xx:xx:xx', ; convert it to six bytes and put it in MACBuf ldx #$02 ; Source index ldy #$00 ; Dest index : lda CfgBuf,x jsr decodeHex asl asl asl asl sta temp inx lda CfgBuf,x jsr decodeHex clc adc temp sta MACBuf,y iny inx ; Extra to skip ':' inx cpy #$06 ; Done all bytes? bne :- rts temp: .byte $00 .endproc .proc decodeHex ; Decode a single hex character in A ; Hex char may be '0'-'9', 'a'-'f', 'A'-'F' cmp #('f'+1) bcc :+ jmp bad ; char > 'f' : cmp #'a' ; char <= 'f' bcc :+ sec ; 'a' <= char <= 'f' sbc #('a'-10) rts : cmp #('F'+1) bcc :+ jmp bad ; 'F' < char < 'a' : cmp #'A' bcc :+ sec ; 'A' <= char <= 'F' sbc #('A'-10) rts : cmp #('9'+1) bcc :+ jmp bad ; '9' < char < 'A' : cmp #'0' bcc bad sec ; '0' <= char <= '9' sbc #'0' rts bad: lda #$00 rts .endproc .proc setmac ; Set the MAC address on the Uthernet-II ; Expects slot number in A pha jsr iprint .byte $8D ; CR hasc "Slot " .byte $00 pla pha jsr PrDec jsr iprint .byte $8D,$00 ; CR, done pla asl asl asl asl clc adc #$85 tay ; Mode register offset lda #$80 ; Reset W5100 sta IOMINUSONE,y ; Store in MODE register lda #$03 ; Address autoinc, indirect sta IOMINUSONE,y ; Store in MODE register iny ; $d6 lda #$00 ; High byte of MAC reg addr sta IOMINUSONE,y ; Set high byte of pointer iny ; $d7 lda #$09 ; Low byte sta IOMINUSONE,y ; Set low byte ldx #$00 iny ; $d8 l1: lda MACBuf,x ; Load byte of MAC jsr PrHex cpx #5 beq ncolon lda #(':'|$80) jsr COUT ncolon: lda MACBuf,x ; Load byte of MAC again sta IOMINUSONE,y ; Set and autoinc inx cpx #6 bne l1 dey dey ; $d6 lda #$00 ; High byte of $001a reg addr sta IOMINUSONE,y ; Set high byte of pointer iny ; $d7 lda #$1a ; Low byte sta IOMINUSONE,y ; Set low byte lda #$06 ; Magic value: MAC set! iny ; $d8 sta IOMINUSONE,y ; Set and autoinc rts .endproc ; This starts the process of finding & launching next system file ; unfortunately it also uses block reads and can't be run from an AppleShare ; volume. .proc NextSys ; set reset vector to DoQUit lda #DoQuit sta SOFTEV+1 eor #$A5 sta SOFTEV+2 lda DEVNUM ; last unit number accessed sta PL_READ_BLOCK+1 ; put in parameter list jsr GetBlock ; get first volume directory block lda BLOCKBUF+$23 ; get entry_length sta SMENTL ; modify code lda BLOCKBUF+$24 ; get entries_per_block sta SMEPB ; modify code lda #$01 ; sta ENTNUM ; init current entry number as second (from 0) lda #<(BLOCKBUF+$2B) ; set pointer to that entry sta POINTER ; making assumptions as we go lda #>(BLOCKBUF+$2B) sta POINTER+1 ; loop to examine file entries... FEntLp: ldy #$10 ; offset of file_type lda (POINTER),y cmp #$FF ; SYS? bne NxtEnt ; nope.. ldy #$00 ; offset of storage_type & name_length lda (POINTER),y and #$30 ; mask interesting bits of storage_type beq NxtEnt ; if not 1-3 (standard file organizations) lda (POINTER),y ; get storage_type and name_length again and #$0F ; mask in name_length sta LENGTH ; save for later tay ; and use as index for comparison ; comparison loop ldx #$06 ; counter for size of ".SYSTEM" : lda (POINTER),y ; get file name byte cmp system,x ; compare to ".SYSTEM" bne NxtEnt ; no match dey dex bpl :- ; if we got here, have ".SYSTEM" file ldy MyName ; length of our own file name cpy LENGTH ; matches? bne CkExec ; nope, see if we should exec ; loop to check if this is our own name : lda (POINTER),y cmp MyName,y bne CkExec ; no match, see if we should exec dey bne :- ; if we got here, we have found our own self sec ror FdSelf ; flag it ; go to next entry NxtEnt: lda POINTER ; low byte of entry pointer clc ; ready for addition adc #$27 ; add entry length that is SMENTL = * - 1 ; self-modifed sta POINTER ; save it bcc :+ ; no need to do high byte if no carry inc POINTER+1 ; only increment if carry : inc ENTNUM ; next entry number lda ENTNUM ; and get it cmp #$0D ; check against number of entries that is SMEPB = * - 1 ; self-modified bcc FEntLp ; back to main search if not done with this block lda BLOCKBUF+$02 ; update PL_BLOCK_READ for next directory block sta PL_READ_BLOCK+4 lda BLOCKBUF+$03 sta PL_READ_BLOCK+5 ora PL_READ_BLOCK+4 ; see if pointer is $00 beq NoSys ; error if we hit the end and found nothing.. jsr GetBlock ; get next volume directory block lda #$00 sta ENTNUM ; update current entry number lda #<(BLOCKBUF+$04) ; and reset pointer sta POINTER lda #>(BLOCKBUF+$04) sta POINTER+1 jmp FEntLp ; go back to main loop CkExec: bit FdSelf ; did we find our own name yet? bpl NxtEnt ; nope... go to next entry ldx PATHBUF ; get length of path in path buffer beq CpyNam ; skip looking for / if zero : dex ; beq CpyNam ; done if zero lda PATHBUF,x ; eor #$2F ; is '/'? asl a ; in roundabout way bne :- ; no slash ; copy file name onto path, x already has position CpyNam: ldy #$00 : iny ; next source byte offset inx ; next dest byte offset lda (POINTER),y ; get filename char sta PATHBUF,x ; put in path cpy LENGTH ; copied all the chars? bcc :- ; nope stx PATHBUF ; update length of path jmp LaunchSys ; try to launch it! NoSys: jsr iprint .byte $8D, $8D, $8D hasc "* Unable to find next '.SYSTEM' file *" .byte $8D, $00 ; wait for keyboard then quit to ProDOS bit KBDSTR : lda KBD bpl :- bit KBDSTR jmp DoQuit .endproc ; ---------------------------------------------------------------------------- ; inline print routine ; print chars after JSR until $00 encountered ; converts case via CaseCv ($FF = no conversion, $DF = to upper) .proc iprint pla sta POINTER pla sta POINTER+1 bne next : cmp #$E1 bcc noconv and CaseCv noconv: jsr COUT next: inc POINTER bne nohi inc POINTER+1 nohi: ldy #$00 lda (POINTER),y bne :- lda POINTER+1 pha lda POINTER pha rts .endproc ; ---------------------------------------------------------------------------- ; print one or two decimal digits .proc PrDec ldx #$B0 ; tens digit cmp #$0A bcc onedig : sbc #$0A ; repeated subtraction, carry is already set inx cmp #$0A ; less than 10 yet? bcs :- ; nope onedig: pha cpx #$B0 beq nozero ; skip printing leading zero txa jsr COUT nozero: pla ora #$B0 jsr COUT rts .endproc CaseCv: .byte $FF ; default case conversion byte = none ; ---------------------------------------------------------------------------- ; print hex digits in A (already validated) .proc PrHexDigit cmp #$0a bcc :+ ; A < $A clc adc #('a' | $80) - $0a jsr COUT rts : ;; clc adc #('0' | $80) jsr COUT rts .endproc ; ---------------------------------------------------------------------------- ; print two hex digits in A .proc PrHex pha lsr lsr lsr lsr and #$0f jsr PrHexDigit pla and #$0f jsr PrHexDigit rts .endproc ; ---------------------------------------------------------------------------- .proc DoQuit jsr PRODOS .byte $65 ; MLI QUIT .word PL_QUIT brk ; crash into monitor if QUIT fails rts ; (!) if that doesn't work, go back to caller PL_QUIT: .byte $04 ; param count .byte $00 ; quit type - $00 is only type .word $0000 ; reserved .byte $00 ; reserved .word $0000 ; reserved .endproc .proc GetBlock jsr PRODOS .byte $80 ; READ_BLOCK .word PL_READ_BLOCK bcs LaunchFail rts .endproc PL_READ_BLOCK: .byte $03 .byte $60 ; unit number .word BLOCKBUF .word $0002 ; first volume directory block ; ---------------------------------------------------------------------------- ; launch next .SYSTEM file .proc LaunchSys jsr PRODOS .byte $C8 ; OPEN .word PL_OPEN bcs LaunchFail lda PL_OPEN+$05 ; copy ref number sta PL_READ+$01 ; into READ parameter list jsr PRODOS .byte $CA ; READ .word PL_READ bcs LaunchFail ; bug the first: Close should be done every time the OPEN call is successful ; but only done when the READ succeeds ; bug the second: Others may not consider this a bug, but we close all open ; files, even if we didn't open them. That's not very polite. jsr PRODOS .byte $CC ; CLOSE .word PL_CLOSE bcs LaunchFail jmp SYSEXEC .endproc ; ---------------------------------------------------------------------------- ; failed to launch next .SYSTEM file .proc LaunchFail pha jsr iprint .byte $8D, $8D, $8D hasc "** Disk Error $" .byte $00 pla jsr PRBYTE jsr iprint hasc " **" .byte $8D, $00 ; wait for keyboard, then quit to ProDOS bit KBDSTR : lda KBD bpl :- bit KBDSTR jmp DoQuit .endproc ; ---------------------------------------------------------------------------- PL_OPEN: .byte $03 .word PATHBUF .word BLOCKBUF .byte $01 ; ref num (default 1 wtf?) PL_READ: .byte $04 .byte $01 ; ref num .word SYSEXEC ; data buffer .word $FFFF ; request count .word $0000 ; transfer count PL_RDCFG: .byte $04 .byte $01 ; ref num .word CfgBuf ; data buffer .word $0016 ; request count .word $0000 ; transfer count PL_CLOSE: .byte $01 .byte $00 ; ref num $00 = all files ; ---------------------------------------------------------------------------- FdSelf: .byte $00 ; bit 7 set if we found our name in volume dir system: .byte ".SYSTEM" MyName: .byte $0D,"SETMAC.SYSTEM" CfgName: .byte $0A,"SETMAC.CFG" CfgBuf: .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; slt spc m1h m1l : m2h m2l : m3h m3l : m4h m4l : m5h m5l : m6h m6l UWSlot: .byte $00 MACBuf: .byte $00,$08,$dc,$10,$20,$30 ; Fallback value Default: .byte $ff ; Set to $00 if we load SETMAC.CFG