Comment ~
        X2B.ASM
        A replacement for the Exe2Bin program, which is NOT included with
        PC-DOS 3.3.
        written by Henry T. Nettles, Dec. 21, 1987 -- Feb. 20, 1988
        compiled with MicroSoft Macro Assembler, version 5.0
        The inspiration (and model) for this program was EXE2COM.C, written
        by Chris Dunford.
        The include module, DOS.INC, comes with MASM 5.0
        The subroutine BinToStr is borrowed from the SHOW.ASM program which
        also comes with MASM 5.0
        Some of this code (the command line parser) is borrowed from a program
        by Vernon Buerg (TABS.ASM)
        This program is hereby released to the public domain.

        Usage:  >X2B  [d:][\path\]PROG[.EXE]  [d:][\path\][PROG][.COM]

        The only necessary parameter is the file name of the input file.  A
        drive and/or path may be given if needed.
        If no extension is given, .EXE is assumed.  If no ouput file is
        given, then the same file name with an extension of .COM is used.
        WARNING: If a drive and/or path is given for the input file, and
        no ouput file is given, then the input drive and/or path will be
        used for the ouput (not the default directory).

        This program works on my computer, on the files I have tried it on.
        However, extensive testing has not been performed, and I assume no
        responsibility whatever for the program.  I would be interested in
        bug reports, and if anyone improves the code it would be nice to
        receive a copy.  I can be reached at:

                        22547 Braken Carter
                        Katy, Texas 77449-3619

Toad Hall Tweak, 15 Oct 89
- Converted to .COM format
- General tightening
- Moved EXE header buffer, filename buffers to dynamic space at code end
  (to reduce program size)
- Changed BinToStr procedure so it carries parms directly in AX/DI
  instead of the slow pushes.
- Removed some unnecessary local variables

Comment ends    ~

RECSIZE         equ     512
CMDTAIL         equ     80h
CR              equ     0dh
LF              equ     0ah
EOM             equ     '$'
SIZE_OF_HEADER  equ     28

exe_header      STRUC           ; what an exe header looks like
exe_sig1        db      0       ; EXE file signature: "MZ"
exe_sig2        db      0
excess          dw      0       ; image size mod 512 (valid bytes in last page)
pages           dw      0       ; # of 512 byte pages in image
relo_ct         dw      0       ; count of relocation table entries
hdr_size        dw      0       ; size of header in paragraphs
min_mem         dw      0       ; min required memory
max_mem         dw      0       ; max required memory
xss             dw      0       ; stack seg offset in load module
xsp             dw      0       ; initial value of sp
cksum           dw      0       ; file checksum
xip             dw      0       ; initial value of IP
xcs             dw      0       ; cs offset in load module
relo_start      dw      0       ; offset of first relocatable item
ovl_num         dw      0       ; overlay number
exe_header ENDS                 ; end of structure

CSEG    SEGMENT PARA PUBLIC 'CODE'
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG

        org     100H

X2B     proc    near
        jmp     Start           ;skip over runtime data                 v1.1

errflag         db      0       ;error flag                             v1.1
save_ip         dw      0
handle1         dw      0
handle2         dw      0

msg1    db      'Input File ==>',eom
msg2    db      '  Output File ==>',eom
msg3    db      CR,LF,EOM

author          db      'X2B 1.1  Public Domain Software by Henry T. Nettles'
                db      CR,LF,EOM
code_size_msg   db      'Code Size = '
code_size_str   db      '      ',eom
code_start_msg  db      '  Code Start = '
code_start_str  db      '      ',eom
ip_msg          db      '  Initial IP = '
ip_str          db      '      ',CR,LF,EOM
actual_code_size_msg db 'Size of .Com file = '
actual_code_size_str db '      ',CR,LF,EOM

code_size dw    0               ; amount of code to move to .com file
code_start dw   0

no_file_msg1 db 'USAGE:  >X2B  [d:][\path\]file[.exe] [d:][\path\][file][.com]'
             db CR,LF,EOM
open_fail_msg  db 'ERROR: Open of input file failed!',CR,LF,EOM
create_fail_msg db 'ERROR: Create of output file failed!',CR,LF,EOM
disk_full_msg  db 'ERROR: The disk is full!',CR,LF,EOM
Bad_Read_msg   db 'I/O Error on Read!',CR,LF,EOM
bad_write_msg  db 'I/O Error on Write!',CR,LF,EOM
seek_error_msg db 'I/O Error on Seek!',CR,LF,EOM

