; 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
        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    "<Press Any Key>"
        .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
        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

                 

