; hc.asm v1.4           2/10/84
; fast hexconverter
; Copyright (C) 1984 Martin Smith
;
; Fixes odd line problem, further optimized.
;
; Assemble and then use EXE2BIN to make a COM file.
;
; Convert com/exe/bin to hex or back.
; format
; 1) hc file.hex file.com
; 2) hc file.com file.hex
; 3) hc file.hex
;       program examines file for .exe format, else .com
; 4) hc file(.com/.exe/.bin)
;       program outputs file.hex
; 5) hc
;       program prints doc.
; Note: HC does is NOT produce an Intel format HEX file.
;       Rather it is the modulo 2048 single checksum format
;       found on many IBM BBS's.

        title [[hc]] hexconverter
        page 60,100

TRUE    equ     -1
FALSE   equ     0
EOF     equ     26      ; Ctrl-Z
PARAGRAPHS      EQU     256     ; adjust for program size, leaving room
                                ; for a stack. Generally leave alone.
BUFSIZE         EQU     64      ; for args

; DOS function calls used

DISPLAYOUT      EQU     0200H
PRINTSTRING     EQU     0900H
GETDOSVERSION   EQU     3000H
CREAT           EQU     3C00H
OPENFILE        EQU     3D00H
CLOSEFILE       EQU     3E00H
READFILE        EQU     3F00H
WRITEFILE       EQU     4000H
DELETEFILE      EQU     4100H
ALLOCATE        EQU     4800H
FREEMEMORY      EQU     4900H
SETBLOCK        EQU     4A00H

cseg    segment para 'code'
        org     100h                    ; COM program
        assume  cs:cseg,ds:cseg

start   proc    far

begin:
        mov     bp,sp
        mov     cs:oldstack,bp
        test    ax,0ffffh               ; test for invalid drive spec.
        jz      begin_ok
        mov     ax,15                   ; invalid drive
        jmp     report_bad

begin_ok:
        mov     ax,cs                   ; better safe than sorry.
        mov     ds,ax
        mov     es,ax

        mov     dx,offset himess        ; print greeting
        mov     ax,PRINTSTRING
        int     21h
        mov     si,80h                  ; get args
        mov     di,offset arg1
        mov     cl,[si]
        cmp     cl,0                    ; no args
        jnz     ck_for_right_dos
        jmp     do_doc                  ; so print a documentation

ck_for_right_dos:
        push    si
        push    di
        push    cx
        mov     ax,GETDOSVERSION
        int     21h
        pop     cx
        pop     di
        pop     si
        cmp     al,2                    ; dos 2.+ ?
        jnb     dos_ok
        mov     dx,offset must_be_20
        jmp     report_abort

dos_ok:

        mov     sp,16*PARAGRAPHS-1      ; stack not adjusted by
        mov     bp,sp                   ; SETBLOCK, so must do it here.
                                        ; adjust PARAGRAPHS in EQUates to
                                        ; reflect program size.
        mov     ch,0

b2:
        inc     si                      ; ignor leading blanks
        mov     al,[si]
        cmp     al,' '
        jnz     b3
        loop    b2
        jmp     do_doc                  ; do doc if no args

b3:
        mov     [di],al                 ; else copy first arg to our space
        inc     di
        inc     si
        mov     al,[si]
        cmp     al,' '
        jz      b4
        cmp     al,13
        jz      b4
        loop    b3

b4:
        dec     cx              ; got an arg, allow for trailing blanks
        mov     al,0            ; keep up with CX to use LOOP instruction
        mov     [di],al
        mov     byte ptr cs:numargs,1
        cmp     cx,0
        jnz     b5
        jmp     memory_setup

b5:
        mov     di,offset arg2  ; after this its arg2
        dec     cx              ; keep up with CX
        cmp     cx,0
        jnz     b6
        jmp     memory_setup

b6:
        inc     si              ; ignor leading blanks
        mov     al,[si]
        cmp     al,' '
        jnz     b7
        loop    b6
        jmp     memory_setup    ; go to next if nothing

b7:
        mov     [di],al         ; else copy input to our space.
        inc     di
        inc     si
        mov     al,[si]
        cmp     al,' '
        jz      b8
        cmp     al,13
        jz      b8
        loop    b7

b8:
        mov     al,0            ; got an arg, put \0 for ASCIZ
        mov     [di],al
        mov     byte ptr cs:numargs,2
        jmp     memory_setup    ; ignor any more

start   endp

do_doc  proc    near

        mov     dx,offset doc   ; print until $
        mov     ax,PRINTSTRING
        int     21h
        mov     dx,offset do_cr
        jmp     report_abort    ; exit

do_doc  endp

report_bad      proc    near
;
; use AX error return to point DX to message from
; error return table
;
        push    cs
        pop     ds
        shl     ax,1
        mov     si,offset error
        add     si,ax
        mov     dx,[si]

report_bad      endp

