This commit is contained in:
mgcaret 2017-09-27 09:04:54 -07:00
commit 8742530a8d
6 changed files with 659 additions and 0 deletions

.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@

Makefile Executable file
View File

@ -0,0 +1,32 @@
ACMD=java -jar ~/bin/AppleCommander-1.3.5-ac.jar
.PHONY: all
all: NetBoot_LC.mac.bin ;
test.po: NetBoot_LC.bin
$(ACMD) -pro140 test.po NETBOOT.LC.TST
$(ACMD) -p test.po NETBOOT.LC BIN 0x0800 < NetBoot_LC.bin
emulate: boot.dsk test.po
open boot.dsk test.po -a 'Virtual ]['
NetBoot_LC.mac.bin: NetBoot_LC
rm -f NetBoot_LC.mac.bin
macbinary encode -t 2 -o NetBoot_LC.mac.bin NetBoot_LC
NetBoot_LC: NetBoot_LC.r
Rez -rd -o NetBoot_LC NetBoot_LC.r
NetBoot_LC.r: NetBoot_LC.bin
utils/ NetBoot_LC.bin > NetBoot_LC.r
NetBoot_LC.bin: NetBoot_LC.o
ld65 -t none -o NetBoot_LC.bin Netboot_LC.o
NetBoot_LC.o: NetBoot_LC.s
ca65 -o NetBoot_LC.o -l NetBoot_LC.lst NetBoot_LC.s
.PHONY: clean
rm -f NetBoot_LC *.bin *.r *.o *.lst test.po

NetBoot_LC.s Normal file
View File

