diff options
| author | Jeff Armstrong <jeff@approximatrix.com> | 2026-10-25 17:42:06 -0400 |
|---|---|---|
| committer | Jeff Armstrong <jeff@approximatrix.com> | 2026-10-25 17:42:06 -0400 |
| commit | ebb2ff2af569d5822b70ee555a32d27cf7494816 (patch) | |
| tree | 7ac5e7b3fa73f7a8d996129086932ecce66374ad /tools/mload.asm | |
| download | kermit-80-main.tar.gz kermit-80-main.zip | |
Diffstat (limited to 'tools/mload.asm')
| -rw-r--r-- | tools/mload.asm | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/tools/mload.asm b/tools/mload.asm new file mode 100644 index 0000000..668796a --- /dev/null +++ b/tools/mload.asm @@ -0,0 +1,1493 @@ + 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 |