report_abort    proc    near
;
; print message from DX, restore regs and abort.
; common exit, neatness counts.
; plus we've screwed around with the stack.
;
        assume  ds:cseg

        mov     bp,cs:oldstack
        mov     sp,bp
        mov     ax,cs
        mov     ds,ax
        mov     es,ax
        mov     ax,PRINTSTRING
        int     21h
        int     20h                     ; common exit all routines

report_abort    endp

hexbin  proc    near
;
;       Convert HEX to binary
;
; Call with     DL = first HEX digit
;               DH = second HEX digit
;
; Returns with  DL = converted byte
;               Carry flag = 0  valid input
;               Carry flag = 1  invalid input
;               Other registers preserved.
;
;       From Dr. Dobbs #74
;       December 1982  p.14
;       by Robert Blair
;
        push    cx
        push    bx
        mov     bl,dl
        call    hexbin_test
        mov     bh,bl
        mov     bl,dh
        call    hexbin_test
        mov     cx,4
        shl     bh,cl
        and     bl,00001111b
        mov     dl,bh
        or      dl,bl
        clc
        pop     bx
        pop     cx
        ret
hexbin_test:
        cmp     bl,'0'
        jb      hexbin_error
        cmp     bl,'9'
        ja      $+3
        ret
;       cmp     bl,'f'          ; don't swap capitols here
;       ja      hexbin_error
;       cmp     bl,'a'          ; lowercase is allright after all.
;       jb      $+6
;       add     bl,9            ; uncomment this if they start up with
;       ret                     ; lowercase hex files.
        cmp     bl,'A'
        jb      hexbin_error
        cmp     bl,'F'
        ja      hexbin_error
        add     bl,9
        ret
hexbin_error:
        clc
        cmc
        pop     bx
        pop     bx
        pop     cx
        ret
hexbin  endp

binhex  proc    near
;
; Convert binary to hex
;
; Call with     DL = byte to convert
;
; Returns with  DL = first HEX digit
;               DH = second HEX digit
;               Other registers preserved
;
        push    cx
        push    bx
        mov     cx,4
        mov     dh,dl
        shr     dl,cl
        and     dh,00001111b
        sub     bh,bh
        mov     bl,dl
        mov     dl,cs:translate_hex [bx]
        mov     bl,dh
        mov     dh,cs:translate_hex [bx]
        pop     bx
        pop     cx
        ret

translate_hex   db      '0123456789ABCDEF'

binhex  endp


print   proc    near
;
; print string until \0
; SI points to first character
; All other regs preserved
; String in CS
;
        push    ds
        push    ax
        push    dx
        mov     ax,cs
        mov     ds,ax

pr1:
        mov     dl,[si]
        cmp     dl,0
        jz      pr2
        mov     ax,DISPLAYOUT
        int     21h
        inc     si
        jmp     pr1

pr2:
        mov     dl,' '                  ; insert space for neatness.
        mov     ax,DISPLAYOUT
        int     21h
        pop     dx
        pop     ax
        pop     ds
        ret

print   endp

crlf    proc    near
                                ; send a carriage return/line feed
        push    dx              ; to console.
        push    ax
        mov     ax,DISPLAYOUT
        mov     dl,13
        int     21h
        mov     ax,DISPLAYOUT
        mov     dl,10
        int     21h
        pop     ax
        pop     dx
        ret

crlf    endp

free_memory     proc    near

        push    ax              ; gives back memory we allocated,
        mov     ax,cs:comseg    ; preserves AX, which maybe has
        mov     es,ax           ; error code.
        mov     ax,FREEMEMORY
        int     21h
        jc      fm_bad
        mov     ax,cs:txtseg
        mov     es,ax
        mov     ax,FREEMEMORY
        int     21h
        jc      fm_bad
        pop     ax
        ret

fm_bad:
        jmp     report_bad

free_memory     endp


memory_setup    proc    near
;
;       request 96k, adjust for less
;
        assume  ds:nothing
        mov     bx,PARAGRAPHS           ; adjust to actual. Use equ at top
        mov     ax,SETBLOCK
        int     21h                     ; SETBLOCK - shrink us
        jnc     msa
        jmp     report_bad

msa:
        mov     bx,1800h                ; ask for 96k
        mov     ax,ALLOCATE
        int     21h
        jnc     msb
        cmp     ax,8                    ; insufficient memory
        jz      ms1

msb:
        mov     bx,1800h                ; got our request
        jmp     ms2

ms1:
        push    bx
        mov     ax,ALLOCATE
        int     21h                     ; so ask for less
        jnc     msc
        jmp     report_bad              ; still fouled up?

msc:
        pop     bx

ms2:
        mov     cs:txtseg,ax
        mov     es,ax                   ; now have allocated total space
        mov     dx,0
        mov     ax,bx
        mov     cx,3
        div     cx                      ; divide by three
        dec     ax                      ; safety factor
        mov     cs:space,ax
        mov     bx,ax
        shl     bx,1                    ; multiply by 2
        mov     ax,SETBLOCK             ; shrink our total space
        int     21h
        jnc     ms2a
        jmp     report_bad              ; get out if error after all this

