;BCOPY.A86 - binary copy program written specifically for use with FORM3547
debug=0 ;set to zero when debugged
cr      equ     13
lf      equ     10
dos     equ     21h
esc     equ     1bh
space   equ     20h
stdin   equ     0       ;standard input
stdout  equ     1       ;standard output
stderr  equ     2       ;standard error device
stdaux  equ     3       ;auxiliary (COM1) port
stdprn  equ     4       ;printer port
;
print   macro
	info    'BCOPY -- ',#1
	#em
;
pdigit  macro   ;for debugging only
#if debug
	pushf
	push    ax
	mov     ax,#1
	add     >m1,al
	call    txterr
	jmp     short >m2
m1      db      '0'
m2:     pop     ax
	popf
#endif        
	#em
;
info    macro
	call    txterr
	jmp     short >m1
	db      #1,#2,cr,lf
m1:
	#em
;
errchk  macro
	call    #1
	#em
;
dbgmsg  macro
	#if debug
	print   #1
	#endif
	#em
;        
	jmp     start
argc    dw      0       ;storage for argument count
argv    dw      4 dup (0) ;storage for args, 2 expected
ihandle dw      0       ;storage for input file handle
ohandle dw      0       ;storage for output file handle
space   equ     ' '     ;space character
filesize dw     0,0     ;storage for current file's size
fileptr dw      0       ;points to input filespec after initialization
altdta  db      64 dup (?) ;for directory searches
mode    db      0       ;storage for raw/cooked mode
;
;
start:  
	dbgmsg  'Calling GETARGS'
	call    getargs ;get the arguments
	dbgmsg  'Checking for errors from GETARGS...'
	errchk  >e1     ;quit on error
	dbgmsg  'Calling OPENOUT'
	call    openout ;open output file
	dbgmsg  'Checking for errors from OPENOUT...'
	errchk  >e2     ;quit on error
a1:     dbgmsg  'Calling OPENIN'
	call    openin  ;open an input file
	dbgmsg  'Checking for errors from OPENIN...'
	errchk  >e3     ;exit normally if just NMF
a2:     dbgmsg  'Calling READ'
	call    read    ;read in one buffer full
	dbgmsg  'Checking for errors from READ...'
	errchk  >e4     ;restart if EOF
	dbgmsg  'Calling WRITE'
	call    write   ;attempt to write the buffer to output
	dbgmsg  'Checking for errors from WRITE'
	errchk  >e5     ;quit on failure
	jmp     short a2 ;else loop
e1:     jnc     ret     ;quit on bad command line
	info    'Usage: BCOPY infile.ext outfile.ext'
	mov     ax,4c01h ;set errorlevel
	int     dos
	ret
e2:     jnc     ret     ;return if opened OK
	print   'Failed on OPEN of output file'
	mov     ax,4c05h ;set errorlevel
	int     dos
	ret     ;target for j?? ret
e3:     ja      ret     ;continue if no error
	pop     ax      ;else try again
	if nz jmp a1    ;loop if still more files
	mov     ax,4c00 ;no more files...
	jmp     quit    ;exit normally
e4:     ja      ret     ;return if neither C nor Z set (error/EOF)
	jnz     >e6     ;warn if error
	pop     bx      ;else trash return address
	jmp     a1      ;done with this file, try another
e5:     jnc     ret     ;return if not error
	print   '*FATAL* - Failed on WRITE of output file'
	mov     ax,4c01 ;errorlevel 1
	jmp     quit    ;exit
	ret     ;target for j?? ret
e6:     print   'ERROR - failed on READ, skipping input file'
	pop     ax      ;trash the return address
	jmp     a1      ;loop back for another input file
;
openout:        
	mov     ah,1ah  ;set DTA
	mov     dx,offset altdta ;so as not to corrupt argv buffer
	int     dos     ;done
	mov     dx,argv+6 ;get pointer to 2nd arg
	mov     cx,0    ;no attributes
	mov     ah,3ch  ;create, truncate if already exists
	int     dos     ;try...
	errchk  >e1     ;quit on error
	mov     ohandle,ax ;save the handle if OK
	mov     ax,4400h ;get device data
	mov     bx,ohandle ;get handle back
	int     dos     ;request the device data
	errchk  >e2     ;abort on error
	mov     mode,dl ;save the current mode
	or      dl,20h  ;set "raw" bit
	jns     ret     ;return if bit 7 not set (disk file)
	xor     dh,dh   ;shouldn't have to, but fails otherwise under DOS5
	mov     ax,4401h ;set device data
	int     dos     ;do it...
	errchk  >e2     ;check for error
	ret
e1:     jnc     ret     ;return if OK
	print   '*FATAL* - could not open output file'
	pop     ax      ;toss the return address
	ret     ;quit from main routine
e2:     jnc     ret     ;continue if OK
	print   '*FATAL* - could not set output device to RAW mode'
	pop     ax      ;quit from main routine
	ret
;
openin:
	mov     ah,4fh  ;assume DTA already initialized
	xor     dx,dx   ;for test
	mov     cx,dx   ;zero for plain vanilla files
	or      dx,fileptr ;is a pointer defined?
	jne     >l1     ;continue if so
	mov     dx,argv+2 ;else use input string, possibly with wildcards
	mov     ah,4eh  ;specify new search
l1:     mov     fileptr,dx ;make sure to use correct function next time
	int     dos     ;load DTA with file info
	errchk  >e1     ;check if no more files
	mov     dx,offset altdta+30 ;point to filename string
	call    showfile ;indicate what we're copying to where
	mov     ax,3d00h ;open for read
	int     dos     ;call DOS
	errchk  >e2     ;check for error
	mov     bx,dx   ;copy pointer into DTA
	sub     bx,4    ;point to file size
	mov     ihandle,ax ;save the handle
	mov     cx,[bx] ;get size from DTA
	mov     filesize,cx ;store it for later
	mov     cx,[bx+2] ;high word of size
	mov     filesize+2,cx ;store it too
	ret
