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