]> cloudbase.mooo.com Git - kermit-80.git/blob - mload.asm
Convert line endings to CP/M format (cr/lf).
[kermit-80.git] / mload.asm
1 title 'MLOAD MULTI-FILE HEX LOAD UTILITY'
2 ;
3 ; *********************************
4 ; * MLOAD.ASM *
5 ; * Multi-file Hex Load Utility *
6 ; * for CP/M *
7 ; *********************************
8 ;
9 ;
10 ; Replacement for the cp/m "LOAD" program: this program
11 ; fixes many of the problems associated with the "CP/M"
12 ; load program, and adds many new features.
13 ;
14 ; ----------------
15 ;
16 ; Rev 2.5
17 ; 03/10/88
18 ; Property of NightOwl Software, Inc. Fort Atkinson, WI 53538
19 ; Written by Ron Fowler, Nightowl Software, Inc.
20 ;
21 ; ----------------
22 ; Notice: this program is NOT public domain; copyright is retained by
23 ; NightOwl Software, Inc. of Fort Atkinson, WI ... All Rights Reserved.
24 ;
25 ; License is granted for free use and re-distribution this program, as
26 ; long as such use and re-distribution is done without profit.
27 ;
28 ; ----------------
29 ;
30 ; modification history:
31 ;
32 ; 2.5 (WOD) This version corrects a bug that overlayed the first six
33 ; bytes of the CCP. The error did not show up unless a
34 ; jump to the CCP was done without a warm boot since MLOAD
35 ; used. This source file has been modified here with
36 ; concurrence of the author of MLOAD, Ron Fowler.
37 ;
38 ; 2.4 (RGF) We apologize for this relatively insubstantial update,
39 ; but someone has caused what we consider to be a problem,
40 ; by making changes to the program, and re-releasing under
41 ; the same version number. The changes in this case were
42 ; conversion of the opcode fields (but not the comments,
43 ; can you believe that??) of every line to upper case! That
44 ; totally invalidated the CRC of the source file, since there
45 ; are now two different MLOAD 2.3's running around.
46 ;
47 ; We DO NOT want these stupid mixed upper/lower case changes.
48 ; Someone somewhere has decided that this is the way assembly
49 ; language source should be, and we most VEHEMENTLY disagree.
50 ; It's a pain in the neck to make changes to and we don't
51 ; care to run our programs through conversion programs every
52 ; time we make changes.
53 ;
54 ; So ... leave the case of this file AS IS. Any changes made
55 ; to this program and not co-ordinated through us may very
56 ; well endanger availability of source code when we make
57 ; future updates. 'nuff said --NightOwl Software
58 ;
59 ; 2.3 (RGF) Trivial cosmetic changes
60 ; 2.2 (RGF) Modified copyright notice to show new owner of the
61 ; program.
62 ; 2.1 (RGF) Fixed problem on disk-full when writing output file
63 ; (mload previously didn't error out on a full disk)
64 ; 2.0 (RGF) Added the ability to pre-load a non-hex file, allowing
65 ; mload to be used to load hex file patches (obviating any
66 ; need to use DDT). The normal mload syntax is preserved.
67 ; the first (and only the first) filespec (after the "=",
68 ; if used) may be non-hex; the filetype must be specified.
69 ; Examples:
70 ;
71 ; 1) mload ws.com,wspatch
72 ; 2) mload MEXTEST=MEX112.COM,MXO-US13
73 ; 3) mload ws.ovr,ovrpatch
74 ;
75 ; The first example loads WS.COM, overlays it with
76 ; wspatch.hex, and writes the output to WS.COM. The
77 ; second example loads MEX112.COM, overlays it with
78 ; MXO-US13.HEX, and writes the output file to MEXTEST.COM.
79 ; (note that the second example is the recommended technique,
80 ; since it preserves the original file). The third example
81 ; loads WS.OVR and patches it with the file "OVRPATCH.HEX".
82 ;
83 ; Also added this rev: ZCPR2-style du specs are now fully
84 ; supported, for both input and output files. Thus, the
85 ; following command lines are permissable:
86 ;
87 ; b3>mload a4:myfile.com=0:bigfil,b6:patch1,c9:patch2
88 ; a6>mload b5:=c3:mdm717.com,mdmpatch
89 ;
90 ; After loading, an additional information line is now printed
91 ; in the statistics report, which displays the true size of the
92 ; saved image (the previous report was technically correct, but
93 ; could result in confusion for certain kinds of files with
94 ; imbedded "DS" and "ORG" statements in the original source code).
95 ;
96 ; 1.0 - 1.4 (RGF) change log removed to conserve space
97 ;
98 ; originally written by ron fowler, fort atkinson, wisconsin
99 ;
100 ;
101 ;
102 ; For assembly with asm.com or mac (delete above title line if
103 ; assembling with asm.com).
104 ;
105 ; This program is a replacement for the cp/m "LOAD" program.
106 ; Why replace "LOAD"? well... LOAD.COM has a few deficiencies.
107 ; For example, if your hex file's origin is above 100h, LOAD.COM
108 ; prepends blank space to the output file to insure it will work
109 ; as a CP/M transient. It cares not if the file is not intended
110 ; as a CP/M transient. it also doesn't like hex records with mixed
111 ; load addresses (for example, one that loads below a previous record --
112 ; which is a perfectly legitimate happenstance). Also, LOAD.COM
113 ; can load only one program at a time, and has no provision for
114 ; a load bias in the command specification. Finally, there is no
115 ; provision for user specification of the output file name.
116 ;
117 ;
118 ; Hence, this program....
119 ;
120 ;------------------------------------------------------------
121 ;
122 ; Syntax is as follows:
123 ;
124 ; mload [<outnam=] <filename>[,<filename>...] [bias]
125 ;
126 ; where <outnam> is the (optional!;) output file name (only the drive
127 ; spec and primary filename may be specified; the output filetype is
128 ; derived exclusively from the 3-byte string at 103h within MLOAD),
129 ; <filename> specifies files to load and <bias> is the offset within
130 ; the saved image to apply when loading the file.
131 ;
132 ; MLOAD with no arguments prints a small help message -- this message
133 ; is also printed whenever a command line syntax error occurs.
134 ;
135 ; Filenames may contain drive/user specs, and must not contain wildcards.
136 ; Input filenames must be separated by commas, and a space is required
137 ; between the last filename and the optional bias.
138 ;
139 ; A load information summary is printed at the successful conclusion
140 ; of the load. Any errors in loading will generally include the name
141 ; of the file in question.
142 ;
143 ; If no output filename is specified, it will be derived from the first
144 ; input filename, with filetype of 'COM', if not otherwise specified
145 ; (this default filetype may be patched directly into mload via DDT
146 ; (or with MLOAD itself, using a patch file) -- its location is at 103H
147 ; in MLOAD.COM). Note that a command line of the form "C:=<FILENAME>"
148 ; will place the output file on the "C" drive with the same primary
149 ; filename as the input file.
150 ;
151 ; In its simplest form, MLOAD's syntax is identical to LOAD.COM; thus
152 ; there should be no problem in learning to use the new program. The
153 ; only significant difference here is that, under LOAD.COM, all files
154 ; are output starting at 100h, even if they originate elsewhere. MLOAD
155 ; outputs starting at the hex file origin (actually, the first hex rec-
156 ; ord specifies the output load address). The bias option may be used
157 ; to override this.
158 ;
159 ; An example should clarify this. Suppose you have a file that loads
160 ; at 1000h. LOAD.COM would save an output file that begins at 100h and
161 ; loads past 1000h (to wherever the program ends). MLOAD will save an
162 ; output file starting from 1000h only. If, for some reason you need the
163 ; file to start at 100h in spite of its 1000h origin (i can think of sev-
164 ; eral circumstances where this would be necessary), you'd have to specify
165 ; a bias to mload. thus, using this example, "MLOAD MYFILE 0F00" would do.
166 ;
167 ; Note that this program re-initializes itself each time it is run.
168 ; Thus, if your system supports a direct branch to the tpa (via a zero-length
169 ; .COM file, or the ZCPR "GO" command), you may safely re-execute MLOAD.
170 ;
171 ; Please report any bugs, bug fixes, or enhancements to
172 ;
173 ; "FORT FONE FILE FOLDER" rcpm/cbbs
174 ; Fort Atkinson, Wisconsin
175 ; (414) 563-9932 (no ring back)
176 ;
177 ; --Ron Fowler
178 ; 03/08/84 updated 1/31/85
179 ;
180 ;------------------------------------------------------------
181 ;
182 ; CP/M equates
183 ;
184 warmbt equ 0 ;warm boot
185 system equ 5 ;system entry (also top of mem pntr)
186 dfcb equ 5ch ;default file control block
187 ft equ 9 ;fcb offset to filetype
188 tbuf equ 80h ;default buffer
189 tpa equ 100h ;transient program area
190 eof equ 1ah ;cp/m end-of-file mark
191 fcbsiz equ 33 ;size of file control block
192 ;
193 ; CP/M system calls
194 ;
195 pcharf equ 2 ;print char
196 seldf equ 14 ;select disk drive
197 openf equ 15 ;open file
198 closef equ 16 ;close file
199 fsrchf equ 17 ;search for first
200 fsrchn equ 18 ;search for next
201 erasef equ 19 ;delete file
202 readf equ 20 ;read record
203 writef equ 21 ;write record
204 creatf equ 22 ;create file
205 getdrf equ 25 ;return dflt drive #
206 sdmaf equ 26 ;set dma address
207 gsuser equ 32 ;get/set user #
208 rrand equ 33 ;read random
209 wrand equ 34 ;write random
210 filszf equ 35 ;compute file size
211 srand equ 36 ;set random
212 ;
213 ; ASCII character constants
214 ;
215 cr equ 13
216 lf equ 10
217 bel equ 7
218 tab equ 9
219 ;
220 ; without further ado...
221 ;
222 org tpa
223 ;
224 jmp begin ;jump over default output filetype
225 ;
226 ; the default output filetype is located at 103h for easy patching
227 ;
228 outtyp: db 'COM'
229 ;
230 begin: lxi h,0 ;save system stackpointer
231 dad sp
232 shld spsave
233 lxi sp,stack ;load local stack
234 call ilprnt ;sign on
235 db 'MLOAD ver. 2.5 Copyright (C) 1983, 1984, 1985'
236 db cr,lf
237 db 'by NightOwl Software, Inc.'
238 db cr,lf,0
239 call setup ;initialize
240 main: call nxtfil ;parse and read next input file
241 jc done ;no more...
242 call lodfil ;yep, load it
243 call closfl ;close it (in case MP/M or TurboDOS)
244 jmp main ;maybe more
245 done: call wrtfil ;write the output file
246 ;
247 ; exit to cp/m
248 ;
249 exit: lxi d,tbuf ;restore dma address
250 mvi c,sdmaf
251 call bdos
252 lda system+2 ;get top of memory pointer
253 sui 9 ;allow for ccp+slop
254 lxi h,hiload+1 ;highest load address
255 sub m ;above ccp?
256 jc warmbt ;then warm-boot
257 lhld spsave ;nope, ccp still in memory
258 sphl ;restore its stack
259 ret ;return to ccp
260 ;
261 ; load program initialization
262 ;
263 setup: lxi h,varset ;initialize variables
264 lxi d,vars
265 mvi b,varlen ;by moving in default values
266 call move
267 lhld cmdptr ;get first free mem pointer
268 xchg ;in de
269 lxi h,tbuf ;point to command tail bufr
270 mov a,m ;get its length
271 inx h
272 ora a ;does it have any length?
273 jz help ;nope, go give usage help
274 mov b,a ;yep, get length to b
275 call move ;move cmd tail to buffer
276 xchg ;end of dest to hl
277 mvi m,0 ;stuff a terminator
278 inx h ;point to first free memory
279 shld filbuf ;set up file buffer
280 xchg ;file bufr adrs to de
281 lhld system+1 ;get top of memory pointer
282 xra a ;round system to page boundary
283 sub e
284 mov c,a ;with result in bc
285 mov a,h
286 sui 9 ;allow for ccp
287 sbb d
288 mov b,a
289 xchg ;buffer pointer to hl
290 nitmem: mvi m,0 ;clear buffer
291 inx h
292 dcx b
293 mov a,b
294 ora c
295 jnz nitmem
296 ;
297 ; look for a bias specification in command line
298 ;
299 lhld cmdptr ;point to command buffer-1
300 dcx h
301 call scanbk ;scan past blanks
302 ora a ;no non-blank chars?
303 jz help ;then go print help text
304 fndspc: inx h ;point to next
305 mov a,m ;fetch it
306 ora a ;test it
307 rz ;line ended, return
308 cpi ' ' ;nope, test for blank
309 jnz fndspc ;not blank, continue
310 call scanbk ;skip blanks
311 ora a ;end-of-line?
312 rz ;return if so
313 ;
314 ; hl points to bias in command line
315 ;
316 lxi d,0 ;init bias
317 call hexdig ;insure a hex digit
318 jc synerr ;bad...
319 hexlp: mov a,m ;no. get next char
320 inx h ;skip over it
321 call hexdig ;test for hex digit
322 jnc digok ;jump if good hex digit
323 ora a ;must end on null terminator
324 jnz synerr
325 xchg ;good end, get bias to hl
326 shld bias ;stuff it
327 ret ;done
328 digok: xchg ;bias to hl
329 dad h ;skift left 4 to make room
330 dad h ; for new hex digit
331 dad h
332 dad h
333 xchg ;back to de
334 add e ;add in new digit
335 mov e,a
336 jnc hexlp ;jump if no 8-bit ovfl
337 inr d ;carry
338 jmp hexlp
339 ;
340 ; parse next input name, and open resultant file
341 ;
342 nxtfil: lhld cmdptr ;get command line pointer
343 next2: lxi d,dfcb ;destination fcb
344 call fparse ;parse a filename
345 cpi '=' ;stopped on output specifier?
346 jnz noteq
347 lda outnam+2 ;insure no name yet specified
348 cpi ' '
349 jnz synerr ;syntax error if already named
350 lda outflg ;already been here?
351 ora a
352 jnz synerr ;can't be here twice
353 inr a ;flag that we've been here
354 sta outflg
355 inr b ;insure no ambiguous output name
356 dcr b
357 jnz afnerr
358 inx h ;skip over '='
359 push h ;save cmd line pointer
360 lxi h,dfcb-1 ;move the name to output name hold
361 lxi d,outnam
362 mvi b,13 ;drive spec too
363 call move
364 pop h ;restore command line pointer
365 jmp next2 ;go parse another
366 noteq: cpi ',' ;stopped on comma?
367 jz gcomma ;jump if so
368 mvi m,0 ;nope, insure end of input
369 jmp nxt2 ;don't advance over fake end
370 gcomma: inx h ;skip over comma
371 nxt2: shld cmdptr ;save new command line pntr
372 mov a,b ;get ambig char count
373 ora a ;test it
374 jnz afnerr ;allow no ambig characters
375 sta bufptr ;force a disk read
376 lxi d,dfcb+1 ;look at parsed filename
377 ldax d
378 cpi ' ' ;blank? (input ended?)
379 stc ;get carry ready in case so
380 rz ;return cy if input gone
381 dcx d ;nope, point de to start of fcb
382 open2: push d ;try to open the file
383 mvi c,openf
384 call bdos
385 pop d
386 inr a ;return=0ffh?
387 jnz openok ;jump if not
388 ;
389 ; file not found: if filetype blank, set to 'hex' and try again
390 ;
391 lxi h,dfcb+ft ;point to file type
392 mov a,m ;anything there?
393 cpi ' '
394 jnz fnferr ;yes, so file not found
395 mvi m,'H' ;nope, fill in 'hex'
396 inx h
397 mvi m,'E'
398 inx h
399 mvi m,'X'
400 jmp open2 ;go try again
401 ;
402 ; here after a good file open
403 ;
404 openok: call hexchk ;is this a hex file?
405 rz ;if so, all done
406 lxi h,comflg ;no, get pointer to flag
407 mov a,m ;loading first file?
408 ora a
409 rnz ;if not, ignore type, consider hex
410 inr m ;else, set the flag
411 ret
412 ;
413 ; load current file
414 ;
415 lodfil: lxi h,comflg ;loading a com file?
416 mov a,m ;get flag
417 ani 1
418 jnz lodcom ;jump if so
419 lhld bias ;else get bias on top of stack
420 push h
421 ;
422 ; load a hex record
423 ;
424 loadlp: call gnb ;get next file byte
425 sbi ':' ;look for start-record mark
426 jnz loadlp ;scan until found
427 sta cksum ;got it, init checksum to zero
428 mov d,a ;upper byte of rec cnt=0
429 pop b ;retrieve bias adrs
430 push b ;save it again
431 call ghbcks ;get hex byte w/checksum
432 mov e,a ;de now has record length
433 ora a ;test it
434 jnz notend ;jump if len<>0 (not eof rec)
435 pop h ;all done
436 ret
437 notend: call ghbcks ;hi byte of rec ld adrs
438 mov h,a ;accumulate in hl
439 call ghbcks ;get lo byte
440 mov l,a ;put lo in l
441 lda lodflg ;test load flag
442 ora a
443 cz lodnit ;not first record, initialize
444 push h ;save load address
445 dad d ;add in record length
446 dcx h ;make highest, not next
447 lda hipc ;a new high?
448 sub l
449 lda hipc+1
450 sbb h
451 jnc notgt ;jump if not
452 shld hipc ;yep, update hipc
453 push d ;save reclen
454 xchg ;load adrs to de
455 lhld offset ;get offset to form true memory adrs
456 dad d ;add in offset
457 dad b ;and bias
458 shld hiload ;mark highest true memory load adrs
459 lda system+2 ;validate against top-mem pointer
460 cmp h
461 jc memful ;jump if out of memory
462 pop d ;restore reclen
463 notgt: pop h ;restore load address
464 dad b ;add bias to load adrs
465 push d ;save record length
466 push h
467 lhld bytcnt ;add record length to byte count
468 dad d
469 shld bytcnt
470 pop h
471 xchg
472 lhld offset ;calculate true memory adrs
473 dad d ;hl=true loading adrs
474 pop d ;restore record length
475 call ghbcks ;skip unused byte of intel format
476 ;
477 ; move the record into memory
478 ;
479 reclp: call ghbcks ;get hex byte
480 mov m,a ;store it in buffer
481 inx h ;point to next
482 dcr e ;count down
483 jnz reclp ;until record all read
484 call ghbcks ;get checksum byte
485 jnz cserr ;final add cksum should sum 0
486 jmp loadlp ;good load, go do nxt record
487 ;
488 ; get next hex byte from input, and
489 ; accumulate a checksum
490 ;
491 ghbcks: push b ;save em all
492 push h
493 push d
494 call hexin ;get hex byte
495 mov b,a ;save in b
496 lxi h,cksum ;add to checksum
497 mov a,m
498 add b
499 mov m,a
500 mov a,b ;get byte back
501 pop d ;restore checksum
502 pop h ;restore other regs
503 pop b
504 ret
505 ;
506 ; routine to get next byte from input...forms
507 ; byte from two ascii hex characters
508 ;
509 hexin: call gnb ;get next input file byte
510 call hexval ;convert to binary w/validation
511 rlc ;move into ms nybble
512 rlc
513 rlc
514 rlc
515 ani 0f0h ;kill possible garbage
516 push psw ;save it
517 call gnb ;get next byte
518 call hexval ;convert it, w/validation
519 pop b ;get back first
520 ora b ;or in second
521 ret ;good byte in a
522 ;
523 ; gnb - utility subroutine to get next
524 ; byte from disk file
525 gnb: push h ;save all regs
526 push d
527 push b
528 lda bufptr ;get input bufr pointer
529 ani 7fh ;wound back to 0?
530 jz diskrd ;go read sector if so
531 gnb1: mvi d,0 ;else form 16 bit offset
532 mov e,a
533 lxi h,tbuf ;from tbuf
534 dad d ;add in offset
535 mov a,m ;get next byte
536 cpi eof ;end of file?
537 jz eoferr ;error if so
538 lxi h,bufptr ;else bump buf ptr
539 inr m
540 ora a ;return carry clear
541 pop b ;restore and return
542 pop d
543 pop h
544 ret
545 ;
546 ; read next sector from disk
547 ;
548 diskrd: mvi c,readf ;bdos "READ SEC" function
549 lxi d,dfcb
550 call bdos ;read sector
551 ora a
552 jnz eoferr ;error if phys end of file
553 sta bufptr ;store 0 as new buf ptr
554 jmp gnb1 ;go re-join gnb code
555 ;
556 ; load a com file
557 ;
558 lodcom: inr m ;bump the comfile flag
559 lxi h,tpa ;set origin
560 call lodnit ;and initialize
561 xchg ;load address in de
562 lhld bias ;add in bias
563 dad d
564 xchg
565 lhld offset ;and offset
566 dad d
567 xchg ;de has absolute mem adrs of load
568 ;
569 comlp: lxi h,128 ;calculate next dma
570 dad d
571 lda system+2 ;check for space
572 cmp h
573 jc memful ;jump if none
574 push h ;else save next dma
575 push d ;and this dma
576 mvi c,sdmaf ;set this dma
577 call bdos
578 lxi d,dfcb ;read next record
579 mvi c,readf
580 call bdos
581 pop h ;recall this dma
582 pop d ;de=next dma
583 ora a ;end of read?
584 jnz lodend ;jump if so
585 lhld comsiz ;no, advance com byte count
586 lxi b,128
587 dad b
588 shld comsiz
589 jmp comlp ;continue
590 ;
591 lodend: dcx h ;one less byte is highest
592 shld hiload ;set a new high
593 lhld comsiz ;hi pc=bytecount+100h
594 lxi d,tpa
595 dad d
596 xchg ;to de
597 lhld bias ;add in bias
598 dad d
599 shld hipc
600 lxi d,tbuf ;reset dma for hex files
601 mvi c,sdmaf
602 call bdos
603 ret
604 ;
605 ; write output file
606 ;
607 wrtfil: lxi d,dfcb ;point to fcb
608 push d ;save 2 copies of pointer
609 push d
610 call nitfcb ;initialize output fcb
611 lxi h,outnam ;move output name in
612 dcx d ;point to user # (prior to fcb)
613 mvi b,10 ;move user, drive, primary name
614 call move
615 mov a,m ;output type blank?
616 cpi ' '
617 jnz wrtnb ;jump if not
618 lxi h,outtyp ;yes, move dflt output filetype in
619 wrtnb: mvi b,3
620 call move
621 pop d ;restore fcb pointer
622 mvi c,erasef ;erase any existing file
623 call bdos
624 pop d ;restore fcb pointer
625 mvi c,creatf ;create a new file
626 call bdos
627 inr a ;good create?
628 jz dirful ;goto directory full error if not
629 lhld hiload ;yep, get top of bufr pntr
630 xchg ;in de
631 lhld filbuf ;get start of bufr adrs
632 mov a,e ;calculate output file size
633 sub l
634 mov c,a ;with result in bc
635 mov a,d
636 sbb h
637 mov b,a
638 mov a,b ;test length
639 ora c
640 jz loderr ;nothing to write???
641 lxi d,dfcb ;get fcb pointer
642 wrlp: push b ;save count
643 push d ;and fcb pointer
644 xchg ;get memory pointer to de
645 lxi h,128 ;add in sector length for next pass
646 dad d
647 xthl ;save next dma
648 push h ;above fcb
649 mvi c,sdmaf ;set transfer address
650 call bdos
651 pop d ;fetch fcb pointer
652 push d ;save it again
653 mvi c,writef ;write a sector
654 call bdos
655 ora a ;test result
656 jnz dskful ;disk full error...
657 lhld reccnt ;no,increment count of records
658 inx h
659 shld reccnt
660 pop d ;restore fcb pointer
661 pop h ;and memory write pointer
662 pop b ;and count
663 mov a,c ;subtract 128 (sec size) from count
664 sui 128
665 mov c,a
666 jnc wrlp ;jump if some left
667 mov a,b ;hi-order borrow
668 sui 1 ;do it (can't "DCR B", doesn't affect cy)
669 mov b,a ;restore
670 jnc wrlp ;jump if more left
671 call closfl ;close output file
672 ;
673 ; report statistics to console
674 ;
675 call ilprnt
676 db 'Loaded ',0
677 lhld bytcnt ;print # bytes
678 call decout
679 call ilprnt
680 db ' bytes (',0
681 call hexout
682 call ilprnt
683 db 'H)',0
684 call ilprnt
685 db ' to file %',0
686 lda comflg ;did we load a comfile too?
687 ora a
688 jz notcom ;jump if not
689 call ilprnt
690 db cr,lf,'Over a ',0
691 lhld comsiz
692 call decout
693 call ilprnt
694 db ' byte binary file',0
695 notcom: call ilprnt
696 db cr,lf,'Start address: ',0
697 lhld lodadr ;print loading address
698 call hexout
699 call ilprnt
700 db 'H Ending address: ',0
701 lhld hipc ;print ending load address
702 call hexout
703 call ilprnt
704 db 'H Bias: ',0
705 lhld bias
706 call hexout
707 call ilprnt
708 db 'H',cr,lf,0
709 call ilprnt
710 db 'Saved image size: ',0
711 lhld reccnt ;get count of image records
712 push h ;save it
713 mvi b,7 ;convert to bytes
714 xlp: dad h
715 dcr b
716 jnz xlp
717 call decout ;print it
718 call ilprnt
719 db ' bytes (',0
720 call hexout ;now in hex
721 call ilprnt
722 db 'H, - ',0
723 pop h ;recall record count
724 call decout ;print it
725 call ilprnt
726 db ' records)',cr,lf,0
727 lhld lodadr ;fetch loading address
728 mov a,l ;test if =tpa
729 ora a
730 jnz nottpa ;tpa always on page boundary
731 mov a,h ;lo ok, test hi
732 cpi (tpa shr 8) and 0ffh
733 rz ;return if tpa
734 nottpa: call ilprnt ;not, so print warning msg
735 db cr,lf,bel
736 db '++ Warning: program origin NOT at 100H ++'
737 db cr,lf,0
738 ret ;done
739 ;
740 ; ***********************
741 ; * utility subroutines *
742 ; ***********************
743 ;
744 ;
745 ; routine to close any open file
746 ;
747 closfl: lxi d,dfcb
748 mvi c,closef
749 call bdos
750 inr a ;test close result
751 jz clserr ;jump if error
752 ret
753 ;
754 ; print message in-line with code
755 ;
756 ilprnt: xthl ;message pntr to hl
757 call prathl ;print it
758 xthl ;restore and return
759 ret
760 ;
761 ; print msg pointed to by hl until null. expand
762 ; '%' char to current filename.
763 ;
764 prathl: mov a,m ;fetch char
765 inx h ;point to next
766 ora a ;terminator?
767 rz ;then done
768 cpi '%' ;want filename?
769 jz prtfn ;go do it if so
770 call type ;nope, just print char
771 jmp prathl ;continue
772 ;
773 prtfn: push h ;save pointer
774 push b
775 lda dfcb ;fetch dr field of dfcb
776 ora a ;default drive?
777 jnz prndf ;jump if not
778 call getdsk ;get logged-in drive #
779 inr a ;make it one-relative (as in fcb)
780 prndf: adi 'A'-1 ;make drive name printable
781 call type ;print it
782 lda dfcb-1 ;get user #
783 cpi 0ffh ;null?
784 cz getusr ;iff so, get current user
785 mov l,a ;to hl
786 mvi h,0
787 call decout ;print it
788 mvi a,':' ;drive names followed by colon
789 call type
790 lxi h,dfcb+1 ;setup for name
791 mvi b,8 ;print up to 8
792 call prtnam
793 mvi a,'.' ;print dot
794 call type
795 mvi b,3 ;print filetype field
796 call prtnam
797 pop b
798 pop h ;restore and continue
799 jmp prathl
800 ;
801 ; print file name .HL max length in b. don't print spaces
802 ;
803 prtnam: mov a,m ;fetch a char
804 cpi ' ' ;blank?
805 jz pwind ;go wind if so
806 inx h ;nope, move to next
807 call type ;print it
808 dcr b ;count down
809 jnz prtnam ;continue
810 ret
811 pwind: inx h ;skip remainder of blank name
812 dcr b
813 jnz pwind
814 ret
815 ;
816 ; print HL in decimal on console
817 ;
818 decout: push h ;save everybody
819 push d
820 push b
821 lxi b,-10 ;conversion radix
822 lxi d,-1
823 declp: dad b
824 inx d
825 jc declp
826 lxi b,10
827 dad b
828 xchg
829 mov a,h
830 ora l
831 cnz decout ;this is recursive
832 mov a,e
833 adi '0'
834 call type
835 pop b
836 pop d
837 pop h
838 ret
839 ;
840 ; newline on console
841 ;
842 crlf: mvi a,cr
843 call type
844 mvi a,lf
845 jmp type
846 ;
847 ; print hl on console in hex
848 ;
849 hexout: mov a,h ;get hi
850 call hexbyt ;print it
851 mov a,l ;get lo, fall into hexbyt
852 ;
853 ; type accumulator on console in hex
854 ;
855 hexbyt: push psw ;save byte
856 rar ;get ms nybble..
857 rar ;..into lo 4 bits
858 rar
859 rar
860 call nybble
861 pop psw ;get back byte
862 nybble: ani 0fh ;mask ms nybble
863 adi 90h ;add offset
864 daa ;decimal adjust a-reg
865 aci 40h ;add offset
866 daa ;fall into type
867 ;
868 ; type char in a on console
869 ;
870 type: push h ;save all
871 push d
872 push b
873 mov e,a ;cp/m outputs from e
874 mvi c,pcharf
875 call bdos
876 pop b
877 pop d
878 pop h
879 ret
880 ;
881 ; move: from @hl to @de, count in b
882 ;
883 move: inr b ;up one
884 movlp: dcr b ;count down
885 rz ;return if done
886 mov a,m ;not done, continue
887 stax d
888 inx h ;pointers=pointers+1
889 inx d
890 jmp movlp
891 ;
892 ; scan to first non-blank char in string @hl
893 ;
894 scanbk: inx h ;next
895 mov a,m ;fetch it
896 cpi ' '
897 jz scanbk
898 ret
899 ;
900 ; get hex digit and validate
901 ;
902 hexval: call hexdig ;get hex digit
903 jc formerr ;jump if bad
904 ret
905 ;
906 ; get hex digit, return cy=1 if bad digit
907 ;
908 hexdig: cpi '0' ;lo boundary test
909 rc ;bad already?
910 cpi '9'+1 ;no, test hi
911 jc hexcvt ;jump if numeric
912 cpi 'A' ;test alpha
913 rc ;bad?
914 cpi 'F'+1 ;no, upper alpha bound
915 cmc ;pervert carry
916 rc ;bad?
917 sui 7 ;no, adjust to 0-f
918 hexcvt: ani 0fh ;make it binary
919 ret
920 ;
921 ; ******************
922 ; * error handlers *
923 ; ******************
924 ;
925 synerr: call crlf
926 call ilprnt
927 db ' Command line syntax error',cr,lf,cr,lf,0
928 jmp help ;give help msg too
929 ;
930 afnerr: call errxit ;exit with message
931 db 'Ambiguous file name: % not allowed.',0
932 ;
933 fnferr: call errxit
934 db 'File % not found.',0
935 ;
936 dskful: call errxit
937 db 'Disk full.',0
938 ;
939 dirful: call errxit
940 db 'Directory full.',0
941 ;
942 eoferr: call errxit
943 db 'Premature end-of-file in %',0
944 ;
945 cserr: call errxit
946 db 'Checksum error in %',0
947 ;
948 clserr: call errxit
949 db 'Can''t close %',0
950 ;
951 memful: call errxit
952 db 'Memory full while loading %',0
953 ;
954 formerr:call errxit
955 db 'Format error in file %',0
956 ;
957 loderr: call errxit
958 db 'Writing %, nothing loaded',0
959 ;
960 help: call errxit ;print help text
961 db 'MLOAD syntax:',cr,lf,cr,lf
962 db 'MLOAD [<OUTFIL>=] <FILE1>[,<FILE2>...] [<BIAS>]',cr,lf
963 db tab,' (brackets denote optional items)',cr,lf,cr,lf
964 db tab,'<OUTFIL> is the optional output filename',cr,lf
965 db tab,'<FILEn> are input file(s)',cr,lf
966 db tab,'<BIAS> is a hex load offset within the output file'
967 db cr,lf,cr,lf
968 db tab,'<FILE1> may be an optional non-HEX file to be patched',cr,lf
969 db tab,'by subsequently named HEX files (specifying',cr,lf
970 db tab,'The filetype enables this function).'
971 db cr,lf,cr,lf
972 db 'Note that ZCPR2-style drive/user notation may be used in all'
973 db cr,lf
974 db 'file specifications (e.g., "B3:MYFILE.COM, "A14:MDM7.HEX").'
975 db cr,lf,0
976 ;
977 ; general error handler
978 ;
979 errxit: call crlf ;new line
980 pop h ;fetch error msg pointer
981 call prathl ;print it
982 call crlf
983 jmp exit ;done
984 ;
985 ; initialize load parameters
986 ;
987 lodnit: mvi a,1 ;first record, set load flag
988 sta lodflg
989 shld lodadr ;save load address
990 shld hipc ;and hi load
991 push d ;save record length
992 xchg ;de=load address
993 lhld filbuf ;get address of file buffer
994 mov a,l ;subtract load adrs from file buffer
995 sub e
996 mov l,a
997 mov a,h
998 sbb d
999 mov h,a
1000 shld offset ;save as load offset
1001 push d ;save load address on stack
1002 push b ;save bias
1003 lxi d,outnam+2 ;check output filename
1004 ldax d ;(first char)
1005 cpi ' '
1006 jnz namskp ;jump if so
1007 lxi h,dfcb+1 ;get first name pointer
1008 mvi b,8 ;(don't include drive spec)
1009 call move
1010 ;
1011 ; check for outflg=1 (presence of an "="). note that the
1012 ; filename may well be blank, and yet outflg <>0, for example
1013 ; in the case of "A:=<FILENAME>" or "C4:=<FILENAME>". in
1014 ; this case, we want to remember the drive/user specified, but
1015 ; use the first input file to form the output name. otherwise,
1016 ; we use the current drive/user.
1017 ;
1018 lda outflg ;was there an "="?
1019 ora a
1020 jnz namskp ;jump if so
1021 lxi h,outnam ;get destination pointer
1022 call getusr ;get current user #
1023 mov m,a
1024 inx h ;point to drive
1025 call getdsk ;get it
1026 inr a ;fcb's drive is 1-relative
1027 mov m,a
1028 namskp: mvi a,1 ;insure "=" cannot occur anymore
1029 sta outflg
1030 pop b ;restore bias
1031 pop h ;load address to hl
1032 pop d ;restore record length
1033 ret
1034 ;
1035 ; *********************************
1036 ; * file name parsing subroutines *
1037 ; *********************************
1038 ;
1039 ; credit where credit's due:
1040 ; --------------------------
1041 ; these routines were lifted from bob van valzah's
1042 ; "FAST" program.
1043 ;
1044 ;
1045 ;
1046 ; *********************************
1047 ; * file name parsing subroutines *
1048 ; *********************************
1049 ;
1050 ;
1051 ; getfn gets a file name from text pointed to by reg hl into
1052 ; an fcb pointed to by reg de. leading delimeters are
1053 ; ignored. allows drive spec of the form <du:> (drive/user).
1054 ; this routine formats all 33 bytes of the fcb (but not ran rec).
1055 ;
1056 ; entry de first byte of fcb
1057 ; exit b=# of '?' in name
1058 ; fcb-1= user # parsed (if specified) or 255
1059 ;
1060 ;
1061 fparse: call nitfcb ;init 1st half of fcb
1062 call gstart ;scan to first character of name
1063 call getdrv ;get drive/user spec. if present
1064 mov a,b ;get user # or 255
1065 cpi 0ffh ;255?
1066 jz fpars1 ;jump if so
1067 dcx d ;back up to byte preceeding fcb
1068 dcx d
1069 stax d ;stuff user #
1070 inx d ;onward
1071 inx d
1072 fpars1: call getps ;get primary and secondary name
1073 ret
1074 ;
1075 ; nitfcb fills the fcb with dflt info - 0 in drive field
1076 ; all-blank in name field, and 0 in ex,s1,s2,rc, disk
1077 ; allocation map, and random record # fields
1078 ;
1079 nitfcb: push h
1080 push d
1081 call getusr ;init user field
1082 pop d
1083 pop h
1084 push d ;save fcb loc
1085 dcx d
1086 stax d ;init user # to currnt user #
1087 inx d
1088 xchg ;move it to hl
1089 mvi m,0 ;drive=default
1090 inx h ;bump to name field
1091 mvi b,11 ;zap all of name fld
1092 nitlp: mvi m,' '
1093 inx h
1094 dcr b
1095 jnz nitlp
1096 mvi b,33-11 ;zero others, up to nr field
1097 zlp: mvi m,0
1098 inx h
1099 dcr b
1100 jnz zlp
1101 xchg ;restore hl
1102 pop d ;restore fcb pointer
1103 ret
1104 ;
1105 ; gstart advances the text pointer (reg hl) to the first
1106 ; non delimiter character (i.e. ignores blanks). returns a
1107 ; flag if end of line (00h or ';') is found while scaning.
1108 ; exit hl pointing to first non delimiter
1109 ; a clobbered
1110 ; zero set if end of line was found
1111 ;
1112 gstart: call getch ;see if pointing to delim?
1113 rnz ;nope - return
1114 ora a ;physical end?
1115 rz ;yes - return w/flag
1116 inx h ;nope - move over it
1117 jmp gstart ;and try next char
1118 ;
1119 ; getdrv checks for the presence of a du: spec at the text
1120 ; pointer, and if present formats drive into fcb and returns
1121 ; user # in b.
1122 ;
1123 ; entry hl text pointer
1124 ; de pointer to first byte of fcb
1125 ; exit hl possibly updated text pointer
1126 ; de pointer to second (primary name) byte of fcb
1127 ; b user # if specified or 0ffh
1128 ;
1129 getdrv: mvi b,0ffh ;default no user #
1130 push h ;save text pointer
1131 dscan: call getch ;get next char
1132 inx h ;skip pointer over it
1133 jnz dscan ;scan until delimiter
1134 cpi ':' ;delimiter a colon?
1135 inx d ;skip dr field in fcb in case not
1136 pop h ;and restore text pointer
1137 rnz ;return if no du: spec
1138 mov a,m ;got one, get first char
1139 call cvtuc ;may be drive name, cvt to upper case
1140 cpi 'A' ;alpha?
1141 jc isnum ;jump to get user # if not
1142 sui 'A'-1 ;yes, convert from ascii to #
1143 dcx d ;back up fcb pointer to dr field
1144 stax d ;store drive # into fcb
1145 inx d ;pass pointer over drv
1146 inx h ;skip drive spec in text
1147 isnum: mov a,m ;fetch next
1148 inx h
1149 cpi ':' ;du delimiter?
1150 rz ;done then
1151 dcx h ;nope, back up text pointer
1152 mvi b,0 ;got a digit, init user value
1153 uloop: mov a,b ;get accumulated user #
1154 add a ;* 10 for new digit
1155 add a
1156 add b
1157 add a
1158 mov b,a ;back to b
1159 mov a,m ;get text char
1160 sui '0' ;make binary
1161 add b ;add to user #
1162 mov b,a ;updated user #
1163 inx h ;skip over it
1164 mov a,m ;get next
1165 cpi ':' ;end of spec?
1166 jnz uloop ;jump if not
1167 inx h ;yep, return txt pointer past du:
1168 ret
1169 ;
1170 ; getps gets the primary and secondary names into the fcb.
1171 ; entry hl text pointer
1172 ; exit hl character following secondary name (if present)
1173 ;
1174 getps: mvi c,8 ;max length of primary name
1175 mvi b,0 ;init count of '?'
1176 call getnam ;pack primary name into fcb
1177 mov a,m ;see if terminated by a period
1178 cpi '.'
1179 rnz ;nope - secondary name not given
1180 ;return default (blanks)
1181 inx h ;yup - move text pointer over period
1182 ftpoint:mov a,c ;yup - update fcb pointer to secondary
1183 ora a
1184 jz getft
1185 inx d
1186 dcr c
1187 jmp ftpoint
1188 getft: mvi c,3 ;max length of secondary name
1189 call getnam ;pack secondary name into fcb
1190 ret
1191 ;
1192 ; getnam copies a name from the text pointer into the fcb for
1193 ; a given maximum length or until a delimiter is found, which
1194 ; ever occurs first. if more than the maximum number of
1195 ; characters is present, character are ignored until a
1196 ; a delimiter is found.
1197 ; entry hl first character of name to be scanned
1198 ; de pointer into fcb name field
1199 ; c maximum length
1200 ; exit hl pointing to terminating delimiter
1201 ; de next empty byte in fcb name field
1202 ; c max length - number of characters transfered
1203 ;
1204 getnam: call getch ;are we pointing to a delimiter yet?
1205 rz ;if so, name is transfered
1206 inx h ;if not, move over character
1207 cpi '*' ;ambigious file reference?
1208 jz ambig ;if so, fill the rest of field with '?'
1209 cpi '?' ;afn reference?
1210 jnz notqm ;skip if not
1211 inr b ;else bump afn count
1212 notqm: call cvtuc ;if not, convert to upper case
1213 stax d ;and copy into name field
1214 inx d ;increment name field pointer
1215 dcr c ;if name field full?
1216 jnz getnam ;nope - keep filling
1217 jmp getdel ;yup - ignore until delimiter
1218 ambig: mvi a,'?' ;fill character for wild card match
1219 fillq: stax d ;fill until field is full
1220 inx d
1221 inr b ;increment count of '?'
1222 dcr c
1223 jnz fillq ;fall thru to ingore rest of name
1224 getdel: call getch ;pointing to a delimiter?
1225 rz ;yup - all done
1226 inx h ;nope - ignore antoher one
1227 jmp getdel
1228 ;
1229 ; getch gets the character pointed to by the text pointer
1230 ; and sets the zero flag if it is a delimiter.
1231 ; entry hl text pointer
1232 ; exit hl preserved
1233 ; a character at text pointer
1234 ; z set if a delimiter
1235 ;
1236 getch: mov a,m ;get the character, test for delim
1237 ;
1238 ; global entry: test char in a for filename delimiter
1239 ;
1240 fndelm: cpi '/'
1241 rz
1242 cpi '.'
1243 rz
1244 cpi ','
1245 rz
1246 cpi ' '
1247 rz
1248 cpi ':'
1249 rz
1250 cpi '='
1251 rz
1252 ora a ;set zero flag on end of text
1253 ret
1254 ;
1255 ; bdos entry: preserves bc, de. if system call is a file
1256 ; function, this routine logs into the drive/
1257 ; user area specified, then logs back after
1258 ; the call.
1259 ;
1260 bdos: call filfck ;check for a file function
1261 jnz bdos1 ;jump if not a file function
1262 call getdu ;get drive/user
1263 shld savedu
1264 ldax d ;get fcb's drive
1265 sta fcbdrv ;save it
1266 dcr a ;make 0-relative
1267 jm bdos0 ;if not default drive, jump
1268 mov h,a ;copy to h
1269 bdos0: xra a ;set fcb to default
1270 stax d
1271 dcx d ;get fcb's user #
1272 ldax d
1273 mov l,a
1274 inx d ;restore de
1275 call setdu ;set fcb's user
1276 ;
1277 ; note that unspecified user # (value=0ffh) becomes
1278 ; a getusr call, preventing ambiguity.
1279 ;
1280 call bdos1 ;do user's system call
1281 push psw ;save result
1282 push h
1283 lda fcbdrv ;restore fcb's drive
1284 stax d
1285 lhld savedu ;restore prior drive/user
1286 call setdu
1287 pop h ;restore bdos result registers
1288 pop psw
1289 ret
1290 ;
1291 ; local variables for bdos replacement routine
1292 ;
1293 savedu: dw 0 ;saved drive,user
1294 fcbdrv: db 0 ;fcb's drive
1295 dmadr: dw 80h ;current dma adrs
1296 ;
1297 bdos1: push d
1298 push b
1299 mov a,c ;doing setdma?
1300 cpi sdmaf
1301 jnz bdos1a ;jump if not
1302 xchg ;yep, keep a record of dma addresses
1303 shld dmadr
1304 xchg
1305 bdos1a: call system
1306 pop b
1307 pop d
1308 ret
1309 ;
1310 ; get drive, user: h=drv, l=user
1311 ;
1312 getdu: push b ;don't modify bc
1313 push d
1314 mvi c,gsuser ;get user #
1315 mvi e,0ffh
1316 call bdos1
1317 push psw ;save it
1318 mvi c,getdrf ;get drive
1319 call bdos1
1320 mov h,a ;drive returned in h
1321 pop psw
1322 mov l,a ;user in l
1323 pop d
1324 pop b ;restore caller's bc
1325 ret
1326 ;
1327 ; set drive, user: h=drv, l=user
1328 ;
1329 setdu: push b ;don't modify bc
1330 push d
1331 push h ;save info
1332 mov e,h ;drive to e
1333 mvi c,seldf ;set it
1334 call bdos1
1335 pop h ;recall info
1336 push h
1337 mov e,l ;user # to e
1338 mvi c,gsuser
1339 call bdos1 ;set it
1340 pop h
1341 pop d
1342 pop b
1343 ret
1344 ;
1345 ; check for file-function: open, close, read random, write
1346 ; random, read sequential, write sequential.
1347 ;
1348 filfck: mov a,c ;get function #
1349 cpi openf
1350 rz
1351 rc ;ignore lower function #'s
1352 cpi closef ;(they're not file-related)
1353 rz
1354 cpi readf
1355 rz
1356 cpi writef
1357 rz
1358 cpi rrand
1359 rz
1360 cpi wrand
1361 rz
1362 cpi fsrchf
1363 rz
1364 cpi fsrchn
1365 rz
1366 cpi erasef
1367 rz
1368 cpi creatf
1369 rz
1370 cpi filszf
1371 rz
1372 cpi srand
1373 ret
1374 ;
1375 ; convert char to upper case
1376 ;
1377 cvtuc: cpi 'a' ;check lo bound
1378 rc
1379 cpi 'z'+1 ;check hi
1380 rnc
1381 sui 20h ;convert
1382 ret
1383 ;
1384 ; check for hex filetype in fcb name
1385 ;
1386 hexchk: push h
1387 push d
1388 push b
1389 mvi b,3 ;type is 3 chars
1390 lxi d,dfcb+9 ;point de to type field
1391 lxi h,hextyp ;point hl to "COM"
1392 hexlop: ldax d
1393 ani 7fh ;ignore attributes
1394 cmp m
1395 inx h
1396 inx d
1397 jnz hexit ;jump if not com
1398 dcr b
1399 jnz hexlop
1400 hexit: pop b ;z reg has result
1401 pop d
1402 pop h
1403 ret
1404 ;
1405 hextyp: db 'HEX'
1406 ;
1407 ; routine to return user # without disturbing registers
1408 ;
1409 getusr: push h
1410 push d
1411 push b
1412 mvi c,gsuser
1413 mvi e,0ffh
1414 call bdos
1415 pop b
1416 pop d
1417 pop h
1418 ret
1419 ;
1420 ; routine to return drive # without disturbing registers
1421 ;
1422 getdsk: push h
1423 push d
1424 push b
1425 mvi c,getdrf
1426 call bdos
1427 pop b
1428 pop d
1429 pop h
1430 ret
1431 ;
1432 ; these are the initial values of the variables, and
1433 ; are moved into the variables area by the setup routine.
1434 ; if you add variables, be sure to add their intial value
1435 ; into this table in the order corresponding to their
1436 ; occurance in the variables section.
1437 ;
1438 varset: dw 0 ;bias
1439 dw 0 ;hiload
1440 dw 0 ;hipc
1441 db 0 ;cksum
1442 dw cmdbuf ;cmdptr
1443 db 0 ;bufptr
1444 db 0 ;lodflg
1445 dw cmdbuf ;filbuf
1446 dw 0 ;offset
1447 dw 0 ;lodadr
1448 db 0,0,' ' ;outnam
1449 dw 0 ;reccnt
1450 dw 0 ;bytcnt
1451 db 0 ;comflg
1452 dw 0 ;comsiz
1453 db 0 ;outflg
1454 ;
1455 varlen equ $-varset ;define length of init table
1456 ;
1457 ; working variables
1458 ;
1459 vars equ $ ;define variables area start
1460 ;
1461 bias: ds 2 ;load offset
1462 hiload: ds 2 ;highest true load address
1463 hipc: ds 2 ;highest pc
1464 cksum: ds 1 ;record checksum
1465 cmdptr: ds 2 ;command line pointer
1466 bufptr: ds 1 ;input buffer pointer
1467 lodflg: ds 1 ;something-loaded flag
1468 filbuf: ds 2 ;file buffer location
1469 offset: ds 2 ;load offset into buffer
1470 lodadr: ds 2 ;load address
1471 outnam: ds 13 ;output drive+name
1472 reccnt: ds 2 ;output file record count
1473 bytcnt: ds 2 ;output file bytes loaded count
1474 comflg: ds 1 ;flags com file encountered
1475 comsiz: ds 2 ;size of a loaded com file
1476 outflg: ds 1 ;flags an "=" present in cmd line
1477 ;
1478 ; end of working variables
1479 ;
1480 ;
1481 ;
1482 ; stack stuff
1483 ;
1484 spsave: ds 2 ;system stack pntr save
1485 ;
1486 ;
1487 ds 100 ;50-level stack
1488 ;
1489 stack equ $
1490 cmdbuf equ $ ;command buffer location
1491 ;
1492 ;
1493 end