badsig_msg     db 'Invalid EXE File Signature',CR,LF,EOM
hasrelo_msg    db 'EXE has relocatable items',CR,LF,EOM
has_ss_msg     db 'EXE has stack segment',CR,LF,EOM
bad_ip_msg     db 'IP not 0 or 100h',CR,LF,EOM
too_big_msg    db 'Exe file is too big to convert to COM file',CR,LF,EOM
mul_err_msg    db 'Overflow on multiply',CR,LF,EOM


Start:                                  ;v1.1

;****************************************************************************
;*      Get two file names from command line
;*      This portion of code was borrowed from TABS.ASM by Vernon Buerg
;****************************************************************************

        xor     al,al                   ;handy 0                        v1.1
        mov     FNAME1,al               ;insure both dynamic filename   v1.1
        mov     FNAME2,al               ; buffers are 0                 v1.1

        mov     si,CMDTAIL              ; DS:SI points to command line
        sub     bp,bp                   ;Indicates first or second name
        sub     ch,ch                   ;The PSP may contain one or two
        or      cl,[si]                 ; filenames separated by blanks v1.1
        jz      GetF5                   ; First byte of cmdline should not be 0

        mov     di,offset FNAME1        ;ES:DI points to fname1         v1.1
        Inc     si                      ; point to next char from command line
                                        ; (assume that 1st char is a blank)

GetF1:  Lodsb                           ;Copy command line to file names
                                        ; AL will contain character pointed
                                        ; to by DS:SI
        cmp     AL,' '                  ; skip leading blanks
        jne     GetF2                   ; not a blank -- jump

        or      bp,bp                   ; or until the length is zero
        jz      GetF4                   ;If a second blank is found,
         mov    ax,2400h                ; append zero and dollar sign
         stosw                          ; mov AX to ES:DI
         mov    di,offset FNAME2        ;ES:DI now points to 2d filename v1.1
         jmp    short GetF4

GetF2:  cmp     AL,Cr                   ;Is it CR, end of line?
        je      GetF5                   ; yes, end of command

        stosb                           ; no, save in name
        mov     bp,di                   ; and indicate data copied
GetF4:  loop    GetF1

GetF5:  mov     ax,2400h                ;Append zero and dollar sign
        stosw

        mov     dx,offset author        ;tell them who we are           v1.1
        mov     ah,9                    ;display string
        int     21H

;***************************************************************************
;*      DISPLAY THE TWO FILE NAMES FROM COMMAND LINE
;***************************************************************************
        mov     si,offset FNAME1        ;point to fname1                v1.1
        xor     al,al                   ;handy 0                        v1.1
        cmp     al,[si]                 ;did user enter at least 1 file name? v1.1
        jnz     L1                      ; is ok, we have a file name
         jmp    No_File1

L1:
        mov     di,si   ;offset fname1  ; check fname1 for extension    v1.1
        mov     cx,-1
        cld                             ; direction of search = forward
        repnz   scasb                   ; search through file name for '\0'
        not     cx                      ; CX = length including '\0'
        mov     dx,cx                   ;save length in DX a sec        v1.1
        mov     di,si   ;offset fname1  ;point back to fname1 start     v1.1
        mov     al,'.'
        repnz   scasb                   ; search for period in file name
        jz      L1a                     ; found the period, must have ext

        mov     ax,si   ;offset fname1  ; no period, must add .EXE for user v1.1
        add     ax,dx   ;fname1_len     ;                               v1.1
        dec     ax                      ;
        mov     di,ax                   ; di points to '\0' at end of file name
        mov     ax,'E.' ;452eh          ; ".E" backwards                v1.1
        stosw
        mov     ax,'EX' ;4558h          ; "XE" backwards                v1.1
        stosw
        mov     ax,2400h                ; append zero and dollar sign
        stosw

