home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Columbia Kermit
/
kermit.zip
/
a
/
mload.asm
< prev
next >
Wrap
Assembly Source File
|
2020-01-01
|
37KB
|
1,494 lines
title 'MLOAD MULTI-FILE HEX LOAD UTILITY'
;
; *********************************
; * MLOAD.ASM *
; * Multi-file Hex Load Utility *
; * for CP/M *
; *********************************
;
;
; Replacement for the cp/m "LOAD" program: this program
; fixes many of the problems associated with the "CP/M"
; load program, and adds many new features.
;
; ----------------
;
; Rev 2.5
; 03/10/88
; Property of NightOwl Software, Inc. Fort Atkinson, WI 53538
; Written by Ron Fowler, Nightowl Software, Inc.
;
; ----------------
; Notice: this program is NOT public domain; copyright is retained by
; NightOwl Software, Inc. of Fort Atkinson, WI ... All Rights Reserved.
;
; License is granted for free use and re-distribution this program, as
; long as such use and re-distribution is done without profit.
;
; ----------------
;
; modification history:
;
; 2.5 (WOD) This version corrects a bug that overlayed the first six
; bytes of the CCP. The error did not show up unless a
; jump to the CCP was done without a warm boot since MLOAD
; used. This source file has been modified here with
; concurrence of the author of MLOAD, Ron Fowler.
;
; 2.4 (RGF) We apologize for this relatively insubstantial update,
; but someone has caused what we consider to be a problem,
; by making changes to the program, and re-releasing under
; the same version number. The changes in this case were
; conversion of the opcode fields (but not the comments,
; can you believe that??) of every line to upper case! That
; totally invalidated the CRC of the source file, since there
; are now two different MLOAD 2.3's running around.
;
; We DO NOT want these stupid mixed upper/lower case changes.
; Someone somewhere has decided that this is the way assembly
; language source should be, and we most VEHEMENTLY disagree.
; It's a pain in the neck to make changes to and we don't
; care to run our programs through conversion programs every
; time we make changes.
;
; So ... leave the case of this file AS IS. Any changes made
; to this program and not co-ordinated through us may very
; well endanger availability of source code when we make
; future updates. 'nuff said --NightOwl Software
;
; 2.3 (RGF) Trivial cosmetic changes
; 2.2 (RGF) Modified copyright notice to show new owner of the
; program.
; 2.1 (RGF) Fixed problem on disk-full when writing output file
; (mload previously didn't error out on a full disk)
; 2.0 (RGF) Added the ability to pre-load a non-hex file, allowing
; mload to be used to load hex file patches (obviating any
; need to use DDT). The normal mload syntax is preserved.
; the first (and only the first) filespec (after the "=",
; if used) may be non-hex; the filetype must be specified.
; Examples:
;
; 1) mload ws.com,wspatch
; 2) mload MEXTEST=MEX112.COM,MXO-US13
; 3) mload ws.ovr,ovrpatch
;
; The first example loads WS.COM, overlays it with
; wspatch.hex, and writes the output to WS.COM. The
; second example loads MEX112.COM, overlays it with
; MXO-US13.HEX, and writes the output file to MEXTEST.COM.
; (note that the second example is the recommended technique,
; since it preserves the original file). The third example
; loads WS.OVR and patches it with the file "OVRPATCH.HEX".
;
; Also added this rev: ZCPR2-style du specs are now fully
; supported, for both input and output files. Thus, the
; following command lines are permissable:
;
; b3>mload a4:myfile.com=0:bigfil,b6:patch1,c9:patch2
; a6>mload b5:=c3:mdm717.com,mdmpatch
;
; After loading, an additional information line is now printed
; in the statistics report, which displays the true size of the
; saved image (the previous report was technically correct, but
; could result in confusion for certain kinds of files with
; imbedded "DS" and "ORG" statements in the original source code).
;
; 1.0 - 1.4 (RGF) change log removed to conserve space
;
; originally written by ron fowler, fort atkinson, wisconsin
;
;
;
; For assembly with asm.com or mac (delete above title line if
; assembling with asm.com).
;
; This program is a replacement for the cp/m "LOAD" program.
; Why replace "LOAD"? well... LOAD.COM has a few deficiencies.
; For example, if your hex file's origin is above 100h, LOAD.COM
; prepends blank space to the output file to insure it will work
; as a CP/M transient. It cares not if the file is not intended
; as a CP/M transient. it also doesn't like hex records with mixed
; load addresses (for example, one that loads below a previous record --
; which is a perfectly legitimate happenstance). Also, LOAD.COM
; can load only one program at a time, and has no provision for
; a load bias in the command specification. Finally, there is no
; provision for user specification of the output file name.
;
;
; Hence, this program....
;
;------------------------------------------------------------
;
; Syntax is as follows:
;
; mload [<outnam=] <filename>[,<filename>...] [bias]
;
; where <outnam> is the (optional!;) output file name (only the drive
; spec and primary filename may be specified; the output filetype is
; derived exclusively from the 3-byte string at 103h within MLOAD),
; <filename> specifies files to load and <bias> is the offset within
; the saved image to apply when loading the file.
;
; MLOAD with no arguments prints a small help message -- this message
; is also printed whenever a command line syntax error occurs.
;
; Filenames may contain drive/user specs, and must not contain wildcards.
; Input filenames must be separated by commas, and a space is required
; between the last filename and the optional bias.
;
; A load information summary is printed at the successful conclusion
; of the load. Any errors in loading will generally include the name
; of the file in question.
;
; If no output filename is specified, it will be derived from the first
; input filename, with filetype of 'COM', if not otherwise specified
; (this default filetype may be patched directly into mload via DDT
; (or with MLOAD itself, using a patch file) -- its location is at 103H
; in MLOAD.COM). Note that a command line of the form "C:=<FILENAME>"
; will place the output file on the "C" drive with the same primary
; filename as the input file.
;
; In its simplest form, MLOAD's syntax is identical to LOAD.COM; thus
; there should be no problem in learning to use the new program. The
; only significant difference here is that, under LOAD.COM, all files
; are output starting at 100h, even if they originate elsewhere. MLOAD
; outputs starting at the hex file origin (actually, the first hex rec-
; ord specifies the output load address). The bias option may be used
; to override this.
;
; An example should clarify this. Suppose you have a file that loads
; at 1000h. LOAD.COM would save an output file that begins at 100h and
; loads past 1000h (to wherever the program ends). MLOAD will save an
; output file starting from 1000h only. If, for some reason you need the
; file to start at 100h in spite of its 1000h origin (i can think of sev-
; eral circumstances where this would be necessary), you'd have to specify
; a bias to mload. thus, using this example, "MLOAD MYFILE 0F00" would do.
;
; Note that this program re-initializes itself each time it is run.
; Thus, if your system supports a direct branch to the tpa (via a zero-length
; .COM file, or the ZCPR "GO" command), you may safely re-execute MLOAD.
;
; Please report any bugs, bug fixes, or enhancements to
;
; "FORT FONE FILE FOLDER" rcpm/cbbs
; Fort Atkinson, Wisconsin
; (414) 563-9932 (no ring back)
;
; --Ron Fowler
; 03/08/84 updated 1/31/85
;
;------------------------------------------------------------
;
; CP/M equates
;
warmbt equ 0 ;warm boot
system equ 5 ;system entry (also top of mem pntr)
dfcb equ 5ch ;default file control block
ft equ 9 ;fcb offset to filetype
tbuf equ 80h ;default buffer
tpa equ 100h ;transient program area
eof equ 1ah ;cp/m end-of-file mark
fcbsiz equ 33 ;size of file control block
;
; CP/M system calls
;
pcharf equ 2 ;print char
seldf equ 14 ;select disk drive
openf equ 15 ;open file
closef equ 16 ;close file
fsrchf equ 17 ;search for first
fsrchn equ 18 ;search for next
erasef equ 19 ;delete file
readf equ 20 ;read record
writef equ 21 ;write record
creatf equ 22 ;create file
getdrf equ 25 ;return dflt drive #
sdmaf equ 26 ;set dma address
gsuser equ 32 ;get/set user #
rrand equ 33 ;read random
wrand equ 34 ;write random
filszf equ 35 ;compute file size
srand equ 36 ;set random
;
; ASCII character constants
;
cr equ 13
lf equ 10
bel equ 7
tab equ 9
;
; without further ado...
;
org tpa
;
jmp begin ;jump over default output filetype
;
; the default output filetype is located at 103h for easy patching
;
outtyp: db 'COM'
;
begin: lxi h,0 ;save system stackpointer
dad sp
shld spsave
lxi sp,stack ;load local stack
call ilprnt ;sign on
db 'MLOAD ver. 2.5 Copyright (C) 1983, 1984, 1985'
db cr,lf
db 'by NightOwl Software, Inc.'
db cr,lf,0
call setup ;initialize
main: call nxtfil ;parse and read next input file
jc done ;no more...
call lodfil ;yep, load it
call closfl ;close it (in case MP/M or TurboDOS)
jmp main ;maybe more
done: call wrtfil ;write the output file
;
; exit to cp/m
;
exit: lxi d,tbuf ;restore dma address
mvi c,sdmaf
call bdos
lda system+2 ;get top of memory pointer
sui 9 ;allow for ccp+slop
lxi h,hiload+1 ;highest load address
sub m ;above ccp?
jc warmbt ;then warm-boot
lhld spsave ;nope, ccp still in memory
sphl ;restore its stack
ret ;return to ccp
;
; load program initialization
;
setup: lxi h,varset ;initialize variables
lxi d,vars
mvi b,varlen ;by moving in default values
call move
lhld cmdptr ;get first free mem pointer
xchg ;in de
lxi h,tbuf ;point to command tail bufr
mov a,m ;get its length
inx h
ora a ;does it have any length?
jz help ;nope, go give usage help
mov b,a ;yep, get length to b
call move ;move cmd tail to buffer
xchg ;end of dest to hl
mvi m,0 ;stuff a terminator
inx h ;point to first free memory
shld filbuf ;set up file buffer
xchg ;file bufr adrs to de
lhld system+1 ;get top of memory pointer
xra a ;round system to page boundary
sub e
mov c,a ;with result in bc
mov a,h
sui 9 ;allow for ccp
sbb d
mov b,a
xchg ;buffer pointer to hl
nitmem: mvi m,0 ;clear buffer
inx h
dcx b
mov a,b
ora c
jnz nitmem
;
; look for a bias specification in command line
;
lhld cmdptr ;point to command buffer-1
dcx h
call scanbk ;scan past blanks
ora a ;no non-blank chars?
jz help ;then go print help text
fndspc: inx h ;point to next
mov a,m ;fetch it
ora a ;test it
rz ;line ended, return
cpi ' ' ;nope, test for blank
jnz fndspc ;not blank, continue
call scanbk ;skip blanks
ora a ;end-of-line?
rz ;return if so
;
; hl points to bias in command line
;
lxi d,0 ;init bias
call hexdig ;insure a hex digit
jc synerr ;bad...
hexlp: mov a,m ;no. get next char
inx h ;skip over it
call hexdig ;test for hex digit
jnc digok ;jump if good hex digit
ora a ;must end on null terminator
jnz synerr
xchg ;good end, get bias to hl
shld bias ;stuff it
ret ;done
digok: xchg ;bias to hl
dad h ;skift left 4 to make room
dad h ; for new hex digit
dad h
dad h
xchg ;back to de
add e ;add in new digit
mov e,a
jnc hexlp ;jump if no 8-bit ovfl
inr d ;carry
jmp hexlp
;
; parse next input name, and open resultant file
;
nxtfil: lhld cmdptr ;get command line pointer
next2: lxi d,dfcb ;destination fcb
call fparse ;parse a filename
cpi '=' ;stopped on output specifier?
jnz noteq
lda outnam+2 ;insure no name yet specified
cpi ' '
jnz synerr ;syntax error if already named
lda outflg ;already been here?
ora a
jnz synerr ;can't be here twice
inr a ;flag that we've been here
sta outflg
inr b ;insure no ambiguous output name
dcr b
jnz afnerr
inx h ;skip over '='
push h ;save cmd line pointer
lxi h,dfcb-1 ;move the name to output name hold
lxi d,outnam
mvi b,13 ;drive spec too
call move
pop h ;restore command line pointer
jmp next2 ;go parse another
noteq: cpi ',' ;stopped on comma?
jz gcomma ;jump if so
mvi m,0 ;nope, insure end of input
jmp nxt2 ;don't advance over fake end
gcomma: inx h ;skip over comma
nxt2: shld cmdptr ;save new command line pntr
mov a,b ;get ambig char count
ora a ;test it
jnz afnerr ;allow no ambig characters
sta bufptr ;force a disk read
lxi d,dfcb+1 ;look at parsed filename
ldax d
cpi ' ' ;blank? (input ended?)
stc ;get carry ready in case so
rz ;return cy if input gone
dcx d ;nope, point de to start of fcb
open2: push d ;try to open the file
mvi c,openf
call bdos
pop d
inr a ;return=0ffh?
jnz openok ;jump if not
;
; file not found: if filetype blank, set to 'hex' and try again
;
lxi h,dfcb+ft ;point to file type
mov a,m ;anything there?
cpi ' '
jnz fnferr ;yes, so file not found
mvi m,'H' ;nope, fill in 'hex'
inx h
mvi m,'E'
inx h
mvi m,'X'
jmp open2 ;go try again
;
; here after a good file open
;
openok: call hexchk ;is this a hex file?
rz ;if so, all done
lxi h,comflg ;no, get pointer to flag
mov a,m ;loading first file?
ora a
rnz ;if not, ignore type, consider hex
inr m ;else, set the flag
ret
;
; load current file
;
lodfil: lxi h,comflg ;loading a com file?
mov a,m ;get flag
ani 1
jnz lodcom ;jump if so
lhld bias ;else get bias on top of stack
push h
;
; load a hex record
;
loadlp: call gnb ;get next file byte
sbi ':' ;look for start-record mark
jnz loadlp ;scan until found
sta cksum ;got it, init checksum to zero
mov d,a ;upper byte of rec cnt=0
pop b ;retrieve bias adrs
push b ;save it again
call ghbcks ;get hex byte w/checksum
mov e,a ;de now has record length
ora a ;test it
jnz notend ;jump if len<>0 (not eof rec)
pop h ;all done
ret
notend: call ghbcks ;hi byte of rec ld adrs
mov h,a ;accumulate in hl
call ghbcks ;get lo byte
mov l,a ;put lo in l
lda lodflg ;test load flag
ora a
cz lodnit ;not first record, initialize
push h ;save load address
dad d ;add in record length
dcx h ;make highest, not next
lda hipc ;a new high?
sub l
lda hipc+1
sbb h
jnc notgt ;jump if not
shld hipc ;yep, update hipc
push d ;save reclen
xchg ;load adrs to de
lhld offset ;get offset to form true memory adrs
dad d ;add in offset
dad b ;and bias
shld hiload ;mark highest true memory load adrs
lda system+2 ;validate against top-mem pointer
cmp h
jc memful ;jump if out of memory
pop d ;restore reclen
notgt: pop h ;restore load address
dad b ;add bias to load adrs
push d ;save record length
push h
lhld bytcnt ;add record length to byte count
dad d
shld bytcnt
pop h
xchg
lhld offset ;calculate true memory adrs
dad d ;hl=true loading adrs
pop d ;restore record length
call ghbcks ;skip unused byte of intel format
;
; move the record into memory
;
reclp: call ghbcks ;get hex byte
mov m,a ;store it in buffer
inx h ;point to next
dcr e ;count down
jnz reclp ;until record all read
call ghbcks ;get checksum byte
jnz cserr ;final add cksum should sum 0
jmp loadlp ;good load, go do nxt record
;
; get next hex byte from input, and
; accumulate a checksum
;
ghbcks: push b ;save em all
push h
push d
call hexin ;get hex byte
mov b,a ;save in b
lxi h,cksum ;add to checksum
mov a,m
add b
mov m,a
mov a,b ;get byte back
pop d ;restore checksum
pop h ;restore other regs
pop b
ret
;
; routine to get next byte from input...forms
; byte from two ascii hex characters
;
hexin: call gnb ;get next input file byte
call hexval ;convert to binary w/validation
rlc ;move into ms nybble
rlc
rlc
rlc
ani 0f0h ;kill possible garbage
push psw ;save it
call gnb ;get next byte
call hexval ;convert it, w/validation
pop b ;get back first
ora b ;or in second
ret ;good byte in a
;
; gnb - utility subroutine to get next
; byte from disk file
gnb: push h ;save all regs
push d
push b
lda bufptr ;get input bufr pointer
ani 7fh ;wound back to 0?
jz diskrd ;go read sector if so
gnb1: mvi d,0 ;else form 16 bit offset
mov e,a
lxi h,tbuf ;from tbuf
dad d ;add in offset
mov a,m ;get next byte
cpi eof ;end of file?
jz eoferr ;error if so
lxi h,bufptr ;else bump buf ptr
inr m
ora a ;return carry clear
pop b ;restore and return
pop d
pop h
ret
;
; read next sector from disk
;
diskrd: mvi c,readf ;bdos "READ SEC" function
lxi d,dfcb
call bdos ;read sector
ora a
jnz eoferr ;error if phys end of file
sta bufptr ;store 0 as new buf ptr
jmp gnb1 ;go re-join gnb code
;
; load a com file
;
lodcom: inr m ;bump the comfile flag
lxi h,tpa ;set origin
call lodnit ;and initialize
xchg ;load address in de
lhld bias ;add in bias
dad d
xchg
lhld offset ;and offset
dad d
xchg ;de has absolute mem adrs of load
;
comlp: lxi h,128 ;calculate next dma
dad d
lda system+2 ;check for space
cmp h
jc memful ;jump if none
push h ;else save next dma
push d ;and this dma
mvi c,sdmaf ;set this dma
call bdos
lxi d,dfcb ;read next record
mvi c,readf
call bdos
pop h ;recall this dma
pop d ;de=next dma
ora a ;end of read?
jnz lodend ;jump if so
lhld comsiz ;no, advance com byte count
lxi b,128
dad b
shld comsiz
jmp comlp ;continue
;
lodend: dcx h ;one less byte is highest
shld hiload ;set a new high
lhld comsiz ;hi pc=bytecount+100h
lxi d,tpa
dad d
xchg ;to de
lhld bias ;add in bias
dad d
shld hipc
lxi d,tbuf ;reset dma for hex files
mvi c,sdmaf
call bdos
ret
;
; write output file
;
wrtfil: lxi d,dfcb ;point to fcb
push d ;save 2 copies of pointer
push d
call nitfcb ;initialize output fcb
lxi h,outnam ;move output name in
dcx d ;point to user # (prior to fcb)
mvi b,10 ;move user, drive, primary name
call move
mov a,m ;output type blank?
cpi ' '
jnz wrtnb ;jump if not
lxi h,outtyp ;yes, move dflt output filetype in
wrtnb: mvi b,3
call move
pop d ;restore fcb pointer
mvi c,erasef ;erase any existing file
call bdos
pop d ;restore fcb pointer
mvi c,creatf ;create a new file
call bdos
inr a ;good create?
jz dirful ;goto directory full error if not
lhld hiload ;yep, get top of bufr pntr
xchg ;in de
lhld filbuf ;get start of bufr adrs
mov a,e ;calculate output file size
sub l
mov c,a ;with result in bc
mov a,d
sbb h
mov b,a
mov a,b ;test length
ora c
jz loderr ;nothing to write???
lxi d,dfcb ;get fcb pointer
wrlp: push b ;save count
push d ;and fcb pointer
xchg ;get memory pointer to de
lxi h,128 ;add in sector length for next pass
dad d
xthl ;save next dma
push h ;above fcb
mvi c,sdmaf ;set transfer address
call bdos
pop d ;fetch fcb pointer
push d ;save it again
mvi c,writef ;write a sector
call bdos
ora a ;test result
jnz dskful ;disk full error...
lhld reccnt ;no,increment count of records
inx h
shld reccnt
pop d ;restore fcb pointer
pop h ;and memory write pointer
pop b ;and count
mov a,c ;subtract 128 (sec size) from count
sui 128
mov c,a
jnc wrlp ;jump if some left
mov a,b ;hi-order borrow
sui 1 ;do it (can't "DCR B", doesn't affect cy)
mov b,a ;restore
jnc wrlp ;jump if more left
call closfl ;close output file
;
; report statistics to console
;
call ilprnt
db 'Loaded ',0
lhld bytcnt ;print # bytes
call decout
call ilprnt
db ' bytes (',0
call hexout
call ilprnt
db 'H)',0
call ilprnt
db ' to file %',0
lda comflg ;did we load a comfile too?
ora a
jz notcom ;jump if not
call ilprnt
db cr,lf,'Over a ',0
lhld comsiz
call decout
call ilprnt
db ' byte binary file',0
notcom: call ilprnt
db cr,lf,'Start address: ',0
lhld lodadr ;print loading address
call hexout
call ilprnt
db 'H Ending address: ',0
lhld hipc ;print ending load address
call hexout
call ilprnt
db 'H Bias: ',0
lhld bias
call hexout
call ilprnt
db 'H',cr,lf,0
call ilprnt
db 'Saved image size: ',0
lhld reccnt ;get count of image records
push h ;save it
mvi b,7 ;convert to bytes
xlp: dad h
dcr b
jnz xlp
call decout ;print it
call ilprnt
db ' bytes (',0
call hexout ;now in hex
call ilprnt
db 'H, - ',0
pop h ;recall record count
call decout ;print it
call ilprnt
db ' records)',cr,lf,0
lhld lodadr ;fetch loading address
mov a,l ;test if =tpa
ora a
jnz nottpa ;tpa always on page boundary
mov a,h ;lo ok, test hi
cpi (tpa shr 8) and 0ffh
rz ;return if tpa
nottpa: call ilprnt ;not, so print warning msg
db cr,lf,bel
db '++ Warning: program origin NOT at 100H ++'
db cr,lf,0
ret ;done
;
; ***********************
; * utility subroutines *
; ***********************
;
;
; routine to close any open file
;
closfl: lxi d,dfcb
mvi c,closef
call bdos
inr a ;test close result
jz clserr ;jump if error
ret
;
; print message in-line with code
;
ilprnt: xthl ;message pntr to hl
call prathl ;print it
xthl ;restore and return
ret
;
; print msg pointed to by hl until null. expand
; '%' char to current filename.
;
prathl: mov a,m ;fetch char
inx h ;point to next
ora a ;terminator?
rz ;then done
cpi '%' ;want filename?
jz prtfn ;go do it if so
call type ;nope, just print char
jmp prathl ;continue
;
prtfn: push h ;save pointer
push b
lda dfcb ;fetch dr field of dfcb
ora a ;default drive?
jnz prndf ;jump if not
call getdsk ;get logged-in drive #
inr a ;make it one-relative (as in fcb)
prndf: adi 'A'-1 ;make drive name printable
call type ;print it
lda dfcb-1 ;get user #
cpi 0ffh ;null?
cz getusr ;iff so, get current user
mov l,a ;to hl
mvi h,0
call decout ;print it
mvi a,':' ;drive names followed by colon
call type
lxi h,dfcb+1 ;setup for name
mvi b,8 ;print up to 8
call prtnam
mvi a,'.' ;print dot
call type
mvi b,3 ;print filetype field
call prtnam
pop b
pop h ;restore and continue
jmp prathl
;
; print file name .HL max length in b. don't print spaces
;
prtnam: mov a,m ;fetch a char
cpi ' ' ;blank?
jz pwind ;go wind if so
inx h ;nope, move to next
call type ;print it
dcr b ;count down
jnz prtnam ;continue
ret
pwind: inx h ;skip remainder of blank name
dcr b
jnz pwind
ret
;
; print HL in decimal on console
;
decout: push h ;save everybody
push d
push b
lxi b,-10 ;conversion radix
lxi d,-1
declp: dad b
inx d
jc declp
lxi b,10
dad b
xchg
mov a,h
ora l
cnz decout ;this is recursive
mov a,e
adi '0'
call type
pop b
pop d
pop h
ret
;
; newline on console
;
crlf: mvi a,cr
call type
mvi a,lf
jmp type
;
; print hl on console in hex
;
hexout: mov a,h ;get hi
call hexbyt ;print it
mov a,l ;get lo, fall into hexbyt
;
; type accumulator on console in hex
;
hexbyt: push psw ;save byte
rar ;get ms nybble..
rar ;..into lo 4 bits
rar
rar
call nybble
pop psw ;get back byte
nybble: ani 0fh ;mask ms nybble
adi 90h ;add offset
daa ;decimal adjust a-reg
aci 40h ;add offset
daa ;fall into type
;
; type char in a on console
;
type: push h ;save all
push d
push b
mov e,a ;cp/m outputs from e
mvi c,pcharf
call bdos
pop b
pop d
pop h
ret
;
; move: from @hl to @de, count in b
;
move: inr b ;up one
movlp: dcr b ;count down
rz ;return if done
mov a,m ;not done, continue
stax d
inx h ;pointers=pointers+1
inx d
jmp movlp
;
; scan to first non-blank char in string @hl
;
scanbk: inx h ;next
mov a,m ;fetch it
cpi ' '
jz scanbk
ret
;
; get hex digit and validate
;
hexval: call hexdig ;get hex digit
jc formerr ;jump if bad
ret
;
; get hex digit, return cy=1 if bad digit
;
hexdig: cpi '0' ;lo boundary test
rc ;bad already?
cpi '9'+1 ;no, test hi
jc hexcvt ;jump if numeric
cpi 'A' ;test alpha
rc ;bad?
cpi 'F'+1 ;no, upper alpha bound
cmc ;pervert carry
rc ;bad?
sui 7 ;no, adjust to 0-f
hexcvt: ani 0fh ;make it binary
ret
;
; ******************
; * error handlers *
; ******************
;
synerr: call crlf
call ilprnt
db ' Command line syntax error',cr,lf,cr,lf,0
jmp help ;give help msg too
;
afnerr: call errxit ;exit with message
db 'Ambiguous file name: % not allowed.',0
;
fnferr: call errxit
db 'File % not found.',0
;
dskful: call errxit
db 'Disk full.',0
;
dirful: call errxit
db 'Directory full.',0
;
eoferr: call errxit
db 'Premature end-of-file in %',0
;
cserr: call errxit
db 'Checksum error in %',0
;
clserr: call errxit
db 'Can''t close %',0
;
memful: call errxit
db 'Memory full while loading %',0
;
formerr:call errxit
db 'Format error in file %',0
;
loderr: call errxit
db 'Writing %, nothing loaded',0
;
help: call errxit ;print help text
db 'MLOAD syntax:',cr,lf,cr,lf
db 'MLOAD [<OUTFIL>=] <FILE1>[,<FILE2>...] [<BIAS>]',cr,lf
db tab,' (brackets denote optional items)',cr,lf,cr,lf
db tab,'<OUTFIL> is the optional output filename',cr,lf
db tab,'<FILEn> are input file(s)',cr,lf
db tab,'<BIAS> is a hex load offset within the output file'
db cr,lf,cr,lf
db tab,'<FILE1> may be an optional non-HEX file to be patched',cr,lf
db tab,'by subsequently named HEX files (specifying',cr,lf
db tab,'The filetype enables this function).'
db cr,lf,cr,lf
db 'Note that ZCPR2-style drive/user notation may be used in all'
db cr,lf
db 'file specifications (e.g., "B3:MYFILE.COM, "A14:MDM7.HEX").'
db cr,lf,0
;
; general error handler
;
errxit: call crlf ;new line
pop h ;fetch error msg pointer
call prathl ;print it
call crlf
jmp exit ;done
;
; initialize load parameters
;
lodnit: mvi a,1 ;first record, set load flag
sta lodflg
shld lodadr ;save load address
shld hipc ;and hi load
push d ;save record length
xchg ;de=load address
lhld filbuf ;get address of file buffer
mov a,l ;subtract load adrs from file buffer
sub e
mov l,a
mov a,h
sbb d
mov h,a
shld offset ;save as load offset
push d ;save load address on stack
push b ;save bias
lxi d,outnam+2 ;check output filename
ldax d ;(first char)
cpi ' '
jnz namskp ;jump if so
lxi h,dfcb+1 ;get first name pointer
mvi b,8 ;(don't include drive spec)
call move
;
; check for outflg=1 (presence of an "="). note that the
; filename may well be blank, and yet outflg <>0, for example
; in the case of "A:=<FILENAME>" or "C4:=<FILENAME>". in
; this case, we want to remember the drive/user specified, but
; use the first input file to form the output name. otherwise,
; we use the current drive/user.
;
lda outflg ;was there an "="?
ora a
jnz namskp ;jump if so
lxi h,outnam ;get destination pointer
call getusr ;get current user #
mov m,a
inx h ;point to drive
call getdsk ;get it
inr a ;fcb's drive is 1-relative
mov m,a
namskp: mvi a,1 ;insure "=" cannot occur anymore
sta outflg
pop b ;restore bias
pop h ;load address to hl
pop d ;restore record length
ret
;
; *********************************
; * file name parsing subroutines *
; *********************************
;
; credit where credit's due:
; --------------------------
; these routines were lifted from bob van valzah's
; "FAST" program.
;
;
;
; *********************************
; * file name parsing subroutines *
; *********************************
;
;
; getfn gets a file name from text pointed to by reg hl into
; an fcb pointed to by reg de. leading delimeters are
; ignored. allows drive spec of the form <du:> (drive/user).
; this routine formats all 33 bytes of the fcb (but not ran rec).
;
; entry de first byte of fcb
; exit b=# of '?' in name
; fcb-1= user # parsed (if specified) or 255
;
;
fparse: call nitfcb ;init 1st half of fcb
call gstart ;scan to first character of name
call getdrv ;get drive/user spec. if present
mov a,b ;get user # or 255
cpi 0ffh ;255?
jz fpars1 ;jump if so
dcx d ;back up to byte preceeding fcb
dcx d
stax d ;stuff user #
inx d ;onward
inx d
fpars1: call getps ;get primary and secondary name
ret
;
; nitfcb fills the fcb with dflt info - 0 in drive field
; all-blank in name field, and 0 in ex,s1,s2,rc, disk
; allocation map, and random record # fields
;
nitfcb: push h
push d
call getusr ;init user field
pop d
pop h
push d ;save fcb loc
dcx d
stax d ;init user # to currnt user #
inx d
xchg ;move it to hl
mvi m,0 ;drive=default
inx h ;bump to name field
mvi b,11 ;zap all of name fld
nitlp: mvi m,' '
inx h
dcr b
jnz nitlp
mvi b,33-11 ;zero others, up to nr field
zlp: mvi m,0
inx h
dcr b
jnz zlp
xchg ;restore hl
pop d ;restore fcb pointer
ret
;
; gstart advances the text pointer (reg hl) to the first
; non delimiter character (i.e. ignores blanks). returns a
; flag if end of line (00h or ';') is found while scaning.
; exit hl pointing to first non delimiter
; a clobbered
; zero set if end of line was found
;
gstart: call getch ;see if pointing to delim?
rnz ;nope - return
ora a ;physical end?
rz ;yes - return w/flag
inx h ;nope - move over it
jmp gstart ;and try next char
;
; getdrv checks for the presence of a du: spec at the text
; pointer, and if present formats drive into fcb and returns
; user # in b.
;
; entry hl text pointer
; de pointer to first byte of fcb
; exit hl possibly updated text pointer
; de pointer to second (primary name) byte of fcb
; b user # if specified or 0ffh
;
getdrv: mvi b,0ffh ;default no user #
push h ;save text pointer
dscan: call getch ;get next char
inx h ;skip pointer over it
jnz dscan ;scan until delimiter
cpi ':' ;delimiter a colon?
inx d ;skip dr field in fcb in case not
pop h ;and restore text pointer
rnz ;return if no du: spec
mov a,m ;got one, get first char
call cvtuc ;may be drive name, cvt to upper case
cpi 'A' ;alpha?
jc isnum ;jump to get user # if not
sui 'A'-1 ;yes, convert from ascii to #
dcx d ;back up fcb pointer to dr field
stax d ;store drive # into fcb
inx d ;pass pointer over drv
inx h ;skip drive spec in text
isnum: mov a,m ;fetch next
inx h
cpi ':' ;du delimiter?
rz ;done then
dcx h ;nope, back up text pointer
mvi b,0 ;got a digit, init user value
uloop: mov a,b ;get accumulated user #
add a ;* 10 for new digit
add a
add b
add a
mov b,a ;back to b
mov a,m ;get text char
sui '0' ;make binary
add b ;add to user #
mov b,a ;updated user #
inx h ;skip over it
mov a,m ;get next
cpi ':' ;end of spec?
jnz uloop ;jump if not
inx h ;yep, return txt pointer past du:
ret
;
; getps gets the primary and secondary names into the fcb.
; entry hl text pointer
; exit hl character following secondary name (if present)
;
getps: mvi c,8 ;max length of primary name
mvi b,0 ;init count of '?'
call getnam ;pack primary name into fcb
mov a,m ;see if terminated by a period
cpi '.'
rnz ;nope - secondary name not given
;return default (blanks)
inx h ;yup - move text pointer over period
ftpoint:mov a,c ;yup - update fcb pointer to secondary
ora a
jz getft
inx d
dcr c
jmp ftpoint
getft: mvi c,3 ;max length of secondary name
call getnam ;pack secondary name into fcb
ret
;
; getnam copies a name from the text pointer into the fcb for
; a given maximum length or until a delimiter is found, which
; ever occurs first. if more than the maximum number of
; characters is present, character are ignored until a
; a delimiter is found.
; entry hl first character of name to be scanned
; de pointer into fcb name field
; c maximum length
; exit hl pointing to terminating delimiter
; de next empty byte in fcb name field
; c max length - number of characters transfered
;
getnam: call getch ;are we pointing to a delimiter yet?
rz ;if so, name is transfered
inx h ;if not, move over character
cpi '*' ;ambigious file reference?
jz ambig ;if so, fill the rest of field with '?'
cpi '?' ;afn reference?
jnz notqm ;skip if not
inr b ;else bump afn count
notqm: call cvtuc ;if not, convert to upper case
stax d ;and copy into name field
inx d ;increment name field pointer
dcr c ;if name field full?
jnz getnam ;nope - keep filling
jmp getdel ;yup - ignore until delimiter
ambig: mvi a,'?' ;fill character for wild card match
fillq: stax d ;fill until field is full
inx d
inr b ;increment count of '?'
dcr c
jnz fillq ;fall thru to ingore rest of name
getdel: call getch ;pointing to a delimiter?
rz ;yup - all done
inx h ;nope - ignore antoher one
jmp getdel
;
; getch gets the character pointed to by the text pointer
; and sets the zero flag if it is a delimiter.
; entry hl text pointer
; exit hl preserved
; a character at text pointer
; z set if a delimiter
;
getch: mov a,m ;get the character, test for delim
;
; global entry: test char in a for filename delimiter
;
fndelm: cpi '/'
rz
cpi '.'
rz
cpi ','
rz
cpi ' '
rz
cpi ':'
rz
cpi '='
rz
ora a ;set zero flag on end of text
ret
;
; bdos entry: preserves bc, de. if system call is a file
; function, this routine logs into the drive/
; user area specified, then logs back after
; the call.
;
bdos: call filfck ;check for a file function
jnz bdos1 ;jump if not a file function
call getdu ;get drive/user
shld savedu
ldax d ;get fcb's drive
sta fcbdrv ;save it
dcr a ;make 0-relative
jm bdos0 ;if not default drive, jump
mov h,a ;copy to h
bdos0: xra a ;set fcb to default
stax d
dcx d ;get fcb's user #
ldax d
mov l,a
inx d ;restore de
call setdu ;set fcb's user
;
; note that unspecified user # (value=0ffh) becomes
; a getusr call, preventing ambiguity.
;
call bdos1 ;do user's system call
push psw ;save result
push h
lda fcbdrv ;restore fcb's drive
stax d
lhld savedu ;restore prior drive/user
call setdu
pop h ;restore bdos result registers
pop psw
ret
;
; local variables for bdos replacement routine
;
savedu: dw 0 ;saved drive,user
fcbdrv: db 0 ;fcb's drive
dmadr: dw 80h ;current dma adrs
;
bdos1: push d
push b
mov a,c ;doing setdma?
cpi sdmaf
jnz bdos1a ;jump if not
xchg ;yep, keep a record of dma addresses
shld dmadr
xchg
bdos1a: call system
pop b
pop d
ret
;
; get drive, user: h=drv, l=user
;
getdu: push b ;don't modify bc
push d
mvi c,gsuser ;get user #
mvi e,0ffh
call bdos1
push psw ;save it
mvi c,getdrf ;get drive
call bdos1
mov h,a ;drive returned in h
pop psw
mov l,a ;user in l
pop d
pop b ;restore caller's bc
ret
;
; set drive, user: h=drv, l=user
;
setdu: push b ;don't modify bc
push d
push h ;save info
mov e,h ;drive to e
mvi c,seldf ;set it
call bdos1
pop h ;recall info
push h
mov e,l ;user # to e
mvi c,gsuser
call bdos1 ;set it
pop h
pop d
pop b
ret
;
; check for file-function: open, close, read random, write
; random, read sequential, write sequential.
;
filfck: mov a,c ;get function #
cpi openf
rz
rc ;ignore lower function #'s
cpi closef ;(they're not file-related)
rz
cpi readf
rz
cpi writef
rz
cpi rrand
rz
cpi wrand
rz
cpi fsrchf
rz
cpi fsrchn
rz
cpi erasef
rz
cpi creatf
rz
cpi filszf
rz
cpi srand
ret
;
; convert char to upper case
;
cvtuc: cpi 'a' ;check lo bound
rc
cpi 'z'+1 ;check hi
rnc
sui 20h ;convert
ret
;
; check for hex filetype in fcb name
;
hexchk: push h
push d
push b
mvi b,3 ;type is 3 chars
lxi d,dfcb+9 ;point de to type field
lxi h,hextyp ;point hl to "COM"
hexlop: ldax d
ani 7fh ;ignore attributes
cmp m
inx h
inx d
jnz hexit ;jump if not com
dcr b
jnz hexlop
hexit: pop b ;z reg has result
pop d
pop h
ret
;
hextyp: db 'HEX'
;
; routine to return user # without disturbing registers
;
getusr: push h
push d
push b
mvi c,gsuser
mvi e,0ffh
call bdos
pop b
pop d
pop h
ret
;
; routine to return drive # without disturbing registers
;
getdsk: push h
push d
push b
mvi c,getdrf
call bdos
pop b
pop d
pop h
ret
;
; these are the initial values of the variables, and
; are moved into the variables area by the setup routine.
; if you add variables, be sure to add their intial value
; into this table in the order corresponding to their
; occurance in the variables section.
;
varset: dw 0 ;bias
dw 0 ;hiload
dw 0 ;hipc
db 0 ;cksum
dw cmdbuf ;cmdptr
db 0 ;bufptr
db 0 ;lodflg
dw cmdbuf ;filbuf
dw 0 ;offset
dw 0 ;lodadr
db 0,0,' ' ;outnam
dw 0 ;reccnt
dw 0 ;bytcnt
db 0 ;comflg
dw 0 ;comsiz
db 0 ;outflg
;
varlen equ $-varset ;define length of init table
;
; working variables
;
vars equ $ ;define variables area start
;
bias: ds 2 ;load offset
hiload: ds 2 ;highest true load address
hipc: ds 2 ;highest pc
cksum: ds 1 ;record checksum
cmdptr: ds 2 ;command line pointer
bufptr: ds 1 ;input buffer pointer
lodflg: ds 1 ;something-loaded flag
filbuf: ds 2 ;file buffer location
offset: ds 2 ;load offset into buffer
lodadr: ds 2 ;load address
outnam: ds 13 ;output drive+name
reccnt: ds 2 ;output file record count
bytcnt: ds 2 ;output file bytes loaded count
comflg: ds 1 ;flags com file encountered
comsiz: ds 2 ;size of a loaded com file
outflg: ds 1 ;flags an "=" present in cmd line
;
; end of working variables
;
;
;
; stack stuff
;
spsave: ds 2 ;system stack pntr save
;
;
ds 100 ;50-level stack
;
stack equ $
cmdbuf equ $ ;command buffer location
;
;
end