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