L1a:
        mov     cx,-1                   ;common code                    v1.1
        cmp     byte ptr FNAME2,0       ;did user enter 2d file name?   v1.1
                                        ; (should not be 0)             v1.1
        jnz     L2                      ; fname2 is non-blank, go check for ext

        mov     si,offset FNAME1        ;source is FNAME1               v1.1
        mov     di,si   ;offset FNAME1  ;into DI for a scasb            v1.1
        mov     al,'.'
        cld
        repnz   scasb                   ; search fname1 for '.'
        not     cx                      ; cx has length of fname1 including '.'
        dec     cx                      ; don't copy the period
        mov     di,offset FNAME2        ; let Dest.  Index point to fname2 v1.1
                                        ;SI (source) is already FNAME1  v1.1
        cld                             ; direction of movement = forward
        rep     movsb                   ; move chars from [SI] to [DI]
                                        ;   CX has count (# chars to move)
        jmp     short L2a               ; go add the ".COM"
;
L2:     mov     di,offset fname2        ; check fname2 for extension
        xor     al,al                   ; zero al
        cld                             ; direction of search = forward
        repnz   scasb                   ; search through file name for '\0'
        not     cx                      ; CX = length including '\0'
        mov     dx,cx                   ; save the length in DX a sec   v1.1
        mov     di,offset FNAME2                ;v1.1
        mov     al,'.'
        repnz   scasb                   ; search for period in file name
        jz      L2b                     ; found the period, must have ext

        mov     ax,offset FNAME2                ;v1.1
        add     ax,dx   ;fname2_len     ;add in FNAME2's length         v1.1
        dec     ax                      ;adjust
        mov     di,ax                   ; di points to '\0' at end of file name
L2a:    mov     ax,'C.' ;432eh          ; ".C" backwards                v1.1
        stosw
        mov     ax,'MO' ;4d4fh          ;"OM" backwards                 v1.1
        stosw
        mov     ax,2400h                ; append zero and dollar sign
        stosw
L2b:
        mov     dx,offset msg1
        mov     ah,9
        int     21H
        mov     dx,offset FNAME1
        mov     ah,9
        int     21H
        mov     dx,offset msg2
        mov     ah,9
        int     21H
        mov     dx,offset FNAME2
        mov     ah,9
        int     21H
        mov     dx,offset msg3
        mov     ah,9
        int     21H

;****************************************************************************
;*      OPEN THE INPUT FILE
;****************************************************************************

        mov     dx,offset FNAME1        ;open input file
        mov     ax,3D00H                ;open, read only
        int     21H
        jnc     L3                      ; opened ok
         jmp    Open_Fail               ; no file

L3:     mov     handle1,ax              ; save token for file

;****************************************************************************
;*      OPEN THE OUTPUT FILE
;****************************************************************************
        mov     dx,offset FNAME2        ;output file name
        xor     cx,cx                   ;normal attributes
        mov     ah,3CH                  ;create file
        int     21H
        jnc     L4                      ; created ok
         jmp    Create_Fail             ; create failed

L4:     mov     handle2,ax              ; save token for file

;****************************************************************************
;*      PROCESS THE EXE HEADER
;****************************************************************************
; first we read in the exe header, don't you think?

        mov     dx,offset BUFFER        ;read buffer
        mov     cx,SIZE_OF_HEADER
        mov     bx,handle1
        mov     ah,3FH                  ;read from file/device
        int     21H
        jnc     L5                      ;read ok
         jmp    Bad_Read                ;read error

L5:     mov     bx,offset buffer        ; use DS:BX to address buffer
        cmp     word ptr [bx].exe_sig1,'ZM'     ;'MZ' backwards? v1.1
        je      L7                      ;YEP, OK
         jmp    BadSig                  ;no, print error msg, exit      v1.1

L7:
        xor     ax,ax                   ;handy 0                        v1.1

        cmp     [bx].relo_ct,ax                 ;is relocatable count 0? v1.1
        je      L8                              ; is ok, no relocatable items
         jmp    HasRelo                         ; oops, can't convert

L8:
        cmp     [bx].xss,ax                     ;is stack segment 0?    v1.1
        je      L9                              ; is ok, no stack segment
         jmp    Has_SS

L9:
        cmp     [bx].xsp,ax                     ;is stack segment offset 0? v1.1
        je      L10                             ; is okay
         jmp    Has_SS

L10:
        mov     ax,[bx].xip                     ; initial value for IP
        or      ax,ax                           ; should either be 0 or 100h v1.1
        je      L11
        cmp     ax,100h
        je      L11
         jmp    Bad_IP

L11:    mov     save_ip,ax              ; save the IP for later use
;***************************************************************************
;* Compute offset of program image in module, and program size
;*
;* The program size is computed as follows;  it cannot exceed 64k bytes
;*     512 * (# of EXE pages -1 )
;*   + valid bytes in last EXE page
;*   - offset of program image in EXE file
;*
;* Note that if the IP is nonzero, we will skip the first
;* IP bytes of the program image, and copy IP bytes fewer
;* than the actual size
;*
;***************************************************************************

        mov     ax,[bx].hdr_size        ; size of the program header
                                        ; expressed in 16 byte paragraphs
        mov     cl,4                    ; no of times to shift
        shl     ax,cl                   ; fast multiply by 16
        mov     code_start,ax           ; save it

        mov     ax,[bx].pages           ;nr of 512-byte pages           v1.1
        dec     ax                      ; subtract 1
        cmp     ax,128                  ; is it too big? (128*512 is 64k)
        jle     L12                     ; no, is ok
         jmp    Too_Big                 ; exe file is too big, print error

L12:    mov     cl,9                    ; number of times to shift left
        shl     ax,cl                   ; fast multiply by 512
        jno     L13                     ; jump on no overflow
         jmp    Mul_Err                 ; else multiply error

L13:
        mov     cx,[bx].excess          ; no. of bytes in last non-full page
        add     ax,cx                   ; add to result from above
        sub     ax,code_start           ; subtract the code start address
        mov     code_size,ax            ; save it for later
        mov     di,offset code_size_str ;where to write the Ascii chars v1.1
        call    BinToStr                ;convert code size              v1.1
        mov     dx,offset code_size_msg ;'Code size = xxxx'             v1.1
        mov     ah,9                    ;display msg
        int     21H

        mov     ax,code_start           ;convert code start address     v1.1
        mov     di,offset code_start_str ;where to write the Ascii chars v1.1
        call    BinToStr
        mov     dx,offset code_start_msg        ;'Code start = xxxx'    v1.1
        mov     ah,9
        int     21H

        mov     ax,save_ip              ;program's IP
        mov     di,offset ip_str        ;where to write the Ascii chars v1.1
        call    BinToStr
        mov     dx,offset ip_msg        ;'Initial IP = xxxx'            v1.1
        mov     ah,9
        int     21H

;****************************************************************************
;*      MOVE FILE POINTER TO START OF CODE
;****************************************************************************
;* Add the initial IP to the code start address.  This will give
;* us the file offset, which is the location in the EXE file that we
;* will start copying from
;*
        mov     ax,code_start
        add     ax,save_ip              ;add in program's IP            v1.1
        mov     dx,ax                   ;DX needs it as lower part      v1.1
                                        ;of file offset                 v1.1

        mov     bx,handle1              ; handle of input file
        xor     cx,cx                   ; upper part of offset          v1.1
        mov     ax,4200h                ; move file pointer             v1.1
                                        ;(from start)
        int     21h
        jnc     L14
         jmp    Seek_Error

L14:
;****************************************************************************
;*      COPY THE CODE TO THE OUTPUT (.COM) FILE
;****************************************************************************
; reduce the code_size by the size of the IP
        mov     ax,code_size            ; reduce the code_size
        sub     ax,save_ip              ; by the IP size                v1.1
        mov     code_size,ax            ; store it away

        mov     di,offset actual_code_size_str  ;v1.1
        call    BinToStr                ;convert code size

        mov     dx,offset actual_code_size_msg  ;'Size of .COM file = xxxx' v1.1
        mov     ah,9
        int     21H
Next:                                   ; process next record
        mov     ax,RECSIZE
        cmp     ax,code_size            ; compare code_size to RECSIZE
        jle     L15                     ; if ax < code_size, use ax as is
         mov    ax,code_size            ;  else use size of remaining code
L15:    mov     cx,ax                   ; # of bytes to read
        mov     bx,handle1              ;  input file
        mov     dx,offset buffer        ; where to put it
        mov     ax,3f00h                ; read from file/device
        int     21h
        jnc     L16                     ; read ok
         jmp    Bad_Read                ; ERROR - get out of here

L16:    or      ax,ax                   ; on return, AX has # of bytes read
        jnz     L17                     ; if not zero, keep on
         jmp    Done                    ; read zero bytes, must be done

L17:
        mov     cx,ax                   ; nr bytes to write             v1.1
        mov     bx,handle2              ;  output file
        mov     dx,offset buffer        ; addr of what we are about to write
        mov     ah,40h                  ; write to file/dev
        int     21h
        jnc     L18                     ; carry flag not set, no error on write
         jmp    Bad_Write               ; jump if write error

L18:    cmp     ax,cx   ;wsize          ; AX has # of bytes actually written
        je      L19                     ; if we wrote all of the bytes
                                        ;    that we wanted to, then keep on
         jmp    Disk_Full               ; did NOT write all of the bytes
                                        ; that I wanted to, disk must be full
L19:    mov     bx,code_size
        xchg    ax,bx                   ; ax has code_size, bx has bytes written
        sub     ax,bx                   ; subtract bytes written from code_size
                                        ;   which gives us the number of
        mov     code_size,ax            ;   bytes remaining to be copied
        or      ax,ax                   ; bytes remaining = zero?       v1.1
        je      Done                    ; yes, we're finished
         jmp    Next                    ; no, go read next block

;****************************************************************************
;*      ERROR MESSAGES
;****************************************************************************

No_File1:       mov     dx,offset no_file_msg1
                jmp     short Print_Error
Open_Fail:      mov     dx,offset open_fail_msg
                jmp     short Print_Error
Create_Fail:    mov     dx,offset create_fail_msg
                jmp     short Print_Error
Disk_Full:      mov     dx,offset disk_full_msg
                jmp     short Print_Error
Bad_Read:       mov     dx,offset bad_read_msg
                jmp     short Print_Error
Bad_Write:      mov     dx,offset bad_write_msg
                jmp     short Print_Error
Seek_Error:     mov     dx,offset seek_error_msg
                jmp     short Print_Error
BadSig:         mov     dx,offset badsig_msg
                jmp     short Print_Error
HasRelo:        mov     dx,offset hasrelo_msg
                jmp     short Print_Error
Has_SS:         mov     dx,offset has_ss_msg
                jmp     short Print_Error
Bad_IP:         mov     dx,offset bad_ip_msg
                jmp     short Print_Error
Too_Big:        mov     dx,offset too_big_msg
                jmp     short Print_Error
Mul_Err:        mov     dx,offset mul_err_msg

Print_Error:    mov     ah,9
                int     21h
                not     errflag         ;turn error flag on             v1.1
                                        ;(non-zero)

;****************************************************************************
;*      CLOSE INPUT AND OUTPUT FILES
;****************************************************************************

Done:   mov     ax,handle1              ;input file handle
        or      ax,ax                   ; is the handle still zero,
                                        ; as was initially? v1.1
        je      Get_Out                 ; yes, no files to close
        mov     ah,3eh                  ; close input file
        mov     bx,handle1
        int     21h

        mov     ax,handle2              ;output file handle
        or      ax,ax                   ; is the handle still zero,
                                        ; as was initially? v1.1
        je      Get_Out                 ; yes, no output file to close
        mov     ah,3eh                  ; close output file
        mov     bx,handle2
        int     21h

        cmp     errflag,0               ;any errors?                    v1.1
        jz      Get_Out                 ; no
         mov    dx,offset FNAME2        ; yes, delete the output file   v1.1
         mov    ah,41H                  ;delete file
         int    21H

;****************************************************************************
;*      EXIT WITH STATUS CODE
;****************************************************************************

Get_Out:
        mov     ax,4C00H                ;terminate, errorlevel 0        v1.1
        int     21H

X2B     endp                    ;v1.1

; Procedure BinToStr (number,address)
; Purpose   Converts integer to string
; Input     ax = number to convert, di = near address for write v1.1
; Output    AX has characters written

BinToStr  PROC  near

        sub       cx,cx                 ; Clear counter
        mov       bx,10                 ; Divide by 10

; Convert and save on stack backwards

GetDigit:
        sub       dx,dx                 ; Clear top
        div       bx                    ; Divide to get last digit as remainder
        add       dl,"0"                ; Convert to ASCII
        push      dx                    ; Save on stack
        or        ax,ax                 ; Quotient 0?
        loopnz  GetDigit                ; No? Get another

; Take off the stack and store forward

        neg       cx                    ; Negate and save count
        mov       dx,cx
PutDigit:
        pop       ax                    ; Get character
        stosb                           ; Store it
        loop      PutDigit
        mov       ax,dx                 ; Return digit count

        ret                             ;                               v1.1
BinToStr  ENDP

        even

buffer  label   byte    ;recsize dup(?)
FNAME1  equ     buffer + RECSIZE        ;64-byte input filename buffer
FNAME2  equ     FNAME1 + 64             ;64-byte output filename buffer

CSEG    ENDS
        END     X2B
