1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
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
|