; COMBINE.ASM	Interrupt List combiner
;		by Ralf Brown
;		last edit: 19oct96

		NAME	COMBINE
		TITLE	Combine Interrupt List sections

; declare all the segments in the order in which they are to appear in the
; executable
CODE	SEGMENT 'CODE'
CODE	ENDS
STACKSEG SEGMENT PUBLIC WORD 'STACK'
STACKSEG ENDS
BUFFERSEG SEGMENT PUBLIC WORD 'DATA'
BUFFERSEG ENDS
;
DGROUP	GROUP	CODE,STACKSEG,BUFFERSEG

;;------------------------------------------------------------------------

FFBLK struc
  ff_reserved	db 15h dup (?)
  ff_attrib	db ?
  ff_ftime	dw ?
  ff_fdate	dw ?
  ff_fsize	dd ?
  ff_fname	db 13 dup (?)
FFBLK ends

;;------------------------------------------------------------------------

CODE	SEGMENT 'CODE'
	ORG	100h			; this is a .COM file
	ASSUME	CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP

combine:
	jmp	near ptr main

banner		db	13,"COMBINE v2.00",9,"Ralf Brown 1996",13,10,"$",26
usage_msg	db	"Usage:",9,"COMBINE [options] dest-dir",13,10
		db	9,"where {dest-dir} is the directory in which to place",13,10
		db	9,"  the combined list ('.' for the current directory)",13,10
		db	10
		db	9,"options:",13,10
		db	9,9,"-d",9,"delete sections after copying",13,10
		db	10
		db	"All sections of the interrupt list must be in the current directory."
		db	"$"

bad_dos_msg	db	"Need DOS 2.0+$"
bad_drive_msg	db	"Invalid destination drive$"
no_mem_msg	db	"Insufficient memory$"
no_files_msg	db	"No section files found!$"
readerr_msg	db	"Read Error$"
writeerr_msg	db	"Write Error$"
diskfull_msg	db	"Disk full? while writing$"
no_disk_msg	db	"Out of space on destination drive",13,10,"$"
retry_msg	db	"Try again with -d to delete while copying$"

cant_create_msg db	"Check directory name -- unable to create "
combined_file	db	"INTERRUP.LST",0,"$"
section_file	db	"INTERRUP.A",0,"$"
section_letter equ section_file+9
missing_msg	db	"unavailable (skipped)"
crlf		db	13,10,"$"
section_heading db	"Interrupt List, part "
section_hdr_len equ $-section_heading
complete_msg	db	"Done.$"

;
; flags affecting operation
;
del_after_copy	db	0

;
; data needed while processing
;
filehandle	equ	di		; output file's handle
numsections	db	26
dest_drive	db	0
nondefault_dest	db	0
ftime		dw	0
fdate		dw	0
filesize_lo	dw	0
filesize_hi	equ	bp

; (since we don't use disk_buffer until after FindFirst is no longer needed,
; save memory by overlaying the two)
FindFirst	equ	DGROUP:disk_buffer

;;------------------------------------------------------------------------

write_string:
	mov	ah,9
	int	21h
	ret

;;------------------------------------------------------------------------

skip_whitespace:
	lodsb
	cmp	al,' '
	je	skip_whitespace
	cmp	al,9
	je	skip_whitespace
	dec	si			; unget the last character
	; set ZF to indicate whether we got to end of cmdline
	cmp	al,0Dh
	ret

;;------------------------------------------------------------------------

get_destination_file:
	mov	bx,si			; remember start of destination name
get_dest_file_loop:
	lodsb
	cmp	al,' '
	je	got_dest_end
	cmp	al,9
	je	got_dest_end
	cmp	al,0Dh
	jne	get_dest_file_loop
got_dest_end:
	dec	si			; unget last character
	mov	di,si
	mov	al,[si-1]		; check end of path -- is it terminated
	cmp	al,'\'			;   by a slash or backslash?
	je	dest_has_slash
	cmp	al,'/'
	je	dest_has_slash
	cmp	al,':'
	je	dest_has_slash
	mov	al,'\'
	stosb
dest_has_slash:
	mov	si,offset combined_file
dest_copy_loop:
	lodsb
	stosb
	cmp	al,0
	jne	dest_copy_loop
	; OK, now open the destination file
	; (BX is still pointing at start of pathname)
	cmp	byte ptr [bx+1],':'
	jne	got_dest_drive
	mov	al,[bx]
	and	al,0DFh			; force to uppercase
	sub	al,'A'
	jb	got_dest_drive
	cmp	al,dest_drive
	je	got_dest_drive
	mov	dest_drive,al
	mov	nondefault_dest,al