@ -0,0 +1,523 @@
; Apple //e Workstation Card
; replacement for "hard-coded" boot blocks (in ca65 format).
; By Michael Guidero
; The Apple //e Card for Macintosh LC is quite capable. It
; emulates all of the functions of the Apple //e Workstation Card
; except for one: When you boot "over the network", instead of getting
; the boot blocks over the network, it loads them from the "IIe Startup"
; resource fork, from BBLK#5120. Because the boot blocks contain both
; ProDOS and the Logon program, as shipped by Apple the LC //e Card is
; "stuck" at ProDOS 1.9 and Logon 1.3 as shipped by Apple. Updating them
; requires using ResEdit to hack the IIe Startup program.
; The real Workstation Card finds a boot server on the network and loads
; the boot blocks over the network via ATP. Updating the boot blocks
; is as simple as replacing them on the boot server.
; While I figured out how to update the boot blocks in IIe Startup, having
; to hack it with ResEdit just to make it work the same as my regular //es
; with Workstation Cards every time a new ProDOS comes out was an annoying
; prospect.
; So as a solution, here's a replacement for BBLK#5120 that makes the LC
; //e Card work like a standard Apple //e with a Workstation Card.
; Other features:
; * Puts a letter in the lower left corner indicating what part of the
; boot process is happening, in case something goes wrong.
; * On-screen spinner, spins as each block is received.
; * Displays the current AppleTalk zone.
; * Displays the boot server network, node, and socket.
; Revisions since I gave out the Gist link:
; 07/10/2017 - Fix NBPBuf to be at $xx00 instead of $00xx
; - Display boot server address/socket & object name
; - convert output to use monitor routines rather than direct write
; for messages only, spinners and status still direct write
; - If in the boot scan loop, try next slot if we fail.
; 07/11/2017 - Convert all AT calls to macro
; - Adjust retry interval/tries in GetMyZone call.
; - Display zone if possible, when ca/option held.
; - Added missing init type flags byte to AT Init call. No harm no foul.
.macro ATcall PList
jsr GoCard
.byte $42
.addr PList
NBPBufSz = $0100 ; NBP buffer size
DispTmp = $02 ; temp var for display routines
ch = $24 ; cursor horizontal pos
CardPtr = $fe ; ZP loc of card pointer
IRQvect = $03fe ; ROM calls here on IRQ
BootStart = $0800 ; where we load boot blocks
NotifyLoc = $07d0 ; process notify screen loc
SpinLoc = $428+19 ; spinner screen loc
DeathLoc = $4A8+19
mli = $bf00 ; ProDOS entry pt
init = $fb2f ; init text screen
tabv = $fb5b ; vtab to a-reg
title = $fb60 ; clear screen, display "Apple //e"
bell1 = $fbdd ; beep
wait = $fca8 ; waste time
cout = $fded ; character out
setkbd = $fe89 ; set keyboard as input
setvid = $fe93 ; set text screen as output
.org BootStart ; code gets loaded here
; Main routine
.proc NetBootLC
jsr init ; init text screen
jsr setkbd
jsr setvid
jsr HelloMsg ; Greeting message
lda #'F'+$80
sta NotifyLoc ; tell user we are finding the card
jsr FindCard
bcc :+
errend: jsr ErrorMsg ; whoopsie doodles
die: jmp Death ; Try next slot or hang.
: lda #'R'+$80
sta NotifyLoc ; tell user we are moving boot code
jsr ReloBoot ; move $0300 code
lda #'I'+$80
sta NotifyLoc ; tell user we are initing the card
jsr InitCard
bcs errend ; Init failed... sorry
jsr GetInfo
bcs errend
; local zone lookup doesn't always work when we don't have a bridge
; so if we don't have a bridge yet, don't do zone lookup unless ca/option is
; pressed
lda ATbridge
bne :+ ; if we have a bridge
sec ; flag no zone info
lda $c062 ; check closed-apple/option
bpl :++ ; skip if not pressed
: lda #'Z'+$80
sta NotifyLoc ; tell user we are getting our zone
jsr GetZone
: jsr DispInfo ; carry set = do not try to display zone from NBPBuf
lda #'L'+$80
sta NotifyLoc ; tell user we are looking for server
jsr FindSrv
bcc :+ ; found it
jsr NoServer ; didn't find one... sorry
bra die
: jsr DispServ ; give user boot network location
lda #'B'+$80
sta NotifyLoc ; tell user we are gonna boot
ldx #3 ; copy boot server addr to ATP req
: lda NBPBuf,x
sta ATPaddr,x
stz mli,x ; and zero out ProDOS MLI entry point, too,
; because the Logon program might check this and
; avoid initializing the card.
bpl :-
; brk ; DEBUG
jmp ATPBoot ; go to $300 code
; Find workstation card, since the //e Card is flexible about its
; placement. Also we can run on a regular //e with a Workstation Card
; for no particular reason.
.proc FindCard
lda #$f9 ; offset to ID bytes
sta CardPtr
lda #$c7 ; start at slot 7
sta CardPtr+1
nextslot: ldy #3 ; check ID bytes
: lda (CardPtr),y
cmp idtbl,y
bne nomatch
bpl :-
ldy #4 ; This is card type byte offset
; 0 = IIgs
; 1 = Workstation Card
; 2 = unseen Server(!) Card
; $F0 = card diags in progress
: lda (CardPtr),y
sta NotifyLoc ; a little visual info
cmp #$f0 ; Shouldn't happen on LC Card, but...
beq :- ; wait for it all the same
cmp #1 ; Because it's proper to make sure it's a working WS Card
beq gotcard ; found it!
nomatch: dec CardPtr+1 ; next slot
lda CardPtr+1 ; get it
cmp #$c0 ; hit slot 0?
bne nextslot ; nope
sec ; no card found
gotcard: clc ; card found
idtbl: .byte "ATLK"
; Initialize the WorkStation Card
.proc InitCard
sei ; disable interrupts for now
lda #<CardInt ; set up IRQ vector
sta IRQvect ; for card
lda #>CardInt
sta IRQvect+1
lda CardPtr+1
sta GoCard+2 ; init card MLI call addr
sta CardInt+2 ; init card interrupt addr
ATcall iniparms
done: cli ; re-enable interrupts
; AppleTalk init call parms. Undocumented for the most part.
iniparms: .byte 0,1 ; synchronous init
.word $0000 ; result code, most likely
.byte $00 ; init type. Known types & users:
; $00 - partial init, no MLI global page update
; used by: ETalk (ethernet NetBoot)
; $80 - full init, no MLI global page update
; used by: ETalk (no NetBoot), Fizzy
; Possibly not usable on WS Card.
; $40 - full init, MLI global page update
; used by: ATInit, Logon (boot block version)
.dword $00000000 ; "ProDOS" entry point - zero seems to work
; if not using global page update
.byte $00 ; node num preference, 0 = any node number
.word $0000 ; unknown or reserved
; do GetInfo call
.proc GetInfo
ATcall inforeq
bcs done
lda abridge
sta ATbridge
lda thisnet
sta ATnet
lda thisnet+1
sta ATnet+1
lda nodenum
sta ATnode
done: rts
inforeq: .byte 0,2 ; sync GetInfo
.word $0000 ; result code
.dword $00000000 ; completion address
thisnet: .word $0000 ; this network #
abridge: .byte $00 ; local bridge
.byte $00 ; hardware ID
.word $00 ; ROM version
nodenum: .byte $00 ; node number
; Display our info. If carry is clear, try to display zone name
; from results of GetMyZone in NBPBuf
; i.e. call GetInfo, then call GetMyZone, then call this.
.proc DispInfo
lda #17 ; line 18
jsr tabv
stz ch ; col 0
; display our address
lda ATnet ; network is 16-bit
ldx ATnet+1
jsr PrintU16
lda #'.'+$80 ; standard separator
jsr cout
lda #$00
ldx ATnode ; node number
jsr PrintU16
; display bridge node if present
lda ATbridge
beq :+ ; skip if 0
ldx #msg4-msg1 ; " bridge ."
jsr Disp
lda #$00
ldx ATbridge
jsr PrintU16
: plp ; see if we should display zone
bcs done ; nope
; display zone
ldx #msg5-msg1 ; " zone "
jsr Disp
jsr DispZone ; display it
done: rts
; Get our zone - for information
.proc GetZone
ATcall zonereq
bcs done ; error, bail
lda NBPBuf ; check pascal str count
bne done ; if nonzero, done
sec ; otherwise signal error
done: rts
zonereq: .byte 0,$1a ; sync GetMyZone
.word $0000 ; result
.dword $00000000 ; completion
.dword NBPBuf ; we'll use the NBP buffer since zone is just FYI
.byte 4,4 ; 4 times every 1 sec
.word $0000 ; reserved
.proc DispZone
; below commented out because we assume we are called from DispInfo
;lda #17 ; line 19
;jsr tabv
;stz ch ; col 0
ldx #$00
: cpx NBPBuf ; did we display all of them?
beq done ; yes, done
lda NBPBuf+1,x ; get char
ora #$80
jsr cout ; display
inx ; next
bra :-
done: rts
; Find a boot server in our zone
.proc FindSrv
ATcall lookup
bcs done ; error, bail
lda matches ; check # matches
bne done ; OK if not zero
done: rts
; parameter list for NBPLookup
lookup: .byte 0,16 ; sync NBPLookup
.word $0000 ; result
.dword $00000000 ; completion
.dword srvname ; pointer to name to find
.byte 8,16 ; 16 times, every 2 secs
.word $0000 ; reserved
.word NBPBufSz ; buffer size
.dword NBPBuf ; buffer loc
.byte 1 ; matches wanted
matches: .byte $00 ; matches found
srvname: .byte 1,"=" ; object
.byte 14,"Apple //e Boot" ; type
.byte 1,"*" ; zone
; Display found boot server address and object name
.proc DispServ
lda #18 ; line 19
jsr tabv
stz ch ; col 0
; display the server address
lda NBPBuf ; network is 16-bit
ldx NBPBuf+1
jsr PrintU16
lda #'.'+$80 ; standard separator
jsr cout
lda #$00
ldx NBPBuf+2 ; node number
jsr PrintU16
lda #'/'+$80 ; standard separator
jsr cout
lda #$00
ldx NBPBuf+3 ; socket number
jsr PrintU16
lda #' '+$80
; now display server object name
jsr cout
ldx #$00
: cpx NBPBuf+5 ; did we display all of them?
beq done ; yes, done
lda NBPBuf+6,x ; get char
ora #$80
jsr cout ; display
inx ; next
bra :-
done: rts
; Print unsigned 16-bit integer
; adapted from
.proc PrintU16
stx DispTmp
sta DispTmp+1
lda #$00
: pha
lda #$00
ldy #$10
: cmp #$05
bcc :+
sbc #$85
: rol DispTmp
rol DispTmp+1
rol a
bne :--
ora #$b0
bvs :---
: jsr cout
bne :-
; we are dead and can't even start downloading boot blocks
; so go to next slot if we are booting, or hang otherwise
; can't be used after we go to $300 code
.proc Death
lda $00 ; $00 must be 0
bne :+ ; or we are not booting
lda $01
and #$f0
cmp #$c0 ; $01 must be $Cx
bne :+
lda #$ff
jsr wait ; wait a bit so user can see message
jmp $faba
: lda #$58 ; flashing (red) X
sta DeathLoc ; on screen
hang: bra hang ; hang
; display the "no boot server" message
.proc NoServer
ldx #msg3-msg1
bra Disp
; display the "something went wrong" message
.proc ErrorMsg
ldx #msg2-msg1
bra Disp
; display the greeting
.proc HelloMsg
jsr title ; apple ii title screen (card boot clears screen)
ldx #msg1-msg1 ; better be zero!
; fall-through
; Display one of the messages, can't be used after we go to $300 code
.proc Disp
lda msg1,x
bne :+
: inx ; set up for next message byte
cmp #$18 ; last line + 1
bcc repos
eor #$80
jsr cout ; not supposed to change anything
bra Disp
repos: jsr tabv ; destroys a and y, but not x
lda msg1,x ; get horizontal
sta ch ; and write
inx ; next message byte
bra Disp
msg1: .byte 05,08,"NetBoot LC v1.0 by M.G."
.byte 06,05,"Starting up over the network...",$00
msg2: .byte 08,09,"Something went wrong!",$00
msg3: .byte 08,12,"No boot server!",$00
msg4: .byte ", bridge .",$00
msg5: .byte ", zone ", $00
; move $300 code into position
.proc ReloBoot
ldx #BootOSize+1
: lda BootRStrt-1,x
sta $0300-1,x
bne :-
; Code to be moved to $300 follows
BootRStrt = *
.org $0300
BootOBgn = *
; Call the card's MLI
.proc GoCard
jmp $C714 ; For slot 7, modify in InitCard
; Provide an interrupt handler for the card
.proc CardInt
jsr $C719 ; For slot 7, modify in InitCard
; Boot using ATP requests to retrieve boot blocks.
.proc ATPBoot
fetch: lda #1
sta ATPbmap ; want only block 0 (ATP-wise)
ATcall ATPparms
bcs error ; oops
lda Status ; is EOF?
beq :+ ; keep reading if not EOF
sei ; otherwise, no more interrupts
jmp BootStart ; and execute next boot stage
: inc BlkNum ; implicitly limited, below
lda BlkNum ; get it for spinner
and #$03 ; mask in low bits
lda spinner,y ; get spinner char
sta SpinLoc ; put on middle of screen
lda BlkPtr+1 ; block pointer (load addr)
adc #$02 ; $200 bytes
sta BlkPtr+1 ; inc address
cmp #$c0 ; Reading too far?
bcc fetch ; read next block if not
error: jsr bell1 ; beep speaker
lda #$58 ; flashing (red) X
sta SpinLoc ; on screen
hang: bra hang
spinner: .byte '|'+$80
.byte '/'+$80
.byte '-'+$80
.byte '\'+$80
ATbridge: .byte $00 ; local bridge
ATnet: .word $0000 ; local net
ATnode: .byte $00 ; our node number
ATPparms: .byte 0,18 ; sync SendATPReq
.word $0000 ; result
.dword $00000000 ; compl. addr
.byte $00 ; socket #
ATPaddr: .dword $00000000 ; destination address
.word $0000 ; TID
.word $0000 ; req buffer size
.dword $00000000 ; req buffer addr
.byte $02 ; boot type
; $01 = IIgs stage 1
; $02 = //e
; $03 = IIgs boot image
BlkNum: .word $0000 ; block number to req
.byte $00 ; unused
.byte $01 ; one response buffer
.dword BDS ; pointer to response BDS
.byte $00 ; ATP flags
.byte 8,32 ; try 32 times every 2 seconds
ATPbmap: .byte $00 ; bitmap of blocks to recieve
.byte $00 ; number of responses
.res 6 ; 6 bytes reserved
BDS: .word $0200 ; length of buffer
BlkPtr: .dword BootStart ; block pointer
Status: .dword $00000000 ; returned user bytes, first byte = 1 if EOF
.word $0000 ; actual length
BootOEnd = *
.assert BootOEnd < $3d0, warning, "Page 3 code too big"
BootOSize = BootOEnd - BootOBgn
; end of $300 code, fix up origin
.org BootRStrt + BootOSize
NBPBuf = (* >> 8 + 1)*$100 ; put this on next page boundary
.out .sprintf("NBP Buffer at $%x", NBPBuf)