ms2a:
        mov     bx,cs:space
        mov     ax,ALLOCATE             ; allocate second block
        int     21h
        jnc     ms3
        jmp     report_bad

ms3:
        mov     cs:comseg,ax
        mov     bx,cs:space             ; get 1/3 of paragraphs
        mov     cl,4
        shl     bx,cl                   ; times 16 = bytes available
        dec     bx                      ; sub 2 for safety
        dec     bx
        mov     cs:comspace,bx          ; com file put here in either
        inc     bx                      ; routine.
        inc     bx
        shl     bx,1                    ; times 2 = text space
        dec     bx
        dec     bx
        mov     cs:txtspace,bx          ; hex file always in this one.
        mov     ax,cs:txtspace          ; [bp-2]  structure for easy access
                                        ;         to end of space.
        push    ax                      ;         Valid in either routine.
        mov     ax,cs:comspace          ; [bp-4]
        push    ax
        mov     ax,cs                   ; don't use blocks yet, have
        mov     ds,ax                   ; to do file setup.
        mov     es,ax
        jmp     open_files

memory_setup    endp

write_hc        proc    near
        assume  ds:nothing              ; universal write to file routine.
        push    cx                      ; Saves registers used in time
        push    bx                      ; critical loops, provides safe
        push    ds                      ; error exits. Most of the errors
        push    es                      ; in this program will come from
        pop     ds                      ; disk, so take care here.
        mov     dx,0
        mov     cx,di
        mov     bx,cs:handle2           ; assume file already open for write
        mov     ax,WRITEFILE
        int     21h
        jnc     whc1
        push    cs
        pop     ds
        mov     si,offset arg2          ; lots of messages for foul ups
        call    print
        call    crlf
        shl     ax,1
        mov     si,offset error
        add     si,ax
        mov     dx,[si]
        mov     ax,PRINTSTRING
        int     21h
        jmp     bad_input               ; bad_input tidies up mess.

whc1:
        cmp     di,ax
        jnz     wh_full
        mov     ax,0
        add     cs:f_size,di
        adc     cs:f_size+2,ax
        mov     di,0
        pop     ds
        pop     bx
        pop     cx
        ret

wh_full:
        mov     si,offset cs:disk_full  ; if disk is full,
        call    print
        call    crlf
        call    free_memory
        mov     bx,cs:handle2
        mov     ax,CLOSEFILE            ; close file,
        int     21h
        jnc     wh_full2
        jmp     report_bad

wh_full2:
        push    cs
        pop     ds
        mov     dx,offset arg2
        mov     ax,DELETEFILE           ; then delete it from disk.
        int     21h                     ; save user some time.
        jnc     wh_full3
        jmp     report_bad

wh_full3:
        mov     dx,offset cs:cancelled
        jmp     report_abort

write_hc        endp

hcread  proc    near
                                        ; universal read from file routine.
        push    bx                      ; just slight differences for
        mov     cx,cs:txtspace          ; com->hex and hex<-com.
        push    cx
        jmp     read_input

hcread  endp

chread  proc    near

        push    bx
        mov     cx,cs:comspace
        push    cx
        jmp     read_input

chread  endp

read_input      proc    near

        mov     bx,cs:handle1
        mov     dx,0
        mov     ax,READFILE
        int     21h                     ; read from file
        jnc     ri_ok
        push    ax                      ; if we come back with the
        push    cs                      ; carry flag set, things didn't
        pop     ds                      ; go allright, so we had better
        mov     si,offset arg1          ; not continue
        call    print
        call    crlf
        mov     bx,cs:handle2
        mov     ax,CLOSEFILE            ; close 2
        int     21h
        call    free_memory
        mov     dx,offset arg2
        mov     ax,DELETEFILE           ; delete
        int     21h
        mov     dx,offset cs:cancelled
        mov     ax,PRINTSTRING
        int     21h
        pop     ax
        jmp     report_bad

ri_ok:

        cmp     ax,0                    ; EOF and no checksum?
        jnz     ri1                     ; special case for HEX file.
        cmp     byte ptr cs:com_to_hex,TRUE   ; its all right to not have
        jz      ri1                     ; a checksum, but theres no
        jmp     no_hc_checksum          ; sense in heading back to
                                        ; the process loop.
ri1:
        mov     si,ax
        mov     byte ptr [si],0ffh      ; set last byte to ff for check
        pop     cx
        pop     bx
        mov     si,0
        cmp     ax,cx                   ; all read in?
        jb      ri_done

        ret

ri_done:
        mov     byte ptr cs:all_read,TRUE  ; set flag indicating no more
        cmp     byte ptr cs:com_to_hex,TRUE  ; reading required
        jnz     ri_hc
        mov     cs:comspace,ax          ; store maxbytes all over
        mov     [bp-4],ax
        mov     cx,ax
        ret

ri_hc:
        mov     cs:txtspace,ax
        mov     [bp-2],ax
        mov     cx,ax
        ret

read_input      endp