got_dest_drive:
	mov	ah,3Ch			; create the output file
	xor	cx,cx			; no special file attributes
	mov	dx,bx
	int	21h
	mov	dx,offset cant_create_msg
	jc	exit_with_err_2
	mov	filehandle,ax
	ret

;;------------------------------------------------------------------------

check_total_size:
	mov	byte ptr section_letter,'A'-1
	mov	ah,1Ah			; set DTA
	mov	dx,offset FindFirst
	int	21h
	xor	si,si			; keep track of # of sections found
check_size_loop:
	inc	byte ptr section_letter
	cmp	byte ptr section_letter,'Z'
	ja	short get_free_space
	mov	ah,4Eh			; find first
	mov	cx,001Fh		; ...regardless of attribute
	mov	dx,offset section_file
	int	21h
	jc	check_size_loop
	inc	si			; another section found
	mov	ax,FindFirst.ff_ftime
	mov	ftime,ax
	mov	ax,FindFirst.ff_fdate
	mov	fdate,ax
	mov	ax,word ptr FindFirst.ff_fsize
	mov	dx,word ptr FindFirst.ff_fsize+2
	cmp	del_after_copy,0
	je	count_full_size
	cmp	nondefault_dest,0
	jnz	count_full_size
	cmp	dx,filesize_hi
	jb	check_size_loop
	ja	check_size_bigger
	cmp	ax,filesize_lo
	jbe	check_size_loop
check_size_bigger:
	mov	filesize_lo,ax
	mov	filesize_hi,dx
	jmp	check_size_loop

count_full_size:
	add	filesize_lo,ax
	adc	filesize_hi,dx
	jmp	check_size_loop

get_free_space:
	test	si,si			; check number of sections found
	mov	dx,offset no_files_msg
	jz	short exit_with_err_2
	mov	dl,dest_drive
	inc	dx
	mov	ah,36h			; get free disk space
	int	21h
	cmp	ax,0FFFFh
	jne	got_free_space
	mov	dx,offset bad_drive_msg
exit_with_err_2:
	jmp	near ptr exit_with_errmsg
got_free_space:
	mul	cx			; DX:AX <- AX*CX
	mov	cx,dx			; store high half of intermediate
	mul	bx			; DX:AX <- low(AX*CX)*BX
	xchg	ax,bx			; store low half of second interm.
	xchg	cx,dx			; store high half of second interm.
	mul	dx			; DX:AX <- high(AX*CX)*BX
	xchg	ax,bx			; DX:BX:0000h + CX:AX = result
	add	bx,cx
	adc	dx,0			; DX:BX:AX = AX*BX*CX = free space
	jnz	plenty_free_space	; >4G free?
	sub	ax,filesize_lo
	sbb	bx,filesize_hi
	jnb	plenty_free_space
not_enough_space:
	mov	dx,offset no_disk_msg
	call	write_string
	cmp	nondefault_dest,0
	jnz	size_check_failed
	cmp	del_after_copy,0
	jne	size_check_failed
	mov	dx,offset retry_msg
	call	write_string
size_check_failed:
	mov	al,2
	jmp	exit
plenty_free_space:
	ret

;;------------------------------------------------------------------------

check_section_header:
	push	si
	push	di
	mov	si,offset DGROUP:disk_buffer
	mov	di,offset section_heading
	mov	cx,section_hdr_len
	or	cx,cx
	rep	cmpsb
	jnz	not_section_heading
scan_curr_section:
	lodsb
	cmp	al,' '			; scan for the " of "
	jne	scan_curr_section
	add	si,3			; skip "of "
	xor	cl,cl
num_sections_loop:
	lodsb
	sub	al,'0'
	jb	num_sections_done
	cmp	al,9
	ja	num_sections_done
	mov	ch,al
	mov	al,10
	mul	cl
	mov	cl,al
	add	cl,ch
	jmp	num_sections_loop
num_sections_done:
	mov	numsections,cl
got_num_sections:
not_section_heading:	
	pop	di
	pop	si
	ret

;;------------------------------------------------------------------------

; in:	SI = file handle for current section
copy_section:
	mov	ax,4201h
	mov	bx,filehandle
	xor	cx,cx
	xor	dx,dx
	int	21h			; get current file position (== size)
	mov	filesize_lo,ax
	mov	filesize_hi,dx
copy_section_loop:
	mov	ah,3Fh
	mov	bx,si
	mov	cx,disk_buffer_end - disk_buffer
	mov	dx,offset DGROUP:disk_buffer
	int	21h
	jc	copy_read_error
	mov	cx,ax			; write same number of bytes read
	mov	ah,40h
	mov	bx,filehandle
