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 [[,...] [bias] ; ; where 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), ; specifies files to load and 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:=" ; 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 [=] [,...] []',cr,lf db tab,' (brackets denote optional items)',cr,lf,cr,lf db tab,' is the optional output filename',cr,lf db tab,' are input file(s)',cr,lf db tab,' is a hex load offset within the output file' db cr,lf,cr,lf db tab,' 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:=" or "C4:=". 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 (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