open_files      proc    near


        mov     ax,cs
        mov     ds,ax
        mov     cx,BUFSIZE*2            ; make args lowercase
        mov     si,offset arg1

of2:
        mov     al,[si]
        cmp     al,'A'
        jl      of3
        cmp     al,'Z'
        ja      of3
        add     al,32
        mov     [si],al

of3:
        inc     si
        loop    of2

        cmp     byte ptr cs:numargs,2      ; have to find which is hex
        jz      of4
        jmp     find_files

of4:
        mov     si,offset arg1

of4a:

        mov     al,[si]
        cmp     al,'.'
        jz      of5
        inc     si
        cmp     al,0            ; end of arg and no extension
        jnz     of4a
        mov     byte ptr cs:com_to_hex,TRUE ; better be true
        jmp     of10

of5:
        inc     si
        mov     al,[si]
        cmp     al,'h'          ; is this hex?
        jz      of6
        mov     byte ptr cs:com_to_hex,TRUE ; set flag
        jmp     of10

of6:
        mov     byte ptr cs:com_to_hex,FALSE  ; first arg is HEX,
        jmp     open                          ; so its hex_to_com.

of10:
        mov     si,offset arg2

of11:
        mov     al,[si]
        cmp     al,'.'          ; look for .
        jz      of12
        inc     si
        cmp     al,0            ; end of arg and no extension
        jnz     of11
        call    free_memory
        mov     dx,offset need_hex   ; two args, one has to be hex
        jmp     report_abort

of12:
        inc     si
        mov     al,[si]
        cmp     al,'h'          ; here this better be HEX
        jz      of13
        call    free_memory     ; otherwise bye bye
        mov     dx,offset need_hex
        jmp     report_abort

of13:
        jmp     open

find_files:                     ; only one arg supplied, have to find which
        mov     si,offset arg1
        mov     di,offset arg2

ff1:
        mov     al,[si]         ; search for extension
        mov     [di],al
        cmp     al,'.'
        jz      ff2
        inc     si
        inc     di
        cmp     al,0            ; no ext means com_to_hex
        jnz     ff1

        dec     di

        mov     byte ptr cs:com_to_hex,TRUE ; make second arg HEX
        mov     al,'.'
        mov     [di],al

ff1a:
        mov     al,'h'
        inc     di
        mov     [di],al
        mov     al,'e'
        inc     di
        mov     [di],al
        mov     al,'x'
        inc     di
        mov     [di],al
        mov     al,0
        inc     di
        mov     [di],al
        jmp     open

ff2:
        inc     si
        inc     di
        mov     cs:temp,di
        mov     al,[si]
        cmp     al,'h'          ; is it hex?
        jz      ff3

        mov     byte ptr cs:check_exe,FALSE ; no, make second HEX
        mov     byte ptr cs:com_to_hex,TRUE
        dec     di
        jmp     ff1a

ff3:
        mov     byte ptr cs:check_exe,TRUE
        mov     byte ptr cs:com_to_hex,FALSE

open:           ; we now have 2 args, with one possibly waiting
                ; for COM or EXE extension
        assume  ds:cseg
        mov     ax,cs
        mov     ds,ax
        cmp     byte ptr cs:com_to_hex,TRUE
        jz      openc
        jmp     openh

openc:
        mov     dx,offset arg1
        mov     ax,OPENFILE     ; for reading
        clc
        int     21h
        jnc     opcok
        mov     si,offset arg1
        call    print
        call    free_memory
        jmp     report_bad

opcok:
        mov     cs:handle1,ax

        mov     dx,offset arg2
        mov     cx,0            ; normal file attribute
        mov     ax,CREAT
        clc
        int     21h
        jnc     opcok1
        mov     si,offset arg2  ; print which file caused error
        call    print
        call    free_memory
        jmp     report_bad

opcok1:
        mov     cs:handle2,ax   ; otherwise store handle
        jmp     com_hex         ; do com->hex

openh:
        mov     dx,offset arg1
        mov     ax,OPENFILE     ; for reading
        clc
        int     21h
        jnc     ophok
        mov     si,offset arg1
        call    print
        jmp     report_bad

ophok:
        mov     cs:handle1,ax
        cmp     byte ptr cs:check_exe,TRUE
        jz      opcheck
        jmp     ophok1

opcheck:
        assume  ds:nothing      ; here we read in first block
        mov     ax,cs:txtseg    ; then look for the  MZ
        mov     ds,ax           ; footprint of an EXE file.

        call    hcread

opcheck2:
        mov     si,0

opchl:
        mov     dl,[si]         ; ignor all leading stuff until
        cmp     dl,13           ; first convertible bytes
        jz      opch1
        cmp     dl,10
        jz      opch1
        cmp     dl,' '
        jz      opch1
        cmp     dl,';'
        jnz     opch4
        call    op_find

        jmp     opchl

opch1:
        inc     si
        jmp     opchl

opch4:

        inc     si
        mov     dh,[si]
        call    hexbin          ; hexbin comes back with carry set
        jnc     opch5           ; if it can't convert input.
        jmp     opcom           ; can't make sense, so make com