75 Normal file
View File

@ -0,0 +1,75 @@
# NetBoot LC
NetBoot LC is an alternative Apple II Workstation boot program for the Apple //e Card for Macintosh LC.
The built-in Apple II Workstation functionality of the card closely mimics the combination of an Enhanced //e with a Workstation Card in all aspects except two:
- It shares the AppleTalk node address with the host Macintosh. This is not really a problem, because most client applications don't care what the node number is (unless you want to access File Sharing or an AppleShare server on the host Macintosh - see here).
- It does not load the boot blocks over the network, instead they are contained within the IIe Startup application's BBLK resources. This might be a problem depending on your use cases or preferences. For reference, the Apple II boot blocks contain ProDOS and the Logon program.
The main problems I see with the “firm-coded” boot blocks are:
They are more difficult to update as they require use of ResEdit each time a new ProDOS is released. While this was not a problem for some 25 years, new ProDOS releases have changed that.
The behavior is not the same as the combination of Enhanced //e and Workstation Card.
It is not clear to me why Apple decided to do it differently in the //e Card. My guesses are: It adds a little bit of speed; it prevents some administrative issues where the boot blocks served over the network have not been updated to meet a requirement of the card; and perhaps the engineers of the Card knew that the next version of AppleShare Server was going to drop Apple II boot support.
In any case, after a small and successful quest to update the boot blocks to ProDOS 2.4.1 and Logon 1.5, I decided that I wanted the //e Card to boot like my other //es with Workstation Cards, and NetBoot LC is the result.
## What it Does
NetBoot LC replaces the firm-coded ProDOS 1.9 and Logon 1.3 boot blocks in the IIe Startup program with a new program that downloads boot blocks over the network like any Enhanced //e with a Workstation Card.
Along the way, it also provides some useful info such as the the workstation node address, bridge node number, AppleTalk zone, and boot server address and name. There is a nice spinner that lets you know something is happening.
If the boot is happening due to system cold start and fails before the boot block download starts, the next slot will be tried (if “scan” is configured in the slot preferences).
A status letter is indicated on the lower-left corner of the screen, useful for figuring out what is slow or failing:
* ``F``: Finding Workstation Card.
* ``R``: Relocating $300 code.
* ``I``: Initializing Workstation Card & Getting Info.
* ``Z``: Identifying local Zone.
* ``L``: Looking for boot server.
* ``B``: Downloading boot blocks. At this point spinner will start after the first block is retrieved.
## Building
The only supported build environment for the moment is MacOS X.
* Working cc65 installation with binaries in PATH.
* Apple command-line developer tools (``xcode-select --install``).
* Make sure you have the following binaries in your PATH:
* ``hexdump``
* ``Rez``
* ``macbinary``
There is a make target to build a disk image and execute in Virtual ][. To use it you will need AppleCommander and will need to edit the Makefile. This is for testing and is otherwise not necessary to build the code and use it.
The build process uses resource forks, your filesystem must support them.
To build, change to the project directory and execute ``make``.
## Installation
- Download NetBoot LC to your Macintosh.
- Decode the Macbinary file. The resulting file will look like an application, but it is not and clicking it will do nothing.
- Open NetBoot LC and a copy of IIe Startup in ResEdit.
- Open the BBLK resources in the copy of IIe Startup, there should be one resource with ID 5120.
- Delete BBLK ID 5120.
- Open the BBLK resources in NetBoot LC.
- Copy ID 5120 from NetBoot LC to the BBLK resources in the copy of IIe Startup.
- Quit ResEdit, save the copy of IIe Startup on your way out.
At this point launching IIe Startup and booting over AppleTalk should work as described above.
Obviously, to be useful, you need an AppleShare 2.x/3.x server or netatalk server, with Apple II booting set up, to make use of this.
## Technical / Developing
I recommend having a look at the source code. It's a bit of a mess but it demonstrates some undocumented Apple II AppleTalk functionality, as well as use of ATP.
If you add features or fix bugs, please send a pull request.

utils/ Executable file
View File

@ -0,0 +1,19 @@
set -e
if [ ! -r "$1" ]; then
echo "Usage: $0 <file>"
exit 1
hexdump -v -f `dirname $0`/rezhex.format "$1" | tr 'Q' '"' > $TFILE
cat << EOF
type 'BBLK' {
hex string;
resource 'BBLK' (5120, "Apple //e Boot Blocks") {
`cat $TFILE`
rm -f $TFILE

utils/rezhex.format Normal file
View File

@ -0,0 +1 @@
"$Q" 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X " 1/1 "%02.2X" 1/1 "%02.2X" "Q\n"