Linker/seg.asm

1242 lines
24 KiB
NASM

keep obj/seg
mcopy seg.mac
****************************************************************
*
* Segment Processing
*
* This module contains the subroutines used to find the next
* segment that needs to be linked.
*
****************************************************************
copy directPage
****************************************************************
*
* SegCommon - global data for the segment module
*
****************************************************************
*
SegCommon privdata
;
; Scalars
;
inFile ds 2 are we processing a file?
isLibrary ds 2 is the file we are processing a library file?
largeLibFile ds 2 largest library file number
libDisp ds 4 disp in library symbol table
suffix ds 2 suffix letter
autoSegCounter ds 2 auto-segmentation counter (hex string)
autoSegLength ds 4 length of current auto segment
autoSegNamePtr ds 4 pointer to current auto-segment name
end
****************************************************************
*
* AutoSegment - perform auto-segmentation
*
* inputs:
* loadNamePtr - load segment name
* segLength - # of bytes of code in the segment
* segAlign - segment alignment factor
*
* outputs:
* loadNamePtr - modified if using auto-segmentation
*
****************************************************************
*
AutoSegment start
using SegCommon
using Common
using OutCommon
move4 loadNamePtr,r0 if load seg name is not 'AUTOSEG~~~'
ldy #nameSize-2
lb0 lda [r0],Y
cmp autoSegStr,Y
beq lb0a
rts return
lb0a dey
dey
bpl lb0
lda segAlign perform alignment if necessary
bne lb1
lda segAlign+2
beq lb2
lda #0
lb1 dec a
bit autoSegLength
beq lb2
ora autoSegLength
inc a
sta autoSegLength
bne lb2
inc autoSegLength+2
lb2 add4 autoSegLength,segLength update auto seg length
lda autoSegLength+2 if it is over $10000 bytes
beq lb5
dec a
ora autoSegLength
beq lb5
move4 segLength,autoSegLength set length to seg length
short M update auto-seg counter
lda autoSegCounter+1
inc a
cmp #'9'+1
bne lb3
lda #'A'
lb3 cmp #'F'+1
bne lb3b
lda autoSegCounter
inc a
cmp #'9'+1
bne lb3a
lda #'A'
lb3a sta autoSegCounter
lda #'0'
lb3b sta autoSegCounter+1
long M
ph4 #nameSize make new auto-seg name string
jsr MLalloc
sta r4
sta autoSegNamePtr
stx r4+2
stx autoSegNamePtr+2
ldy #nameSize-2
lda autoSegCounter
bra lb4a
lb4 lda autoSegStr,Y
lb4a sta [r4],Y
dey
dey
bpl lb4
! set load seg name to auto-seg name
lb5 move4 autoSegNamePtr,loadNamePtr
rts
;
; Local constant
;
autoSegStr dc c'AUTOSEG~~~'
end
****************************************************************
*
* CopyBasename - make a copy of the base name
*
* inputs:
* basename - base keep name
*
* outputs:
* fname - copy of basename
*
****************************************************************
*
CopyBasename start
using SegCommon
ph4 fname free old buffer
jsr Free
lda [basename] get new buffer
pea 0
inc A
inc A
pha
jsr MLalloc
sta fname
stx fname+2
sta r4 copy basename to fname
stx r6
move4 basename,r0
jsr MoveName
rts
end
****************************************************************
*
* Exists - see if a file exists
*
* Inputs:
* fname - pointer to the file name
*
* Returns:
* 1 if the file exists, else 0
*
****************************************************************
*
Exists private
val equ 1 does the file exist?
sub (4:fname),2
stz val assume the file does not exist
move4 fname,giPathname if it does exist then
OSGet_File_Info giRec
bcs lb1
inc val ++val
lb1 ret 2:val return val
giRec dc i'2'
giPathname ds 4
ds 2
end
****************************************************************
*
* ExistsM - see if a file exists in the memory list
*
* Inputs:
* fname - pointer to the file name
* memory - is this a +m link?
*
* Returns:
* 1 if the file exists, else 0
*
****************************************************************
*
ExistsM private
using Common
val equ 1 does the file exist?
sub (4:fname),2
ph4 fname (needed for both if and else branch)
lda memory if this is a +m link then
beq lb1
jsr ScanFastFile scan the FastFile list
bra lb2 else
lb1 jsr Exists check the disk
lb2 sta val
ret 2:val return val
end
****************************************************************
*
* FileType - get the type of a file
*
* Inputs:
* fname - pointer to the file name
*
* Returns:
* file type (0 for none)
*
****************************************************************
*
FileType private
sub (4:fname),0
stz giFiletype assume the file does not exist
move4 fname,giPathname if it does exist then
OSGet_File_Info giRec
ret 2:giFiletype return giFiletype
giRec dc i'3'
giPathname ds 4
ds 2
giFiletype ds 2
end
****************************************************************
*
* FindSuffix - find the highest keep suffix
*
* Inputs:
* basename - base file name
*
* Outputs:
* suffix - highest existing obj file suffix letter
*
****************************************************************
*
FindSuffix private
using SegCommon
lda #'a' set the initial suffix
sta lsuffix
lb1 lda lsuffix try it out
sta suffix
jsr KeepName
ph4 fname
jsr ExistsM
tax
beq lb2
inc lsuffix it works, so try the next one
bra lb1
lb2 lda lsuffix use the last one - it worked (or did
dec A not exist, as in 'a'-1)
sta suffix
rts
lsuffix ds 2 local suffix
end
****************************************************************
*
* GetName - get the next file name
*
* Inputs:
* sdisp - disp in the name list
* slist - list of file names
*
* Outputs:
* basename - new file name
* C - set if a name was found, else clear
*
****************************************************************
*
GetName start
ph4 baseName Free(baseName)
jsr Free
stz baseName basename = NULL
stz baseName+2
lda [slist] maxDisp = length(slist)+2
inc A
inc A
sta maxDisp
ldy sdisp Y = sdisp+2
iny
iny
lb1 cpy maxDisp while (Y < maxDisp)
blt lb1a
clc
rts
lb1a lda [slist],Y and (slist[Y] = ' ') do
and #$00FF
cmp #' '
bne lb2 ++Y
iny
bra lb1
lb2 sty nDisp save the starting disp
lb3 cpy maxDisp while (Y < maxDisp)
bge lb4
lda [slist],Y and (slist[Y] <> ' ') do
and #$00FF
cmp #' '
beq lb4
iny ++Y
bra lb3
lb4 sec A = Y-sDisp {length of the new string}
tya
sbc nDisp
dey sdisp = Y-2
dey
sty sdisp
pha baseName = mlalloc(A+2)
inc A
inc A
pea 0
pha
jsr MLalloc
sta baseName
stx baseName+2
lda 1,S set the file name length
sta [baseName]
add4 slist,nDisp,r0 set r0 to the start of the name-2
sub4 r0,#2
plx move in the new characters
ldy #2
short M
lb5 lda [r0],Y
sta [baseName],Y
iny
dex
bne lb5
long M
sec return found
rts
;
; Local data area
;
nDisp ds 4 disp in sname
maxDisp ds 2 max allowed disp
end
****************************************************************
*
* InitPass - initialize pass dependent variables
*
****************************************************************
*
InitPass start
using Common
using SegCommon
stz libIndex no libraries scanned
stz sdisp no chars processed in the source list
stz inFile not processing a file
stz fileNumber no files processed, so far
stz lastFileNumber
stz dataNumber no data areas processed
stz lastDataNumber
lda #'00' initial auto-seg is 'AUTOSEG~00'
sta autoSegCounter
lla autoSegNamePtr,initialAutoSegName
stz autoSegLength initial auto-seg length is 0
stz autoSegLength+2
rts
;
; Local constant
;
initialAutoSegName dc c'AUTOSEG~00'
end
****************************************************************
*
* KeepName - Update the Keep Name
*
* inputs:
* basename - base keep name
* suffix - suffix letter to use
*
* outputs:
* fname - current keep file name
* suffix - decremented
* C - set if there is another dot name, else clear
*
****************************************************************
*
KeepName private
using SegCommon
lda suffix if suffix = 'a'-1 then
cmp #'a'-1
bne kn0
clc return false
rts
kn0 ph4 fname free old buffer
jsr Free
lda [basename] get new buffer
clc
adc #4
pea 0
pha
jsr MLalloc
sta fname
stx fname+2
sta r4 copy basename to fname
stx r6
move4 basename,r0
jsr MoveName
lda [fname] append .suffix to the names
inc A
inc A
sta [fname]
tay
short M
kn1 lda #'.'
sta [fname],Y
iny
lda suffix
sta [fname],Y
long M
phy
ph4 fname if not exists(fname) then
jsr ExistsM
ply
tax
short M
bne kn2
lda suffix uppercase suffix
and #$DF
sta [fname],Y
kn2 dec suffix --suffix
long M
sec
rts
end
****************************************************************
*
* MoveName - move a file name
*
* Inputs:
* r0 - pointer to the name to move
* r4 - pointer to the new file buffer
*
* Notes:
* This subroutine assumes that the buffer is large
* enough.
*
****************************************************************
*
MoveName private
lda [r0]
inc A
tay
short M
lb1 lda [r0],Y
sta [r4],Y
dey
bpl lb1
long M
rts
end
****************************************************************
*
* NextFile - find the next file
*
* Inputs:
* sdisp - disp in the file list
* slist - file list
* fname - pointer to the base file name
* suffix - suffix letter for the next obj file
*
* Outputs:
* C - set if a file was found, else clear
* inFile - set to 1
* isLibrary - 1 for a library, 0 for an obj segment
* fname - pointer to the base file name
* suffix - suffix letter for the next obj file
*
****************************************************************
*
NextFile start
using Common
using SegCommon
;
; If there are more files left in an obj sequence, process the next one. For
; example, if we just processed foo.root, we need to look for foo.a.
;
lda inFile if inFile then
beq lb1
inc lastFileNumber update the file number
lda isLibrary if not isLibrary then
bne lb0
jsr Purge mark the old file as purgeable
stz inFile inFile = false
jsr KeepName form the next file name
bcc lb1 if exists(fname) then
jsr Open open(fname)
stz isLibrary isLibrary = false
lda #1 inFile = true
sta inFile
sec return more files
rts
;
; If the last file was a library file, close it
;
lb0 clc update the file number
lda lastFileNumber
dec A
adc largeLibFile
sta lastFileNumber
jsr CloseLibrary close the library file
;
; If the next file in the file list is a library, process it.
;
lb1 jsr GetName if there are files left then
jcc li1
ph4 basename get the next file
jsr Exists
tay
beq lb2
ph4 basename if filetype = LIB then
jsr FileType
cmp #LIB
bne lb2
lda #1 isLibrary = true
sta isLibrary
! lda #1 inFile = true
sta inFile
stz largeLibFile no files processed
lda lastFileNumber update the source file number
sta fileNumber
jsr CopyBasename make a copy of the file name
jsr OpenLibrary open the library file
jsr ReadLibraryHeader
sec return more files
rts
;
; Get the next file name from the list of file names specified on the
; command line.
;
lb2 lda lastFileNumber update the source file number
sta fileNumber
jsr FindSuffix find the highest dot suffix
jsr RootName form root file
ph4 fname if exists(fname) then
jsr ExistsM
tay
beq lb3
jsr Open open(fname)
lda #1 inFile = true
sta inFile
stz isLibrary isLibrary = false
sec return more files
rts
lb3 jsr KeepName form .a name
bcc lb4 if exists(fname) then
jsr Open open(fname)
lda #1 inFile = true
sta inFile
stz isLibrary isLibrary = false
sec return more files
rts
lb4 lda #1 TermError(1)
jmp TermError
;
; Process a library file from the library directory.
;
li1 jsr Unresolved see if we have unresolved references
bcc nf1
lda libFromShell see if we are using a {Libraries}
bne nf1 variable
jsr GetLibFile find the next library file
bcs li2
ph4 r0 none left -> free the buffer & quit
jsr Free
bra nf1
li2 ph4 baseName Free(baseName)
jsr Free
move4 r0,baseName basename = r0
jsr CopyBaseName make a copy of baseName
lda #1 isLibrary = true
sta isLibrary
! lda #1 inFile = true
sta inFile
stz largeLibFile no files processed
lda lastFileNumber update the source file number
sta fileNumber
jsr OpenLibrary open the library file
jsr ReadLibraryHeader
sec
rts
;
; There are no more files to process
;
nf1 clc return no more files
rts
end
****************************************************************
*
* NextLibrarySeg - get the next library segment
*
* Inputs:
* libSymbols - pointer to the symbol table
* libLength - length of the symbol table
* libNames - pointer to the names table
* libDisp - disp of the next symbol to process
* didLibSegment - did we process one, yet?
*
* Outputs:
* C - set if a segment was found, else clear
*
****************************************************************
*
NextLibrarySeg start
using SegCommon
using Common
dicName equ 0 disp to the name displacement
dicFile equ 4 disp to the file number
dicPriv equ 6 disp to the private flag
dicSeg equ 8 disp to the segment disp
dicLength equ 12 length of one entry
lb1 cmpl libLength,libDisp if we are at the end of the file then
bne lb2
lda didLibSegment if we did not processed a segment then
bne lb1a
clc return false
rts
lb1a stz libDisp start the scan over
stz libDisp+2
stz didLibSegment
lb2 add4 libSymbols,libDisp,r0 get a pointer to the entry
add4 libDisp,#dicLength skip to the next entry
clc push the disp to the name
ldy #2
lda libNames
adc [r0]
tax
lda libNames+2
adc [r0],Y
pha
phx
ldy #dicPriv push the private flag
lda [r0],Y
pha
ldy #dicFile set the file number
lda [r0],Y
clc
adc lastFileNumber
sta fileNumber
lda [r0],Y if file number > largest one so far then
cmp largeLibFile
blt lb3
sta largeLibFile update the largest library file
lb3 jsr NeedSegment if we don't need this segment then
tax
beq lb1 go get the next one
lda #1 note that we did one
sta didLibSegment
ldy #dicSeg read the segment
lda [r0],Y
tax
iny
iny
lda [r0],Y
sta r2
stx r0
jsr ReadLibrarySegment
jsr ProcessHeader process the header
sec return true
rts
end
****************************************************************
*
* NextObjSeg - get the next object segment
*
* Inputs:
* seg - pointer to the first byte in the last segment
* len - # bytes left in the file
* segDisp - length of the last segment
*
* Outputs:
* seg - pointer to the first byte in the new segment
* len - # bytes left in the file
* segLength - # of bytes of code in the segment
* segDisp - length of the new segment, in bytes
* sp - pointer to the first byte to process
* segSpace - reserved space at the end of the segment
* segType - segment type
* segName - pointer to the segment name
* segEntry - disp from start of segment for entry point
* segAlign - segment alignment factor
* startpc - pc at the start of the segment
*
****************************************************************
*
NextObjSeg private
using ExpCommon
using Common
vc0 sub4 len,segDisp update the # of bytes left
add4 seg,segDisp move to the start of the next segment
lda len if we are at the end of the file then
ora len+2
bne vc1
clc return with no segment
rts
vc1 jsr ProcessHeader process the segment header
cmpl len,segDisp make sure there are enough bytes in the
bge vc2 file
lda #4
jmp TermError
vc2 stz expSegment make sure the segment has not already
ph4 segName been included
ph2 #0
jsr GetSymbolValue
lda symbolData
beq vc2a
lda symbolFlag
and #isSegmentFlag
beq vc5
vc2a lda pass
cmp #2
beq vc3
lda #pass1Resolved
bra vc4
vc3 lda #pass2Resolved
vc4 and symbolFlag
bne vc6
vc5 sec
rts
! handle a duplicate segment
vc6 lda symbolFile if the segments are in the same file then
cmp fileNumber
beq vc0 skip this segment
lda segType if this segment is private then
and #$4000
bne vc5 process the segment
lda pass if this is pass 1 then
cmp #1
jeq vc0 don't flag the error
ph4 segName
ph2 #4 flag a duplicate segment error
jsr Error
brl vc0
end
****************************************************************
*
* NextSegment - find the next segment
*
* Outputs:
* C - set if a segment was found, else clear
*
****************************************************************
*
NextSegment start
using SegCommon
lda inFile if we are not processing a file then
bne lb2
lb1 jsr NextFile get one
bcc lb4
lb2 lda isLibrary if we are in a library then
beq lb3
jsr NextLibrarySeg get the next library segment
bcc lb1 if none, go to the next file
bra lb4 else
lb3 jsr NextObjSeg get the next obj segment
bcc lb1 if none, go to the next file
lb4 anop endif
rts
end
****************************************************************
*
* Open - open an object file and prepare it for input
*
* Inputs:
* fname - file name
*
* Outputs:
* seg - pointer to the first byte in the file
* len - length of the file
* segDisp - 0
*
****************************************************************
*
Open private
using Common
jsr Read open the file for input
lda r8 make sure the file is an obj file
cmp #OBJ
beq lb1
lda #2
jmp TermError
lb1 move4 r0,seg set the initial byte pointer
move4 r4,len set the lengt of the file
stz segDisp set the "previous" segment disp to 0
stz segDisp+2
rts
end
****************************************************************
*
* ProcessHeader - process the header for the next code segment
*
* Inputs:
* seg - pointer to the first byte in the segment
*
* Outputs:
* segLength - # of bytes of code in the segment
* segDisp - length of the new segment, in bytes
* sp - pointer to the first byte to process
* segSpace - reserved space at the end of the segment
* segType - segment type
* segName - pointer to the segment name
* segEntry - disp from start of segment for entry point
* segAlign - segment alignment factor
* segBanksize - segment bank size
* startpc - pc at the start of the segment
*
****************************************************************
*
ProcessHeader private
using Common
using OutCommon
resspc equ $04 disp to reserved space
length equ $08 disp to code length
lablen equ $0D disp to label length
numlen equ $0E disp to number length
version equ $0F disp to the segment version
banksize equ $10 disp to bank size
s0type equ $0C disp to segment type
s0org equ $14 disp to org
s0align equ $18 disp to alignment factor
s0numsex equ $1C disp to the number type
s1type equ $0C disp to segment type
s1org equ $18 disp to org
s1numsex equ $20 disp to the number type
s1entry equ $24 disp to segment entry
s1dispname equ $28 disp to the name displacement
s1dispdata equ $2A disp to the data displacement
s1align equ $1C disp to alignment factor
s2type equ $14 disp to segment type
s2org equ $18 disp to org
s2numsex equ $20 disp to the number type
s2entry equ $24 disp to segment entry
s2dispname equ $28 disp to the name displacement
s2dispdata equ $2A disp to the data displacement
s2temporg equ $2C disp to temporg
s2align equ $1C disp to alignment factor
;
; Do processing common to all segments
;
ldy #resspc get the reserved space
lda [seg],Y
sta segSpace
iny
iny
lda [seg],Y
sta segSpace+2
ldy #length get the length of the code
lda [seg],Y
sta segLength
iny
iny
lda [seg],Y
sta segLength+2
ldy #banksize get the bank size
lda [seg],Y
sta segBanksize
iny
iny
lda [seg],Y
sta segBanksize+2
ldy #lablen make sure names are pstrings
lda [seg],Y
and #$00FF
bne vc2
ldy #numlen make sure numbers are 4 bytes long
lda [seg],Y
and #$00FF
cmp #4
beq vt0
vc2 lda #4 flag an illegal header value error
jmp TermError
;
; Handle a version 2 header
;
vt0 ldy #version get the segment version number
lda [seg],Y
and #$00FF
sta segVersion
cmp #2 branch if not version 2
jne vo1
ldy #2 get the length of the segment
lda [seg]
sta segDisp
lda [seg],Y
sta segDisp+2
ldy #s2type get the segment type
lda [seg],Y
sta segType
ldy #s2org get the org
lda [seg],Y
sta segOrg
iny
iny
lda [seg],Y
sta segOrg+2
ldy #s2align get the alignment factor
lda [seg],Y
sta segAlign
iny
iny
lda [seg],Y
sta segAlign+2
ldy #s2entry get the entry disp
lda [seg],Y
sta segEntry
iny
iny
lda [seg],Y
sta segEntry+2
ldy #s2dispdata get the disp to the first op code byte
lda [seg],Y
clc
adc seg
sta sp
lda seg+2
adc #0
sta sp+2
ldy #s2dispname get a pointer to the segment name
lda [seg],Y and find the proper load segment
clc
adc seg
sta segName
lda seg+2
adc #0
sta segName+2
move4 segName,loadNamePtr
add4 segName,#10
jsr AutoSegment
jsr FindLoadSegment
ldy #s2numsex verify that numsex = 0
lda [seg],Y
and #$00FF
beq vt1
lda #4
jmp TermError
vt1 lda pass if this is pass 2 then
cmp #2
jne vf1
ldy #s2dispname skip check if disp to names < $30
lda [seg],Y
cmp #$30
jlt vf1
ldy #s2temporg flag temporg errors
lda [seg],Y
iny
iny
ora [seg],Y
jeq vf1
ph4 #0
ph2 #12
jsr Error
brl vf1
;
; Handle a version 1 header
;
vo1 cmp #1 branch if not version 1
jne vz1
ldy #2 get the length of the segment
lda [seg]
sta segDisp+1
lda [seg],Y
short M
stz segDisp
sta segDisp+3
long M
asl segDisp
rol segDisp+2
ldy #s1type get the segment type
lda [seg],Y
and #$00FF
pha
and #$001F
sta segType
pla
xba
and #$E000
ora segType
sta segType
ldy #s1org get the org
lda [seg],Y
sta segOrg
iny
iny
lda [seg],Y
sta segOrg+2
ldy #s1align get the alignment factor
lda [seg],Y
sta segAlign
iny
iny
lda [seg],Y
sta segAlign+2
ldy #s1entry get the entry disp
lda [seg],Y
sta segEntry
iny
iny
lda [seg],Y
sta segEntry+2
ldy #s1dispdata get the disp to the first op code byte
lda [seg],Y
clc
adc seg
sta sp
lda seg+2
adc #0
sta sp+2
ldy #s1dispname get a pointer to the segment name
lda [seg],Y and find the proper load segment
clc
adc seg
sta segName
lda seg+2
adc #0
sta segName+2
move4 segName,loadNamePtr
add4 segName,#10
jsr AutoSegment
jsr FindLoadSegment
ldy #s1numsex verify that numsex = 0
lda [seg],Y
and #$00FF
jeq vf1
lda #4
jmp TermError
brl vf1
;
; Handle a version 0 header
;
vz1 cmp #0 branch if not version 0
jne ve1
ldy #2 get the length of the segment
lda [seg]
sta segDisp+1
lda [seg],Y
short M
stz segDisp
sta segDisp+3
long M
asl segDisp
rol segDisp+2
ldy #s0type get the segment type
lda [seg],Y
and #$00FF
pha
and #$001F
sta segType
pla
xba
and #$E000
ora segType
sta segType
ldy #s0org get the org
lda [seg],Y
sta segOrg
iny
iny
lda [seg],Y
sta segOrg+2
ldy #s0align get the alignment factor
lda [seg],Y
sta segAlign
iny
iny
lda [seg],Y
sta segAlign+2
stz segEntry get the entry disp
stz segEntry+2
add4 seg,#$24,segName get a pointer to the segment name
move4 segName,r0 get the disp to the first op code byte
lda [r0]
and #$00FF
sec
adc segName
sta sp
lda segName+2
adc #0
sta sp+2
ldy #s0numsex verify that numsex = 0
lda [seg],Y
and #$00FF
beq vz2
lda #4
jmp TermError
vz2 lla loadNamePtr,blankSeg find the proper load segment
jsr FindLoadSegment
bra vf1
;
; Segment version error
;
ve1 lda #3
jmp TermError
;
; Do common end processing
;
vf1 stz dataNumber data area number is 0 for code files
lda segType if this is a data area then
and #$00FF
cmp #1
bne vf2
inc lastDataNumber assign a data area number
lda lastDataNumber
sta dataNumber
vf2 move4 pc,startpc record the pc
sec
rts
;
; Local data
;
blankSeg dc 10c' ' default load segment name
end
****************************************************************
*
* RootName - Append .root to file name
*
* inputs:
* basename - base file name
*
* outputs:
* ckname - current keep file name
* tkname - .root appended to contents of kname
* kltr - suffix letter for the main obj file
*
****************************************************************
*
RootName private
using SegCommon
ph4 fname free old buffer
jsr Free
lda [basename] get new buffer
clc
adc #2+l:root
pea 0
pha
jsr MLalloc
sta fname
stx fname+2
sta r4 copy basename to fname
stx r6
move4 basename,r0
jsr MoveName
lda [fname] append root to the name
tay
clc
adc #l:root
sta [fname]
iny
iny
ldx #0
phy
short M
rn1 lda root,X
sta [fname],Y
iny
inx
cpx #l:root
bne rn1
long M
ph4 fname if not exists(fname) then
jsr ExistsM
ply
tax
bne ret
short M
ldx #1
iny
rn2 lda root,X uppercase suffix
and #$DF
sta [fname],Y
iny
inx
cpx #l:root
bne rn2
long M
ret rts
root dc c'.root'
end