opch5:
        mov     al,dl
        inc     si
        mov     dx,[si]
        call    hexbin
        jnc     opch6
        jmp     opcom

opch6:
        mov     dh,dl
        mov     dl,al
        push    cs
        pop     ds
        cmp     dx,5a4dh        ; MZ are the first two bytes of an EXE file.
        jz      opexe           ; make ext EXE
        jmp     opcom           ; make ext COM

op_find:
        inc     si              ; simple ignor
        mov     dl,[si]         ; we can convert files downloaded
        cmp     dl,13           ; without line feeds.
        jnz     op_find
        inc     si
        mov     dl,[si]
        cmp     dl,10
        jnz     opf1
        inc     si

opf1:
        ret

opexe:
        mov     si,cs:temp
        mov     al,'e'
        mov     [si],al
        inc     si
        mov     al,'x'
        mov     [si],al
        inc     si
        mov     al,'e'
        mov     [si],al
        inc     si
        mov     al,0
        mov     [si],al
        jmp     ophok1

opcom:
        mov     si,cs:temp
        mov     al,'c'
        mov     [si],al
        inc     si
        mov     al,'o'
        mov     [si],al
        inc     si
        mov     al,'m'
        mov     [si],al
        inc     si
        mov     al,0
        mov     [si],al

ophok1:
; now have second arg for sure
        mov     dx,offset arg2
        mov     cx,0
        mov     ax,CREAT        ; CREATE a file
        clc
        int     21h
        jnc     ophok2
        mov     si,offset arg2
        call    print
        call    free_memory
        jmp     report_bad

ophok2:
        mov     cs:handle2,ax

open_files      endp

hex_com         proc    near

        mov     ax,cs:txtseg    ; finally use our memory set-up
        mov     ds,ax
        mov     ax,cs:comseg    ; DS always input, ES always output
        mov     es,ax
                                ; first check if data already in buffer

        mov     al,byte ptr cs:check_exe
        cmp     al,TRUE
        jnz     hc1
        jmp     hc_read

hc1:
        call    hcread

hc_read:
        mov     si,0
        mov     di,0
        mov     bx,0            ; checksum stored in BX, don't lose it
        mov     cx,cs:txtspace

hcloop:
        mov     dl,[si]         ; most used routine, process stuff till
        cmp     dl,13           ; out of space in buffer or EOF
        jz      hclcont
        cmp     dl,10
        jz      hclcont
        cmp     dl,' '
        jz      hclcont
        cmp     dl,';'
        jz      hcl8

hcl1:
        cmp     dl,0ffh         ; if we run out of data,
        jz      hcl6

hcl2:
        inc     si

hcl3:
        mov     dh,[si]
        cmp     dh,0ffh
        jz      hcl5
        call    hexbin          ; if nothing else works it must be data
        jc      bad_i

hcl4:
        mov     es:[di],dl      ; store
        xor     dh,dh
        add     bx,dx           ; checksum
        and     bx,07ffh
        inc     di
        cmp     di,[bp-4]       ; output buffer size in [bp-4]
        jz      hcl7

hclcont:
        inc     si

        jmp     hcloop

hcl5:
        push    dx
        call    hcread          ; if we fall through here, we need more
        pop     dx
        jmp     hcl3            ; data, then go back for more loop.

hcl6:
        call    hcread          ; go dig up some more.
        jmp     hcloop

hcl7:
        call    write_hc        ; write buffer when full.
        jmp     hclcont

hcl8:
        sub     cx,si           ; checksum uses cx
        call    checksum        ; may or may not return to here
        jmp     hclcont
bad_i:
        push    cs
        pop     ds
        mov     si,offset arg1
        call    print              ; if hexbin comes back invalid
        call    crlf               ; abort routine and clean up.
        mov     dx,offset not_hex
        mov     ax,PRINTSTRING
        int     21h

bad_input:
        call    free_memory     ; restores memory to original,
        mov     bx,cs:handle1   ; closes both files,
        mov     ax,CLOSEFILE    ; then deletes output file.
        int     21h
        jnc     bi1
        jmp     report_bad

bi1:
        mov     bx,cs:handle2
        mov     ax,CLOSEFILE
        int     21h
        jnc     bi2
        jmp     report_bad

bi2:
        push    cs
        pop     ds
        mov     dx,offset cs:arg2
        mov     ax,DELETEFILE
        int     21h             ; delete output file
        jnc     bi3
        jmp     report_bad

bi3:
        mov     dx,offset cs:cancelled
        jmp     report_abort


hex_com endp

checksum        proc    near
;
; si points to ';'
; check for checksum, else read to end of line
;
        push    es
        push    di
        mov     ax,cs
        mov     es,ax
        cld
        mov     dh,0
        inc     si
        cmp     dl,0ffh         ; its possible to hit end of buffer
        jnz     ch0             ; during this, so we need to check
        call    hcread          ; and cope.

