-
Notifications
You must be signed in to change notification settings - Fork 0
/
B64_FC.ASM
executable file
·794 lines (627 loc) · 16.8 KB
/
B64_FC.ASM
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
uppercase
bits 16
cpu 8086
; Include support for fortified DOS system calls:
%include "dossyscl.inc"
; Library routines used:
extern conv_bin_b64
extern conv_b64_bin
extern write_str_zterm
extern write_dossyscl_error
extern str_identity_check
extern char_is_space
segment dseg public class=data align=16
help_screen db "Usage: b64_fc [OPTIONS] <INPUT FILE> <OUTPUT FILE>", 0Dh, 0Ah
db 0Dh, 0Ah
db "b64_fc is a program used for encoding files into a Base64 representation, as", 0Dh, 0Ah
db "well as for decoding Base64-encoded files.", 0Dh, 0Ah
db 0Dh, 0Ah
db "Command-line options:", 0Dh, 0Ah
db 0Dh, 0Ah
db " -e, --encode Encode the given input file into Base64 (default)", 0Dh, 0Ah
db " -d, --decode Decode a Base64-encoded file", 0Dh, 0Ah
db " -s, --stdin Read input data from stdin", 0Dh, 0Ah
db " -c, --stdout Write output data to stdout", 0Dh, 0Ah
db " -h, --help Show this help screen and quit", 0Dh, 0Ah
db 0
welcome_str db "b64_fc - Dooshki's Base64 file encoding and decoding utility."
double_nl_str db 0Dh, 0Ah
newline_str db 0Dh, 0Ah, 0
dot_newline_str db ".", 0Dh, 0Ah, 0
encode_lopt_str db "encode", 0
decode_lopt_str db "decode", 0
stdin_lopt_str db "stdin", 0
stdout_lopt_str db "stdout", 0
help_lopt_str db "help", 0
no_ifile_str db "No input file specified.", 0Dh, 0Ah, 0
no_ofile_str db "No output file specified.", 0Dh, 0Ah, 0
invalid_opt_str db "Invalid comman-line option: -", 0
unexp_arg_str db "Unexpected command-line argument: ", 0
usng_stdin_str db "Using stdin for data input.", 0Dh, 0Ah, 0
usng_stdout_str db "Using stdout for data output.", 0Dh, 0Ah, 0
opening_str db "Opening `", 0
opening_if_str db "' for reading... ", 0
opening_of_str db "' for writing... ", 0
encoding_str db 0Dh, 0Ah, "Encoding... ", 0
decoding_str db 0Dh, 0Ah, "Decoding... ", 0
success_str db "ok.", 0Dh, 0Ah, 0
done_str db "done.", 0Dh, 0Ah, 0
fail_str db "failed: ", 0
read_fail_str db "reading from the input file failed: ", 0
write_fail_str db "writing to the output file failed: ", 0
incompl_wr_str db "Incomplete write, probably due to the disk being full.", 0Dh, 0Ah, 0
decode_fail_str db "failed: The input data isn't valid Base64 data.", 0Dh, 0Ah, 0
bin_end_index resw 1
b64_end_index resw 1
in_file_handle resw 1
out_file_handle resw 1
close_in_file db 0
close_out_file db 0
operation db 1 ; 0 means do nothing, 1 means encode,
; 2 means decode.
segment cseg public class=code align=16
PSP: resw 1
..start: mov word [cs:PSP], ds ; Save pgm seg perfix.
mov ax, dseg
mov ds, ax
mov es, ax
mov ax, sseg
mov ss, ax
mov sp, stack_top
xor bp, bp
; The main routine shall set AL as the %ERRORLEVEL%
call near main
mov ah, 4Ch
int 21h
; Process command-line arguments and initialize program state:
;
;
; Inputs:
;
; Text on the command line.
;
; Outputs:
;
; This routine prints the welcome message onto the screen if the cmdline
; arguments were parsed successfully, and informs the user about the status
; of file utilization.
;
; It also prints error messages related to invalid usage.
;
; It also prints the help screen if neccessary, therefore the global
; show_help variable shall be used by the calling routine solely for
; determining whether to prematurely close the program and report success.
;
; NOTE: For the sake of simplicity, this routine doesn't preserve any GPRs.
;
program_init: push bp
mov bp, sp
; Given how in DOS, command-line argumetns are provided to the
; program in a string at most 127 bytes long, for the worst
; case scenario of a filename occupying the entire string, set
; aside 127 + 1 (zero termination) bytes for filenames.
;sub sp, 128 ; [bp-128] - First file name
;sub sp, 128 ; [bp-256] - Second file name
;sub sp, 1 ; [bp-257] - First file name preesnt?
;sub sp, 1 ; [bp-258] - Second file name preesnt?
;sub sp, 1 ; [bp-259] - stdin desired?
;sub sp, 1 ; [bp-260] - stdout desired?
;
; The max. size of a long command-line option, including the
; initial dash, shall be 9 + 1 (zero termination):
;sub sp, 10 ; [bp-270] - Long option buffer
;sub sp, 1 ; [bp-271] - Did a parsing error occur?
;sub sp, 1 ; [bp-272] - Unexpected argument?
; Allocate the above described stack space in a single swoop:
sub sp, 272
; Initialize stack-allocated variables:
mov byte [bp-257], 0
mov byte [bp-258], 0
mov byte [bp-259], 0
mov byte [bp-260], 0
mov byte [bp-271], 0
mov byte [bp-272], 0
; Save the old ES and access the Program Segment Prefix:
mov ax, es
push ax
mov ax, [cs:PSP]
mov es, ax
; Walk through the bytes of the command-line arguments buffer:
xor ch, ch
mov cl, [es:80h]
xor si, si
.main_loop: cmp si, cx
jnb .parsing_done
mov al, [es:81h+si]
inc si
call near char_is_space
jnc .main_loop
cmp al, '-'
jz .read_opts
jmp .read_fname
.read_opts: cmp si, cx
jnb .parsing_done
mov al, [es:81h+si]
inc si
call near char_is_space
jnc .main_loop
cmp al, '-'
jz .read_lopt
cmp al, 'e'
jz .opt_encode
cmp al, 'd'
jz .opt_decode
cmp al, 's'
jz .opt_stdin
cmp al, 'c'
jz .opt_stdout
cmp al, 'h'
jz .opt_help
jmp .opt_invalid
.opt_encode: mov byte [operation], 1
jmp .read_opts
.lopt_encode: mov byte [operation], 1
jmp .lopt_done
.opt_decode: mov byte [operation], 2
jmp .read_opts
.lopt_decode: mov byte [operation], 2
jmp .lopt_done
.opt_stdin: mov byte [bp-259], 1
jmp .read_opts
.lopt_stdin: mov byte [bp-259], 1
jmp .lopt_done
.opt_stdout: mov byte [bp-260], 1
jmp .read_opts
.lopt_stdout: mov byte [bp-260], 1
jmp .lopt_done
.opt_help: mov byte [operation], 0
jmp .read_opts
.lopt_help: mov byte [operation], 0
jmp .lopt_done
.lopt_done: pop ax
mov es, ax
jmp .main_loop
.opt_invalid: mov byte [bp-271], 1
; Since no files have been opened yet, we can use a file
; handle variables to temporarily store things in the data
; segment.
mov [in_file_handle], al
mov bx, stderr
mov dx, invalid_opt_str
call near write_str_zterm
push cx
mov cx, 1
mov dx, in_file_handle
call near dos_syscl_hwrite
mov cx, 2
mov dx, newline_str
call near dos_syscl_hwrite
pop cx
jmp .read_opts
.read_lopt: xor di, di
mov byte [bp-270+di], al
inc di
.rlopt_loop: cmp si, cx
jnb .lopt_loaded
mov al, [es:81h+si]
inc si
call near char_is_space
jnc .lopt_loaded
cmp di, 9 ; The lopt buffer is 9 + 1 (zterm) long.
jnb .lopt_too_long
mov [bp-270+di], al
inc di
jmp .rlopt_loop
.lopt_loaded: mov byte [bp-270+di], 0
lea bx, [bp-270+1] ; +1 for the initial dash.
mov ax, es
push ax
mov ax, ss
mov es, ax ; es:bx is now the loaded lopt.
mov dx, encode_lopt_str
call near str_identity_check
jnc .lopt_encode
mov dx, decode_lopt_str
call near str_identity_check
jnc .lopt_decode
mov dx, stdin_lopt_str
call near str_identity_check
jnc .lopt_stdin
mov dx, stdout_lopt_str
call near str_identity_check
jnc .lopt_stdout
mov dx, help_lopt_str
call near str_identity_check
jnc .lopt_help
pop ax
mov es, ax
jmp .lopt_invalid
.lopt_too_long: mov byte [bp-270+di], 0
.deplete_loop: cmp si, cx
jnb .deplete_complete
mov al, [es:81h+si]
inc si
call near char_is_space
jnc .deplete_complete
jmp .deplete_loop
.deplete_complete:
.lopt_invalid: mov bx, stderr
mov dx, invalid_opt_str
call near write_str_zterm
mov ax, ds
push ax
mov ax, ss
mov ds, ax
lea dx, [bp-270]
call near write_str_zterm
pop ax
mov ds, ax
mov dx, newline_str
call near write_str_zterm
mov byte [bp-271], 1
jmp .main_loop
.read_fname: cmp byte [bp-257], 0 ; Input file known?
jnz .have_file1
cmp byte [bp-259], 0 ; stdin desired?
jnz .have_file1
mov byte [bp-257], 1
lea bx, [bp-128]
jmp .load_fname
.have_file1: cmp byte [bp-258], 0 ; Output file known?
jnz .have_file2
cmp byte [bp-260], 0 ; stdout desired?
jnz .have_file2
mov byte [bp-258], 1
lea bx, [bp-256]
jmp .load_fname
; Unexpected argument? Well, since we'll error out, we won't
; end up opening any files, so we might as well load this
; unexpected argument into the output file name buffer, so that
; we can print it in an error message.
.have_file2: mov byte [bp-272], 1
lea bx, [bp-256]
.load_fname: mov [ss:bx], al
inc bx
.fnload_loop: cmp si, cx
jnb .fname_loaded
mov al, [es:81h+si]
inc si
call near char_is_space
jnc .fname_loaded
mov [ss:bx], al
inc bx
jmp .fnload_loop
.fname_loaded: mov byte [ss:bx], 0
; Sure, we've loaded something, but do we want it?
cmp byte [bp-272], 0
jz .main_loop
mov bx, stderr
mov dx, unexp_arg_str
call near write_str_zterm
mov ax, ds
push ax
mov ax, ss
mov ds, ax
lea dx, [bp-256]
call near write_str_zterm
pop ax
mov ds, ax
mov dx, newline_str
call near write_str_zterm
mov byte [bp-271], 1
jmp .main_loop
.parsing_done: cmp byte [bp-271], 0
jz .parsing_ok
.err_showhelp: mov byte [operation], 0
mov bx, stderr
mov dx, help_screen
call near write_str_zterm
; The input file might be open if we failed to open the
; output file.
.err_nohelp: call near close_files
.err_noiclose: stc
jmp .epilogue
.parsing_ok: cmp byte [operation], 0
jz .show_help
; Sanity check:
cmp byte [bp-257], 0 ; Input file known?
jnz .have_ifile
cmp byte [bp-259], 0 ; stdin desired?
jnz .have_ifile
mov bx, stderr
mov dx, no_ifile_str
call near write_str_zterm
mov byte [bp-271], 1
.have_ifile: cmp byte [bp-258], 0 ; Output file known?
jnz .have_ofile
cmp byte [bp-260], 0 ; stdout desired?
jnz .have_ofile
mov bx, stderr
mov dx, no_ofile_str
call near write_str_zterm
mov byte [bp-271], 1
.have_ofile: cmp byte [bp-271], 0
jnz .err_showhelp
; Now that we've reached this point, we can show the program's
; welcome message:
mov bx, stderr
mov dx, welcome_str
call near write_str_zterm
; Since we'll be switching between segments, have them ready.
mov si, ss
mov di, ds
.ready_ifile: cmp byte [bp-259], 0 ; stdin desired?
jz .open_ifile
mov dx, usng_stdin_str
call near write_str_zterm
mov word [in_file_handle], stdin
mov byte [close_in_file], 0
jmp .ready_ofile
.open_ifile: mov dx, opening_str
call near write_str_zterm
mov ds, si
lea dx, [bp-128]
call near write_str_zterm
mov ds, di
mov dx, opening_if_str
call near write_str_zterm
mov ds, si
lea dx, [bp-128]
mov al, file_read_only
call near dos_syscl_hfopen
mov ds, di
jnc .ifopen_ok
mov cx, ax
mov dx, fail_str
call near write_str_zterm
mov ax, cx
call near write_dossyscl_error
jmp .err_nohelp
.ifopen_ok: mov [in_file_handle], ax
mov byte [close_in_file], 1
mov dx, success_str
call near write_str_zterm
.ready_ofile: cmp byte [bp-260], 0 ; stdout desired?
jz .open_ofile
mov dx, usng_stdout_str
call near write_str_zterm
mov word [out_file_handle], stdout
mov byte [close_out_file], 0
jmp .all_done
.open_ofile: mov dx, opening_str
call near write_str_zterm
mov ds, si
lea dx, [bp-256]
call near write_str_zterm
mov ds, di
mov dx, opening_of_str
call near write_str_zterm
mov ds, si
lea dx, [bp-256]
xor cx, cx
call near dos_syscl_hfcreate
mov ds, di
jnc .ofopen_ok
mov cx, ax
mov dx, fail_str
call near write_str_zterm
mov ax, cx
call near write_dossyscl_error
jmp .err_nohelp
.ofopen_ok: mov [out_file_handle], ax
mov byte [close_out_file], 1
mov dx, success_str
call near write_str_zterm
.all_done: clc
jmp .epilogue
.show_help: mov bx, stdout
mov dx, help_screen
call near write_str_zterm
clc
.epilogue: pop ax
mov es, ax
mov sp, bp
pop bp
retn
; Close open files:
;
; Inputs:
;
; Program state within the data segment.
;
; Outputs:
;
; None.
;
; The values of all GPRs are preserved.
;
close_files: push ax
push bx
cmp byte [close_in_file], 0
jz .no_close_in
mov bx, [in_file_handle]
call near dos_syscl_hclose
mov byte [close_in_file], 0
mov word [in_file_handle], 0
.no_close_in:
cmp byte [close_out_file], 0
jz .no_close_out
mov bx, [out_file_handle]
call near dos_syscl_hclose
mov byte [close_out_file], 0
mov word [out_file_handle], 0
.no_close_out:
pop bx
pop ax
retn
; The main routine of the program:
;
main: call near program_init
jnc .init_ok
; If we failed to initialize, return a non-zero exit status.
mov al, 1
retn
.init_ok: cmp byte [operation], 0
jnz .check_op
; Only printed a help screen? In that case, we're done here.
xor al, al
retn
; Determine which operation to perform:
.check_op: cmp byte [operation], 1
jnz .perform_dec
; Encode the input file:
.perform_enc: mov bx, stderr
mov dx, encoding_str
call near write_str_zterm
mov si, bin_buf_seg
mov di, b64_buf_seg
mov ax, ds
push ax
mov es, ax
.enc_loop: mov ds, si
mov bx, [es:in_file_handle]
mov cx, 48 * 1024
xor dx, dx
call near dos_syscl_hread
jc .read_failed
test ax, ax
jz .done
mov cx, ax
dec cx
mov bx, si
mov dx, di
pop ax
mov ds, ax
push ax
call near conv_bin_b64
mov bx, [es:out_file_handle]
mov ds, di
xor dx, dx
cmp cx, 0FFFFh
jz .split_write
inc cx
call near dos_syscl_hwrite
jc .write_failed
cmp ax, cx
jnz .write_short
jmp .enc_loop
; We have 64K of bytes to write, that's 1 byte more than
; the DOS "handle" write routine can handle; let's split
; it into two 32K writes.
.split_write: mov cx, 32 * 1024
call near dos_syscl_hwrite
jc .write_failed
cmp ax, cx
jnz .write_short
mov dx, cx
call near dos_syscl_hwrite
jc .write_failed
cmp ax, cx
jnz .write_short
jmp .enc_loop
; Decode the input file:
.perform_dec: mov bx, stderr
mov dx, decoding_str
call near write_str_zterm
mov si, b64_buf_seg
mov di, bin_buf_seg
mov ax, ds
push ax
mov es, ax
; Since the Base64 buffer is 64K long, which is 1 byte more
; than the DOS "handle" read routine can handle, fill it using
; two 32K reads:
.dec_loop: mov ds, si
xor dx, dx
mov bx, [es:in_file_handle]
mov cx, 32 * 1024
call near dos_syscl_hread
jc .read_failed
test ax, ax
jz .done
; Did we read less than 32K? No need to read more then.
cmp ax, cx
jb .read2_not_needed
push ax
mov dx, cx
call near dos_syscl_hread
jc .read_failed
pop cx
add ax, cx
.read2_not_needed:
mov cx, ax
dec cx
mov bx, si
mov dx, di
pop ax
mov ds, ax
push ax
call near conv_b64_bin
jc .dec_failed
mov bx, [es:out_file_handle]
mov ds, di
xor dx, dx
inc cx
call near dos_syscl_hwrite
jc .write_failed
cmp ax, cx
jnz .write_short
jmp .dec_loop
.read_failed: mov di, ax
pop ax
mov ds, ax
mov bx, stderr
mov dx, read_fail_str
call near write_str_zterm
mov ax, di
call near write_dossyscl_error
call near close_files
mov al, 2
retn
.dec_failed: pop ax
mov ds, ax
mov bx, stderr
mov dx, decode_fail_str
call near write_str_zterm
call near close_files
mov al, 3
retn
.write_failed: mov di, ax
pop ax
mov ds, ax
mov bx, stderr
mov dx, write_fail_str
call near write_str_zterm
mov ax, di
call near write_dossyscl_error
call near close_files
mov al, 2
retn
.write_short: pop ax
mov ds, ax
mov bx, stderr
mov dx, write_fail_str
call near write_str_zterm
mov dx, incompl_wr_str
call near write_str_zterm
call near close_files
mov al, 2
retn
.done: pop ax
mov ds, ax
mov bx, stderr
mov dx, done_str
call near write_str_zterm
call near close_files
xor al, al
retn
; 48K binary data buffer:
segment bin_buf_seg private class=bss align=16
resb 48 * 1024
; 64K Base64 data buffer:
segment b64_buf_seg private class=bss align=16
resb 64 * 1024
; 2K stack segment:
segment sseg stack class=stack align=16
resb 2048
stack_top: