********************************************************************************
* Disassembly of RBOOT, the DOS Toolkit relocatable loader boot shim, as *
* described in "Applesoft Tool Kit" on ch.4 pg.29. This is used to load *
* Apple's relocatable Hi-Res Character Generator (HRCG). *
* *
* This BLOADS at $208 and, after a CALL 520, itself loads RLOAD into free *
* space around 1-2 pages above BASIC variables, then points the USR vector *
* there. There is enough padding to let you allocate some simple variables *
* without clobbering RLOAD before you can use it. You can then load *
* relocatable modules (file type R) by calling RLOAD via USR(0). *
* *
* RBOOT's relocation method is simple but clever. It disassembles RLOAD with *
* monitor routines to find 16-bit operands, which must be addresses on a 6502. *
* If the address is within the module (per the file header address and length) *
* it is relocated. If not--e.g. an Applesoft call--it is left alone. This *
* can't relocate 8-bit low or high address immediates, and only works on code *
* (not data) but RLOAD is written with this in mind. RLOAD even uses the BIT *
* operand once to disguise a data address as code. *
* *
* Not documented: you can load a different binary file (instead of RLOAD) by *
* quoting it after the call: CALL 520"RLOAD2". That could be left over from *
* the development process. *
* *
* Disasm by Jim Ursetto https://github.com/ursetto with 6502bench SourceGen. *
********************************************************************************
FM_OPEN .eq $01 {const}
FM_CLOSE .eq $02 {const}
FM_READ .eq $03 {const}
BAS_HANDLERR .eq $f2e9 {const} ;routine to handle errors if onerr goto active
BAS_USRVEC .eq $0a {addr/3} ;USR() command vector
MON_LENGTH .eq $2f ;output of MON_INSDS2
MON_A3L .eq $40 ;general purpose
MON_A3H .eq $41 ;general purpose
BAS_STREND .eq $6d {addr/2} ;pointer to end of numeric storage (2b)
BAS_MEMSIZE .eq $73 {addr/2} ;HIMEM (2b)
BAS_CHRGET .eq $b1 ;get next character or Applesoft token
BAS_CHRGOT .eq $b7 ;get next, but don't advance TXTPTR
BAS_ERRNUM .eq $de ;error number for onerr handler
BAS_REMSTK .eq $f8 ;stack pointer saved before each statement
DOS_FM .eq $03d6 ;DOS file manager entry point
DOS_LOCFPL .eq $03dc ;loads Y/A with address of FM param list
DOS_LOCRPL .eq $03e3 ;loads Y/A with address of RWTS IOB
MON_INSDS2 .eq $f88e
.org $0208
0208: 4c 32 02 rboot_entry jmp start
020b: e0 rload_bound .dd1 $e0 ;end of RLOAD, rounded up to next page
020c: 00 00 fileaddr .dd2 $0000 ;ORG of RLOAD (as found in file address)
020e: 00 00 filelen .dd2 $0000 ;length of RLOAD
0210: 00 00 usrvec .dd2 $0000 ;intermediate copy of USR vector
0212: 00 00 rload_end .dd2 $0000 ;end of relocated RLOAD in memory
0214: d2 cc cf c1+ filename .str ↑“RLOAD ”
; param_list ($06/$07) is also used to pass rload_end to RLOAD.
param_list .var $06 {addr/2} ;DOS FM / RWTS-IOB param list
0232: 20 e3 03 start jsr DOS_LOCRPL
; First, obtain the current slot and drive number from DOS RWTS.
0235: 84 06 sty param_list ;address of RWTS IOB
0237: 85 07 sta param_list+1
0239: a0 01 ldy #$01
023b: b1 06 lda (param_list),y ;slot*16
023d: 4a lsr A
023e: 4a lsr A
023f: 4a lsr A
0240: 4a lsr A
0241: 48 pha ;push slot number
0242: c8 iny
0243: b1 06 lda (param_list),y
0245: 48 pha ;push drive number
; Build a file manager call to open RLOAD.
0246: 20 dc 03 jsr DOS_LOCFPL
0249: 84 06 sty param_list
024b: 85 07 sta param_list+1
024d: a0 00 ldy #$00
024f: a9 01 lda #FM_OPEN ;Call type: OPEN
0251: 91 06 sta (param_list),y
0253: c8 iny
0254: a9 00 lda #$00
0256: 91 06 sta (param_list),y ;Call subtype: N/A
0258: c8 iny
0259: a9 01 lda #$01 ;Record length: 1
025b: 91 06 sta (param_list),y
025d: c8 iny
025e: a9 00 lda #$00 ;(high byte of record length)
0260: 91 06 sta (param_list),y
0262: c8 iny
0263: 91 06 sta (param_list),y ;Volume: any
0265: c8 iny
0266: 68 pla ;Drive number (from RWTS)
0267: 91 06 sta (param_list),y
0269: c8 iny
026a: 68 pla ;Slot number (from RWTS)
026b: 91 06 sta (param_list),y
026d: c8 iny
026e: a9 04 lda #$04 ;File type: Binary
0270: 91 06 sta (param_list),y
0272: c8 iny
0273: a9 14 lda #<filename ;Filename to open
0275: 91 06 sta (param_list),y
0277: a9 02 lda #>filename
0279: c8 iny
027a: 91 06 sta (param_list),y
027c: 20 b7 00 jsr BAS_CHRGOT
027f: f0 2f beq no_more_args
; If a quoted argument is next on the BASIC CALL line, set the filename to load
; to that (instead of RLOAD). For example: CALL 520"RLOAD2" (end quote can be
; omitted if end of line). This was probably for debugging the relocator.
0281: c9 22 cmp #‘"’ ;begin quote
0283: d0 26 bne discard_eol
0285: 20 b1 00 jsr BAS_CHRGET
0288: f0 26 beq no_more_args
028a: aa tax
028b: a0 1d ldy #$1d ;length of filename buffer (-1)
028d: a9 a0 lda #$a0
028f: 99 14 02 @loop sta filename,y ;clear filename buffer to spaces
0292: 88 dey
0293: 10 fa bpl @loop
0295: 8a txa
0296: c8 copy_filename iny
0297: c0 1e cpy #$1e ;avoid filename buffer overrun
0299: b0 10 bcs discard_eol
029b: 09 80 ora #$80
029d: 99 14 02 sta filename,y
02a0: 20 b1 00 jsr BAS_CHRGET
02a3: f0 0b beq no_more_args ;end of line or
02a5: c9 22 cmp #‘"’ ;end quote terminates filename
02a7: f0 02 beq discard_eol
02a9: d0 eb bne copy_filename
02ab: 20 b1 00 discard_eol jsr BAS_CHRGET ;discard rest of basic line
02ae: d0 fb bne discard_eol
02b0: a2 01 no_more_args ldx #$01 ;do not create file if not found
02b2: 20 67 03 jsr call_dos_fm
02b5: 90 05 bcc check_binary ;carry set if DOS error
02b7: a2 06 ldx #$06
02b9: 4c 53 03 jmp onerr
02bc: a0 07 check_binary ldy #$07 ;file type of opened file
02be: b1 06 lda (param_list),y
02c0: 29 7f and #$7f
02c2: c9 04 cmp #$04 ;is file type B?
02c4: f0 05 beq read_file ;yes, read it
02c6: a2 0a ldx #$0a
02c8: 4c 53 03 jmp onerr
********************************************************************************
* Read RLOAD into just after end of BASIC variables, ignoring its load address *
* of $800. It will be page-aligned and with $101-$200 bytes of padding, *
* presumably so a few simple variable allocations afterward will not clobber *
* it. Later, BASIC's USR vector is pointed to this code at offset 0. *
********************************************************************************
02cb: a0 00 read_file ldy #$00
02cd: a9 03 lda #FM_READ
02cf: 91 06 sta (param_list),y ;READ file
02d1: c8 iny
02d2: a9 02 lda #$02 ;read/write range of bytes
02d4: 91 06 sta (param_list),y
02d6: a0 06 ldy #$06
02d8: a9 04 lda #$04 ;read 0004 bytes (file addr and len)
02da: 91 06 sta (param_list),y
02dc: c8 iny
02dd: a9 00 lda #$00
02df: 91 06 sta (param_list),y
02e1: c8 iny
02e2: a9 0c lda #<fileaddr ;read into our fileaddr/filelen
02e4: 91 06 sta (param_list),y
02e6: c8 iny
02e7: a9 02 lda #>fileaddr
02e9: 91 06 sta (param_list),y
02eb: c8 iny
02ec: a9 04 lda #$04 ;unknown. cannot find docs on FM offset 0A/0B
02ee: 91 06 sta (param_list),y
02f0: c8 iny
02f1: a9 00 lda #$00
02f3: 91 06 sta (param_list),y
02f5: 20 67 03 jsr call_dos_fm ;execute READ
02f8: ad 0e 02 lda filelen
02fb: 8d 12 02 sta rload_end ;anticipating our load address low byte is $00
02fe: a0 06 ldy #$06
0300: 91 06 sta (param_list),y ;place file length into FM READ block
0302: c8 iny
0303: ad 0f 02 lda filelen+1
0306: 48 pha ;save high byte of filelen
0307: 91 06 sta (param_list),y
0309: a9 00 lda #$00 ;#<usrvec (page aligned)
030b: c8 iny
030c: 91 06 sta (param_list),y ;load address low byte
; Round STREND down to the nearest page and add $200, setting usrvec (our load
; address and USR vector) to $101 - $200 bytes past the start of Applesoft free
; space. (The layout is PROGRAM, VARS, ARRAYS, FREE, STRINGS, with strings
; growing down from HIMEM, so STREND is the end of array variable data and the
; start of free space.) usrvec temporarily holds the USR vector until we confirm
; the program will fit.
030e: 8d 10 02 sta usrvec
0311: a6 6e ldx BAS_STREND+1 ;start of free space
0313: e8 inx ;add $200 to BAS_STREND
0314: e8 inx
0315: 8e 11 02 stx usrvec+1 ;STREND+$101 <= usrvec <= STREND+$200
0318: 18 clc
0319: 68 pla ;restore high byte of filelen
031a: 6d 11 02 adc usrvec+1
031d: 8d 13 02 sta rload_end+1 ;rload_end = usrvec + filelen
0320: 8a txa ;#>usrvec
0321: c8 iny
0322: 91 06 sta (param_list),y ;load address high byte
0324: ad 12 02 lda rload_end
0327: c5 73 cmp BAS_MEMSIZE ;Ensure rload_end lies under HIMEM
0329: ad 13 02 lda rload_end+1
032c: e5 74 sbc BAS_MEMSIZE+1
032e: 90 05 bcc setup_usr
0330: a2 0e ldx #$0e
0332: 4c 53 03 jmp onerr ;program does not fit, bail
; The program fits in memory, so load it, relocate it, and update BASIC's USR
; vector with our temporary usrvec. File operations here are not checked for
; error.
0335: 20 67 03 setup_usr jsr call_dos_fm
0338: 20 5e 03 jsr close_file
033b: 20 6d 03 jsr relocate
033e: ad 10 02 lda usrvec
0341: 85 0b sta BAS_USRVEC+1
0343: ad 11 02 lda usrvec+1
0346: 85 0c sta BAS_USRVEC+2
; Stash rload_end in a safe place in zero page. This will be used shortly by
; RLOAD to verify it won't clobber itself when loading the relocatable module.
0348: ad 12 02 lda rload_end
034b: 85 06 sta param_list
034d: ad 13 02 lda rload_end+1
0350: 85 07 sta param_list+1
0352: 60 rts ;Return to BASIC
********************************************************************************
* Close file and jump to ONERR handler, which is expected to be active. X is *
* error number (here $06, $0A or $0E). Note: Sets the BASIC errno and restores *
* stack, normally done at the beginning of HANDLERR. *
********************************************************************************
0353: 86 de onerr stx BAS_ERRNUM ;set errno
0355: 20 5e 03 jsr close_file
0358: a6 f8 ldx BAS_REMSTK ;Restore BASIC stack pointer
035a: 9a txs
035b: 4c ef f2 jmp BAS_HANDLERR+6 ;handle ONERR (bypasses stack/errno set)
035e: a0 00 close_file ldy #$00
0360: a9 02 lda #FM_CLOSE
0362: 91 06 sta (param_list),y
0364: 4c 67 03 jmp call_dos_fm ;falls through
0367: 20 dc 03 call_dos_fm jsr DOS_LOCFPL
036a: 4c d6 03 jmp DOS_FM
; Relocate RLOAD. Scan the RLOAD area with monitor disassembly routines to find
; 16-bit operands (addresses), and if they are internal to RLOAD, relocate them.
; We can identify internal addresses because they are between the ORG of RLOAD
; (fileaddr) and its end (+filelen). Scan ends when a BRK is encountered. Note:
; the org and usrvec are always page-aligned, and the end is rounded up to the
; next page.
036d: ad 10 02 relocate lda usrvec ;usrvec low byte is implicitly 0
0370: 85 40 sta MON_A3L ;store usrvec in 16-bit temporary
0372: ad 11 02 lda usrvec+1 ;... for scan start address
0375: 85 41 sta MON_A3H
0377: ad 0f 02 lda filelen+1 ;high byte of length
037a: 38 sec ;add 1 page for BCS below
037b: 6d 0d 02 adc fileaddr+1 ;page of usrvec+fileaddr+filelen+1
037e: 8d 0b 02 sta rload_bound ;next page after end of RLOAD
0381: a0 00 @loop ldy #$00
0383: b1 40 lda (MON_A3L),y ;get opcode
0385: f0 30 beq @done ;exit on first zero opcode (BRK)
0387: 20 8e f8 jsr MON_INSDS2 ;compute operand length
038a: a4 2f ldy MON_LENGTH ;output of INSDS2
038c: c0 02 cpy #$02 ;look for a two-byte operand (address)
038e: d0 18 bne next_opcode
0390: b1 40 lda (MON_A3L),y ;get operand high byte
0392: cd 0b 02 cmp rload_bound ;are we > the last RLOAD page (>= RLOAD+1)
0395: b0 11 bcs next_opcode ;yes, skip relocation
0397: cd 0d 02 cmp fileaddr+1 ;are we < the lowest RLOAD page?
039a: 90 0c bcc next_opcode ;yes, skip relocation
; To each 16-bit address operand, add the usrvec page - fileaddr page to get the
; relocated page. The lower byte is ignored, as both the file and usrvec are
; page-aligned.
039c: ad 11 02 lda usrvec+1
039f: 18 clc
03a0: 71 40 adc (MON_A3L),y ;*operand += usrvec page
03a2: 38 sec
03a3: ed 0d 02 sbc fileaddr+1 ;*operand -= fileaddr page
03a6: 91 40 sta (MON_A3L),y
03a8: 98 next_opcode tya
03a9: 38 sec ;add opcode len (1)
03aa: 65 40 adc MON_A3L ;and operand len (A) to code ptr
03ac: 85 40 sta MON_A3L
03ae: a5 41 lda MON_A3H
03b0: 69 00 adc #$00
03b2: 85 41 sta MON_A3H
03b4: b8 clv
03b5: 50 ca bvc @loop ;always
03b7: 60 @done rts
No exported symbols found.