ch0:
        mov     dl,[si]         ; we can convert ;checksum
        cmp     dl,'c'          ;             or ;CHECKSUM
        jnz     ch0a
        mov     di,offset cs:lchksum+1
        jmp     ch1

ch0a:
        cmp     dl,'C'
        jnz     ch_read_end
        mov     di,offset cs:uchksum+1

ch1:
        cmpsb                   ; compare till the bitter end.
        jnz     ch_read_end
        dec     cx
        cmp     cx,0
        jnz     ch2
        call    hcread

ch2:
        inc     dh
        cmp     dh,8
        jnz     ch1
        pop     di
        pop     es              ; this is it.
        pop     ax              ; discard return, no longer need hcloop
        jmp     dhc1            ; found checksum word

ch_read_end:
        dec     si

cre1:
        mov     dl,[si]         ; no find, so read till end
        cmp     dl,13           ; of line and go back to hcloop
        jz      cre_back
        inc     si
        dec     cx
        cmp     cx,0
        jnz     cre2
        call    hcread

cre2:
        jmp     cre1

cre_back:
        pop     di
        pop     es
        ret

dhc1:
        mov     dl,[si]         ; ignor leading blanks
        cmp     dl,' '
        jnz     dhc2
        inc     si
        dec     cx
        cmp     cx,0
        jnz     dhc1
        call    hcread          ; get more text if end of buffer
        jmp     dhc1

dhc2:
        push    cx              ; convert ASCII checksum to binary
        mov     ax,0
        mov     cx,10
        mov     dh,0

dhc3:
        mov     dl,[si]         ; read numbers until no number
        cmp     dl,'0'
        jae     dhc4
        jmp     dhc_found

dhc4:
        cmp     dl,'9'
        jbe     dhc5
        jmp     dhc_found

dhc5:
        push    dx              ; the old multiply by ten routine
        mov     dx,0            ; to convert ASCII number string
        mul     cx              ; into binary
        pop     dx
        sub     dl,'0'
        add     ax,dx
        inc     si
        pop     cx
        dec     cx              ; CX still has the loop or buffer count
        cmp     cx,0
        jnz     dhc5a
        call    hcread          ; oops, ran out of buffer, so fill up again.

dhc5a:
        push    cx
        mov     cx,10
        jmp     dhc3

dhc_found:
        pop     cx              ; got a checksum.
        cmp     bx,ax           ; does checksum check out?
        jz      dhc_ok
        mov     si,offset cs:chk_bad
        call    print
        call    crlf
        jmp     dhc_cont        ; report either way.

dhc_ok:
        mov     si,offset cs:chk_ok
        call    print
        call    crlf
        jmp     dhc_cont

no_hc_checksum:
        mov     si,offset chk_no  ; its OK not to have a checksum,
        call    print           ; but we're more confident if its there
        call    crlf

dhc_cont:
        call    write_hc        ; write final section.

dhcok1:
        mov     bx,cs:handle2
        mov     ax,CLOSEFILE
        int     21h             ; close outfile
        jnc     dhcok2
        call    free_memory     ; hope things don't bomb after all this
        jmp     report_bad

dhcok2:
        mov     bx,cs:handle1
        mov     ax,CLOSEFILE
        int     21h             ; close infile
        jnc     dhcok3
        call    free_memory
        jmp     report_bad

dhcok3:

        call    free_memory     ; give back memory
        mov     si,offset cs:arg2
        call    print
        call    crlf
        call    print_file_size
        mov     dx,offset cs:msuccess  ; print a reassuring message
        jmp     report_abort    ; clean up and go home.

checksum  endp

print_file_size proc    near
                                ; print out file size bytes using
        assume  ds:cseg         ; bcd table
        push    cs
        pop     ds
        mov     bx,offset cs:bcd0
        mov     si,0
        mov     ax,cs:f_size
        mov     dx,1

pfs1:
        test    ax,dx           ; test bits
        jz      pfs2
        call    add_bcd         ; add in bcd if so

pfs2:
        add     si,5            ; bump pointer
        cmp     dx,8000h        ; test for end
        jz      pfs3
        shl     dx,1            ; get next test
        jmp     pfs1

pfs3:
        mov     ax,cs:f_size+2  ; get hi word
        mov     dx,1            ; start again

pfs4:
        test    ax,dx
        jz      pfs5
        call    add_bcd

pfs5:
        add     si,5
        cmp     dx,0100h        ; only need up to 16meg
        jz      pfs6
        shl     dx,1
        jmp     pfs4

pfs6:
        mov     si,offset bf_size
        call    print_bcd       ; print out result
        mov     si,offset cs:f_len
        call    print           ; print 'bytes recorded'
        call    crlf
        ret                     ; back

add_bcd:
        push    si
        push    ax
        mov     di,offset bf_size  ; point to bcd storage
        mov     cx,5            ; its five bytes long
        clc                     ; clear carry to start

ab1:
        mov     al,[bx+si]      ; get byte
        adc     al,[di]         ; add
        daa                     ; adjust for bcd
        mov     [di],al         ; store
        inc     si              ; bump
        inc     di
        dec     cx              ; check
        jnz     ab1             ; leave if done

        pop     ax
        pop     si
        ret