;;	mov	dx,offset DGROUP:disk_buffer
	int	21h
	mov	dx,offset writeerr_msg
	jc	copy_error
	mov	dx,offset diskfull_msg
	cmp	ax,cx
	jb	copy_error
	; check for section header at start of buffer, and extract number
	; of sections from it
	push	cx
	call	check_section_header
	pop	ax
	cmp	ax,disk_buffer_end - disk_buffer ; continue until only partial
	je	copy_section_loop		 ;   buffer read (EOF hit)
	ret

copy_read_error:
	mov	dx,offset readerr_msg
copy_error:
	; truncate output to size before section was started
	push	dx			; store error message
	mov	ax,4200h
	mov	bx,filehandle
	mov	cx,filesize_hi
	mov	dx,filesize_lo
	int	21h
	mov	ah,40h
	xor	cx,cx			; write zero bytes to truncate
	int	21h
	pop	dx			; get back error message
	;; fall through to exit_with_errmsg ;;

;;------------------------------------------------------------------------

exit_with_errmsg:
	call	write_string
	; exit with errorlevel 1
	mov	al,01h
	jmp	near ptr exit

main:
	ASSUME	CS:DGROUP, DS:DGROUP, ES:DGROUP, SS:DGROUP
	mov	dx,offset banner
	call	write_string
	; relocate the stack
	mov	sp,offset DGROUP:stackbot
	; ensure that we have enough memory
	mov	ax,cs
	add	ax,1000h		; require 64K memory
	cmp	ax,ds:[0002h]		; is end of mem at least 64K above CS?
	mov	dx,offset no_mem_msg
	ja	exit_with_errmsg
	mov	si,81h			; point at start of cmdline
	mov	bl,[si-1]		; get length of cmdline
	mov	bh,0
	mov	byte ptr [bx+si],0Dh	; ensure cmdline properly terminated
	cld
	call	skip_whitespace
	mov	dx,offset usage_msg
	jz	exit_with_errmsg
get_cmdline_switches:
	call	skip_whitespace
	jz	not_a_switch
	cmp	al,'-'			; is it a switch?
	jne	not_a_switch
	lodsb				; get the switch character
	lodsb				; get the switch itself
	and	al,0DFh			; force to uppercase
	cmp	al,'D'
;;	mov	dx,offset usage_msg
	jne	exit_with_errmsg
	mov	del_after_copy,1
	jmp	get_cmdline_switches
not_a_switch:
	mov	ah,19h			; get default drive
	int	21h
	mov	dest_drive,al
	mov	ah,30h
	int	21h
	cmp	al,2
	mov	dx,offset bad_dos_msg
	jb	exit_with_errmsg
	call	get_destination_file
	xor	filesize_hi,filesize_hi
	call	check_total_size
	;
	; OK, all the preliminaries are done now, so go concatenate the
	; sections of the interrupt list
	;
	mov	al,'A'-1
concat_loop:
	inc	ax
	mov	section_letter,al
	sub	al,'A'-1
	cmp	al,numsections
	ja	concat_done
	mov	dx,offset section_file
	call	write_string
	mov	ax,3D00h
	int	21h
	mov	dx,offset missing_msg
	jc	concat_loop_end
	mov	si,ax
	call	copy_section
	mov	ah,3Eh
	mov	bx,si			; BX <- section file's handle
	int	21h			; close the file
	cmp	del_after_copy,0
	je	concat_no_del
	mov	ah,41h
	mov	dx,offset section_file
	int	21h
concat_no_del:
	mov	dx,offset crlf
concat_loop_end:
	call	write_string
	mov	al,section_letter
	jmp	concat_loop

concat_done:
	mov	dx,offset complete_msg
	call	write_string
	mov	al,00h			; successful completion
exit:
	push	ax
	mov	dx,offset crlf
	call	write_string
	mov	bx,filehandle		; set timestamp of combined file to
	mov	cx,ftime		;   be that of the last of the sections
	mov	dx,fdate
	mov	ax,5701h		; (set file time & date)
	int	21h
	mov	ah,3Eh			; close the destination file
	int	21h
	pop	ax
	mov	ah,4Ch
	int	21h

CODE ENDS

STACKSEG SEGMENT PUBLIC WORD 'STACK'
stacktop dw	160 dup (?)
stackbot label byte
STACKSEG ENDS

BUFFERSEG SEGMENT PUBLIC WORD 'DATA'
disk_buffer db 62*1024 dup (?)
disk_buffer_end label byte
BUFFERSEG ENDS

	END combine
