aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorJeff Armstrong <jeff@approximatrix.com>2026-10-25 17:42:06 -0400
committerJeff Armstrong <jeff@approximatrix.com>2026-10-25 17:42:06 -0400
commitebb2ff2af569d5822b70ee555a32d27cf7494816 (patch)
tree7ac5e7b3fa73f7a8d996129086932ecce66374ad /tools
downloadkermit-80-ebb2ff2af569d5822b70ee555a32d27cf7494816.tar.gz
kermit-80-ebb2ff2af569d5822b70ee555a32d27cf7494816.zip
Initial import of 4.11HEADmain
Diffstat (limited to 'tools')
-rw-r--r--tools/cpkhex.bas88
-rw-r--r--tools/mload.asm1493
-rw-r--r--tools/mload.txt138
3 files changed, 1719 insertions, 0 deletions
diff --git a/tools/cpkhex.bas b/tools/cpkhex.bas
new file mode 100644
index 0000000..9bad29b
--- /dev/null
+++ b/tools/cpkhex.bas
@@ -0,0 +1,88 @@
+1 REM -- CP4HEX.BAS --
+2 REM This program reads an Intel-format hex file and checks it for validity.
+3 REM It discards blank lines, and if it finds any other lines that are bad,
+4 REM either bacause the format is wrong or the checksum doesn't agree, it
+5 REM prompts you to type in a replacement line. Use this program to fix
+6 REM hex files you have downloaded using a non-error-correcting method.
+7 REM Written by F. da Cruz & C. Gianone, Columbia University, April 1986.
+8 REM Used with success on Vector Graphic Model 3 CP/M-80 system.
+9 REM
+100 ON ERROR GOTO 9000
+200 INPUT;"Input hex file "; F$
+300 PRINT
+400 OPEN "I",#1,F$
+500 INPUT;"Output hex file"; O$
+600 PRINT
+700 OPEN "O",#2,O$
+800 B = 0
+900 REM Loop at 1000 for each line, B, of the input hex file, then...
+901 REM Discard blank lines.
+902 REM Look for colon that begins hex record
+903 REM Decode rest of record...
+904 REM L = length of rest, in bytes (not hex characters!), 1 byte
+905 REM N = address, 2 bytes
+906 REM T = record type, 0 = data, 1 = EOF
+907 REM C% = 1-byte checksum
+908 REM Record has negative of actual checksum, adding to C% should give 0.
+909 REM Subroutine at 8000 converts pair of hex nibbles to 8-bit byte.
+910 REM
+1000 LINE INPUT #1, A$
+1010 B = B + 1
+1015 IF A$ = "" THEN GOTO 1000
+1020 IF LEFT$(A$,1) = ":" THEN GOTO 2000
+1030 X = INSTR(2,A$,":")
+1033 IF X < 1 THEN GOTO 1040
+1035 A$ = MID$(A$,X,LEN(A$)-X+1)
+1037 GOTO 2000
+1040 PRINT "Line";B;"has no colon"
+1060 GOTO 3070
+2000 C% = 0
+2005 PRINT ".";
+2010 X$ = MID$(A$,2,2)
+2020 GOSUB 8000
+2030 L = K
+2040 X$ = MID$(A$,4,2)
+2050 GOSUB 8000
+2060 N = K
+2070 X$ = MID$(A$,6,2)
+2080 GOSUB 8000
+2090 N = 256 * N + K
+2100 X$ = MID$(A$,8,2)
+2110 GOSUB 8000
+2120 T = K
+2125 IF T = 1 THEN PRINT "End of";F$;"at line";B : GOTO 9000
+2130 IF T <> 0 THEN PRINT "Invalid record type"; T : GOTO 3070
+2140 M = 10
+3000 X$ = MID$(A$,M,2)
+3002 GOSUB 8000
+3005 M = M + 2
+3010 L = L - 1
+3020 IF L > 0 THEN GOTO 3000
+3030 X$ = MID$(A$,M,2)
+3050 GOSUB 8000
+3060 IF (C% AND 255) = 0 THEN GOTO 3100
+3065 PRINT "Bad Checksum"
+3070 PRINT "Line";B
+3075 PRINT "Bad: "; A$
+3080 INPUT "New "; A$
+3090 PRINT
+3100 PRINT #2,A$
+4000 GOTO 1000
+7999 REM Subroutine to convert pair of hex nibbles to one 8-bit byte.
+8000 I = ASC(LEFT$(X$,1))
+8005 K = 0
+8010 IF I > 64 AND I < 71 THEN I = I - 7
+8020 IF I < 48 OR I > 63 THEN K = -1
+8030 J = ASC(MID$(X$,2,1))
+8040 IF J > 64 AND J < 71 THEN J = J - 7
+8050 IF J < 48 OR J > 63 THEN K = -1
+8060 IF K < 0 THEN RETURN
+8070 K = 16 * (I - 48) + J - 48
+8080 C% = C% + K
+8099 RETURN
+8999 Program exit and error handler
+9000 CLOSE #1
+9010 CLOSE #2
+9020 CLOSE
+9030 PRINT "Records processed:";B
+9999 END
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
diff --git a/tools/mload.txt b/tools/mload.txt
new file mode 100644
index 0000000..879e9b8
--- /dev/null
+++ b/tools/mload.txt
@@ -0,0 +1,138 @@
+ MLOAD
+
+Description:
+
+ MLOAD is a transient command used to convert a "HEX" file (the
+output of an assembler) into an executable "COM" file.
+
+ In its simplest form, MLOAD takes a program that is in valid Intel
+hexadecimal format machine code and converts it into a command file that
+can be executed. The file becomes filename.COM (the hexadecimal file is
+filename.HEX). This form may be used as a replacement to the Digital
+Research "LOAD" command.
+
+ The program may also be used to overlay portions of a "COM" file
+with patches assembled to a new "HEX" file.
+
+Syntax:
+
+ mload [<outnam=>][<file1.type>,]<file2>[,<file3...>] [bias]
+
+ <outnam> is the optional output file name.
+
+ <file1.type> is an optional non-HEX file to be patched by
+ subsequently named HEX files. Specifying the filetype enables this
+ function.
+
+ <file2> specifies the file to be loaded, unless a file1.type has
+ been specified. In that case, it specifies a HEX file to load.
+
+ <file3> specifies HEX file(s) to load.
+
+ <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.
+
+ ZCPR-style du specifications are fully supported, for both
+ input and output files. The following command lines are
+ permissible:
+
+ b3>mload a4:myfile.com=0:bigfil,b6:patch1,c9:patch2
+ a6>mload b5:=c3:mdm717.com,mdmpatch
+
+ Only the optional drive and user spec and the primary filename
+ may be specified. The output filetype is derived exclusively from
+ the 3-byte string at 103h within MLOAD.
+
+ 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.
+
+ This feature obviating any need to use DDT. The first (and
+ only the first) filespec (after the "=", if used) may be non-HEX
+ and the filetype must be specified. Examples:
+
+ 1. "mload MYPROG" loads MYPROG.HEX and writes the output to
+ MYPROG.COM.
+
+ 2. "mload ws.com,wspatch" loads WS.COM, overlays it with
+ WSPATCH.HEX, and writes the output to WS.COM.
+
+ 3. "mload MEXTEST=MEX114.COM,MXO-US13" loads MEX114.COM,
+ overlays it with MXO-US13.HEX, and writes the output file to
+ MEXTEST.COM. Note that this is the recommended technique because
+ it preserves the original file!
+
+ 4. "mload ws.ovr,ovrpatch" loads WS.OVR and patches it with
+ "OVRPATCH.HEX".
+
+ Filenames may contain drive/user specs, and must not contain
+ wildcards. Input filenames must be separated by comas, 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.
+
+ This program is a replacement for the CP/M "LOAD" program. Why
+replace "LOAD"? well... LOAD.COM has a few deficiencies. For example,
+if the 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
+doesn't care if the file is not intended as a CP/M transient and 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.
+
+ In its simplest form, MLOAD's syntax is identical to LOAD.COM.
+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 record
+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
+several circumstances where this would be necessary), you'd have to
+specify a bias to MLOAD. Using this example, "MLOAD MYFILE 0F00"
+would do.
+
+ Note that this program re-initializes itself each time it is run.
+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.
+
+Error Messages:
+
+ Command line syntax error
+ Error in command line, re-enter and try again.
+ Ambiguous file name: <filename.ext> not allowed.
+ Ambiguous files are not allowed.
+ File <filename.ext> not found.
+ Input file could not be found, check spelling and try again.
+ Disk full.
+ No room on disk for output file.
+ Directory full.
+ No room in directory for output file.
+ Premature end-of-file in <filename.ext>
+ Error in input file, correct error, and try again.
+ Checksum error in <filename.ext>
+ Error in input file, correct error, and try again.
+ Can't close <filename.ext>
+ Problem closing file, disk may be in Read/Only status.
+ Memory full while loading <filename.ext>
+ Error in input file, correct error, and try again.
+ Format error in file <filename.ext>
+ Error in input file, correct error, and try again.
+ Writing <filename.ext>, nothing loaded