print_bcd:
        add     si,4            ; go to end (high byte)
        mov     cx,5            ; set up for five total
        mov     ax,0            ; AH used to check first zero

pbd1:
        mov     dl,[si]         ; get byte
        cmp     ah,1            ; see if we've started to print
        jz      pbd2            ; if so don't skip
        cmp     dl,0            ; else check if zero
        jz      pbd3            ; and skip if so

pbd2:
        mov     dh,dl           ; copy
        push    cx              ; don't lose place
        push    ax
        mov     cl,4            ; bcd:( 12 hex. = 12 dec. for example)
        shr     dl,cl           ; shr makes ( 01 )
        cmp     ah,1            ; if printing print
        jz      pbd2a
        cmp     dl,0            ; else see if zero
        jz      pbd2c           ; and don't print if so

pbd2a:
        add     dl,30h          ; make ASCII ( '1' )
        push    dx
        mov     ax,DISPLAYOUT   ; print
        int     21h
        pop     dx

pbd2c:
        mov     dl,dh           ; get copy ( 12 )
        and     dl,0fh          ; mask     ( 02 )
        add     dl,30h          ; make ASCII ( '2' )
        mov     ax,DISPLAYOUT   ; print
        int     21h
        pop     ax
        pop     cx
        mov     ah,1            ; now set flag to print rest

pbd3:
        dec     si
        dec     cx
        jnz     pbd1            ; loop till done.
        ret

print_file_size endp


com_hex         proc    near
; files are open, nothing read in yet.

        mov     ax,cs:txtseg    ; do everything in reverse
        mov     es,ax
        mov     ax,cs:comseg    ; remember in-buffer = DS
        mov     ds,ax           ;         out-buffer = ES
        mov     si,0
        mov     di,0
        mov     bx,0

        call    chread
        mov     ah,0            ; use for line length
                                ; need to put crlf every 32(64) bytes.
chloop:
        mov     dl,[si]         ; this is much simpler than hex->com
        mov     dh,0            ; because there is no extraneous stuff
        add     bx,dx           ; chloop is time-critical, cause
        and     bx,07ffh        ; its 90% of routine
        call    binhex
        mov     es:[di],dx
        inc     ah
        cmp     ah,32
        jnz     ch2a
        inc     di
        inc     di
        cmp     di,[bp-2]       ; out of space in text buffer?
        jnz     ch1a
        call    write_hc        ; clean it out.

ch1a:
        mov     ax,0a0dh        ; crlf
        mov     es:[di],ax
        mov     ah,0

ch2a:
        inc     si
        inc     di
        inc     di
        cmp     di,[bp-2]       ; this buffer is an even number so
        jnz     ch3             ; we don't need to worry about
        push    ax              ; odd addresses like in hex->com
        call    write_hc        ; [bp-2] = always text buffer
        pop     ax              ; [bp-4] = always com buffer

ch3:
        loop    chloop

        cmp     byte ptr cs:all_read,TRUE  ; if its all read in
        jz      ch4                        ; we are almost done.
        push    ax
        call    chread          ; else read next and continue
        pop     ax              ; chread will set CX to length of data
        cmp     cx,0            ; tried to read past EOF?
        jz      ch4             ; then we're finished with loop.
        jmp     chloop

ch4:
        push    ax
        call    write_hc        ; clear buffer to make life simpler
        pop     ax
        cmp     ah,0            ; just did EOL?
        jz      ch4a
        mov     ax,0a0dh        ; skip extra crlf
        mov     es:[di],ax
        inc     di
        inc     di

ch4a:
        mov     ax,0a0dh
        mov     es:[di],ax
        inc     di
        inc     di

        push    cs              ; write out ;checksum
        pop     ds
        mov     si,offset cs:lchksum
        mov     cx,10
        cld

        rep     movsb           ; ;checksum

        mov     ax,bx           ; compute ASCII representation of
        mov     dx,0            ; checksum and make it neat.
        mov     cx,1000
        div     cx
        mov     ch,0
        cmp     ax,0            ; no 1000s?
        jz      ch5
        add     al,30h          ; ASCII 0
        mov     es:[di],al
        inc     di
        mov     ch,1

ch5:
        mov     ax,dx           ; remainder
        mov     cl,100
        div     cl
        cmp     ch,1
        jz      ch5a
        cmp     al,0            ; no 100s?
        jz      ch6

ch5a:
        add     al,30h
        mov     es:[di],al
        inc     di
        mov     ch,1

ch6:
        mov     al,ah
        mov     ah,0
        mov     cl,10
        div     cl
        cmp     ch,1
        jz      ch6a
        cmp     al,0            ; no 10s?
        jz      ch7

ch6a:
        add     al,30h
        mov     es:[di],al
        inc     di