e1:     jnc     ret     ;continue if OK
	pop     ax      ;clean up stack
	xor     ax,ax   ;set Z flag
	ret             ;assume No More Files, just return
e2:     jnc     ret     ;return if OK
	pop     ax      ;clean up stack
	print   'Error opening file, skipping this one'
	ret
;
read:   mov     dx,offset readbf ;point to buffer
	mov     bx,ihandle ;get the handle
	mov     ax,0    ;in case nothing more to do
	mov     cx,0f000h ;attempt to grab a big block
	sub     filesize,cx ;make sure we don't go past the end
	sbb     filesize+2,0
	jnc     >a1     ;continue if not...
	add     cx,filesize ;else make CX the file size...
	mov     filesize,0 ;zero out the size doubleword
	mov     filesize+2,0
	jz      >a2     ;just return if already done
a1:     mov     ah,3fh  ;use READ function
	dbgmsg  'Reading a block of data'
	int     dos     ;do the read
	or      ax,cx   ;clear Z flag
	ret
a2:     dbgmsg  'End of file reached'
	ret
;
write:  mov     ah,40h  ;all other registers remain same as for READ...
	mov     bx,ohandle ;except for the handle
	int     dos     ;request the write
	ret
;
txterr:
;if followed by a string, prints the string to STDERR; if followed
; immediately by a null, uses string address in BX
	pushf           ;save flags
	push    ax      ;save registers we will be using
	push    bx      ;...
	push    cx      ;...
	push    dx      ;...
	push    di      ;to locate null
	push    bp      ;pointer
	mov     bp,sp   ;so we can use relative addressing
	mov     di,[bp+14] ;address of text, after we...
	add     di,2    ;point past the branch
	mov     al,[di] ;let's see if it's null...
	or      al,al   ;...
	mov     dx,di   ;assume it's not
	jne     >a2     ;skip if not
	mov     dx,bx   ;else use address in BX
	mov     di,dx   ;copy pointer to start of string
	mov     cx,80   ;max string length
	xor     al,al   ;look for null
	repnz   scasb   ;loop till null is found
	mov     cx,di   ;get original pointer
	sub     cx,dx   ;subtract string start
	dec     cx      ;don't print the null
	jmp     short   >a3
a2:     mov     bx,[bp+14] ;get the address of the branch instruction
	mov     cl,byte ptr [bx+1] ;load the offset into count register
	xor     ch,ch   ;clear the high byte
a3:     ;skipped to here if address was in BX and we had to find end
	mov     bx,stderr ;standard handle for error device (screen)
	mov     ah,40h  ;specify write function
	int     dos     ;do it...
	pop     bp      ;restore registers...
	pop     di      ;...
	pop     dx      ;...
	pop     cx      ;...
	pop     bx      ;...
	pop     ax      ;...
	popf            ;and flags
	ret
;
getargs:  
	mov     bx,80h  ;point to command line length
	xor     cx,cx   ;clear the register
	mov     cl,[bx] ;get the length
	cmp     cx,8    ;must be at least 8 chars to be valid arg
	errchk  >e1     ;quit if not
	add     bx,cx   ;point to the end
	inc     bx      ;now it does
	mov     byte[bx],space ;replace CR with space
	mov     dx,bx   ;copy the end pointer
	xor     bx,bx   ;make pointer into ARGV array
	mov     si,81h  ;point DI to start of command line
l1:     call    skipspace ;move to first non-white-space
	mov     di,si   ;copy the pointer
	call    skipchars ;move to first whitespace
	sub     si,di   ;get the length
	mov     argv[bx],si ;store it
	add     si,di   ;restore the value
	cmp     dx,si   ;past the end?
	errchk  >e1     ;error if so
	mov     byte [si],0 ;null-terminate the string
	add     bx,2    ;update the pointer
	mov     argv[bx],di ;save pointer to start of this arg
	add     bx,2    ;update the pointer again
	inc     argc    ;update the arg count
	cmp     argc,2  ;done?
	jne     l1      ;loop back if not
	ret
e1:     jnc     ret     ;return if OK
	pop     ax      ;else return with carry set to main routine
	ret
skipchars:
a1:     lodsb   ;get next char
	cmp     al,space ;not whitespace?
	ja      a1      ;loop if so
	dec     si      ;else point back to the space
	ret
skipspace:
a1:     lodsb   ;get next char
	cmp     al,space ;whitespace?
	jbe     a1      ;loop if so
	dec     si      ;else point back to graphics char
	ret
showfile:        
	call    txterr
	jmp     short >s1
	db      'BCOPY -- Info - Copying '
s1:     mov     bx,dx
	call    txterr
	jmp     short >s2
	db      0
s2:     call    txterr
	jmp     short >s3
	db      ' to '
s3:     mov     bx,argv+6
	call    txterr
	jmp     short >s4
	db      0
s4:     call    txterr
	jmp     short >s5
	db      cr,lf
s5:     ret
quit:   ;set device back to cooked mode before quitting
	push    ax      ;save exit code
	mov     bx,ohandle ;get handle back
	mov     dl,mode ;get mode data we saved earlier
	xor     dh,dh   ;shouldn't have to, but fails otherwise
	mov     ax,4401h ;set device data
	int     dos     ;do it...
	pop     ax      ;restore exit code
	int     dos     ;quit
readbf: end