ch7:
        mov     al,ah           ; must print at least one number
        add     al,30h
        mov     es:[di],al
        inc     di
        mov     ax,0a0dh        ; crlf
        mov     es:[di],ax
        inc     di
        inc     di
        mov     al,EOF          ; Ctrl-Z
        mov     es:[di],al
        inc     di

        call    write_hc
        jmp     dhcok1          ; close and finish


com_hex         endp

messages        proc    near

himess  db      '[[hc]]  MS-DOS 2.+ hexconverter,',13,10
        db      '  v1.4 by Marty Smith.',13,10,13,10,'$'


need_hex        db      'One of files must be .HEX',13,10,'$'
not_hex         db      'Not a hex format file.',13,10,'$'
lchksum         db      ';checksum '   ; 10 bytes
uchksum         db      ';CHECKSUM '
msuccess        db      'Successfully created!',13,10,'$'
chk_ok          db      'Checksum verified.',0
chk_bad         db      'Checksums different,',13,10
                db      'file possibly bad.',0
chk_no          db      'No checksum found.',0
disk_full       db      'Disk full.',0
cancelled       db      'Please check file.',13,10,'$'
f_len           db      ' bytes recorded.',0
must_be_20      db      '** Must be running DOS 2.+ **',13,10,'$'
do_cr           db      13,10,'$'

; DOS error returns we may encounter, others return 'unknown error'

err0    db      'unknown error.',13,10,'$'
err1    db      'bad function number.',13,10,'$'
err2    db      'file not found.',13,10,'$'
err3    db      'path not found.',13,10,'$'
err4    db      'too many files.',13,10,'$'
err5    db      'access denied.',13,10,'$'
err6    db      'invalid handle.',13,10,'$'
err7    db      'memory blocks damaged.',13,10,'$'
err8    db      'insufficient memory.',13,10,'$'
err9    db      'bad memory block address.',13,10,'$'
err12   db      'bad access code.',13,10,'$'
err15   db      'bad drive specified.',13,10,'$'
err17   db      'not same device.',13,10,'$'
err18   db      'no more files.',13,10,'$'

error   dw      err0,err1,err2,err3,err4,err5,err6,err7,err8,err9,err0
        dw      err0,err12,err0,err0,err15,err0,err17,err18

f_size          dw      0               ; dword file size
                dw      0
txtseg          dw      0               ; memory management
txtspace        dw      0
comseg          dw      0
comspace        dw      0
space           dw      0               ; temp for figuring workspace
handle1         dw      0               ; file handles
handle2         dw      0
temp            dw      0               ; scratch
oldstack        dw      0               ; store orig. stack ptr.

numargs         db      0               ; command line number of args.
arg1            db      BUFSIZE dup (' ')  ; plenty of space for args
arg2            db      BUFSIZE dup (' ')  ; and PATHs
com_to_hex      db      0               ; flags
check_exe       db      0
all_read        db      0

bf_size  db     5 dup (0)               ; bcd file size

bcd0    db      01h,00h,00h,00h,00h     ; five byte bcd power of 2
        db      02h,00h,00h,00h,00h     ; 2^0 to 2^24
        db      04h,00h,00h,00h,00h     ; for showing file size
        db      08h,00h,00h,00h,00h     ; up to 16meg MS-DOS limit
        db      16h,00h,00h,00h,00h
        db      32h,00h,00h,00h,00h
        db      64h,00h,00h,00h,00h
        db      28h,01h,00h,00h,00h
        db      56h,02h,00h,00h,00h
        db      12h,05h,00h,00h,00h
        db      24h,10h,00h,00h,00h
        db      48h,20h,00h,00h,00h
        db      96h,40h,00h,00h,00h
        db      92h,81h,00h,00h,00h
        db      84h,63h,01h,00h,00h
        db      68h,27h,03h,00h,00h
        db      36h,55h,06h,00h,00h
        db      72h,10h,13h,00h,00h
        db      44h,21h,26h,00h,00h
        db      88h,42h,52h,00h,00h
        db      76h,85h,04h,01h,00h
        db      52h,71h,09h,02h,00h
        db      04h,43h,19h,04h,00h
        db      08h,86h,38h,08h,00h
        db      16h,72h,77h,16h,00h

; doc at end so it can be used for stack space.

doc     db      'Converts HEX format files to COM,EXE or BIN.',13,10
        db      '  or COM,EXE or BIN files to HEX.',13,10,13,10
        db      '  Type: >hc in_file [out_file]',13,10,13,10
        db      'If TWO files are entered, ONE of them MUST be HEX,',13,10
        db      '  i.e. xxx.HEX xxx.COM = Hex to Com, xxx.EXE xxx.HEX = Exe to Hex.',13,10,13,10
        db      'If ONE file is entered, the following occurs:',13,10
        db      '   If the first file has no extension, or it is non-HEX:',13,10
        db      '       A second file is made in HEX format, with an extension .HEX.',13,10
        db      '   If the first file has a .HEX extension:',13,10
        db      '       The program will automatically provide a .COM or .EXE extension,',13,10
        db      '       depending on file type.'
        db      '$'

messages        endp

cseg    ends                    ; that's all folks
        end     start
