; Cut v1.0 : Extracts a defined chunk from a file
; by Kyzer/CSG
; $VER: Cut.asm 1.0 (22.11.98)
;
	incdir	include:
	include	lvo/exec_lib.i
	include	lvo/dos_lib.i
	include	dos/dos.i
	include	exec/memory.i

BLOCK	equr	A2
FH_FROM	equr	A3
FH_TO	equr	A4

BLKLEN	equr	D4
FILELEN	equr	D5
START	equr	D6
LENGTH	equr	D7


stackf	MACRO	; stack_symbol, stackelement_symbol
	IFND	\1
\1	set	0
	ENDC
\1	set	\1-4
\2	equ	\1
	ENDM

	stackf	stk, length	;\
	stackf	stk, start	; | these four filled by ReadArgs
	stackf	stk, to		; |
	stackf	stk, from	;/
	stackf	stk, rdargs
	stackf	stk, execbase
	stackf	stk, dosbase
args=from

	link	a5,#stk

	move.l	4.w,a6
	move.l	a6,execbase(a5)

	lea	dosname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,dosbase(a5)
	beq.s	.nodos
	move.l	d0,a6

	suba.l	BLOCK,BLOCK	 ; BLOCK = FH_FROM = FH_TO = NULL
	suba.l	FH_FROM,FH_FROM
	suba.l	FH_TO,FH_TO

	; parse args

	lea	templat(pc),a0
	move.l	a0,d1
	lea	args(a5),a0
	move.l	a0,d2
	clr.l	(a0)+
	clr.l	(a0)+
	clr.l	(a0)+
	clr.l	(a0)+
	moveq	#0,d3
	jsr	_LVOReadArgs(a6)
	move.l	d0,rdargs(a5)
	beq.s	.fail

	; open 'from' file

	move.l	from(a5),d1
	move.l	#MODE_OLDFILE,d2
	jsr	_LVOOpen(a6)
	tst.l	d0
	beq.s	.fail
	move.l	d0,FH_FROM

	; open 'to' file

	move.l	to(a5),d1
	move.l	#MODE_NEWFILE,d2
	jsr	_LVOOpen(a6)
	tst.l	d0
	beq.s	.fail
	move.l	d0,FH_TO

	; get length of 'from' file by seeking to end of file, then seeking
	; back to beginning, which returns the oldposition at the end of
	; the file which = the file length
	move.l	FH_FROM,d1
	moveq	#0,d2
	moveq	#OFFSET_END,d3
	jsr	_LVOSeek(a6)
	jsr	_LVOIoErr(a6)
	tst.l	d0
	bne	.fail

	move.l	FH_FROM,d1
	moveq	#0,d2
	moveq	#OFFSET_BEGINNING,d3
	jsr	_LVOSeek(a6)
	move.l	d0,FILELEN
	jsr	_LVOIoErr(a6)
	tst.l	d0
	bne.s	.fail

	bsr.s	mainpart

.fail	move.l	dosbase(a5),a6
	jsr	_LVOIoErr(a6)		; get error code immediately
	move.l	d0,d2

	move.l	execbase(a5),a6
	move.l	BLOCK,d0
	beq.s	.nomem
	move.l	d0,a1
	jsr	_LVOFreeVec(a6)		; free BLOCK if allocated
.nomem	move.l	dosbase(a5),a6
	move.l	FH_TO,d1
	beq.s	.noto
	jsr	_LVOClose(a6)		; close 'TO' file if open
.noto	move.l	FH_FROM,d1
	beq.s	.nofrom
	jsr	_LVOClose(a6)		; close 'FROM' file if open
.nofrom	move.l	rdargs(a5),d1
	beq.s	.noargs
	jsr	_LVOFreeArgs(a6)	; free args if allocated
.noargs
	move.l	d2,d1
	moveq	#0,d2
	jsr	_LVOPrintFault(a6)	; report any errors
	move.l	a6,a1
	move.l	execbase(a5),a6
	jsr	_LVOCloseLibrary(a6)	; close dos.library
.nodos	unlk	a5
	moveq	#0,d0
	rts

;------------------------------------------------------------------------------

mainpart
	move.l	start(a5),START	; get pointer to START value
	beq.s	.startok	; if NULL, use this as '0' default
	move.l	START,a0	; otherwise dereference to get the real value
	move.l	(a0),START
.startok

	; negative start offset means 'offset from end of file'
	; (start < 0) -> start = end - (-start)
	bge.s	.start1		; if start < 0, add FILELEN so start is now an
	add.l	FILELEN,START	; offset from the end of the file
	bge.s	.start1		; if still start < 0, start = 0
	moveq	#0,START
.start1
	; now move to chunk to start cutting from
	move.l	FH_FROM,d1
	move.l	START,d2
	moveq	#OFFSET_BEGINNING,d3
	jsr	_LVOSeek(a6)
	jsr	_LVOIoErr(a6)	; Seek() doesn't return a valid errorcode
	tst.l	d0		; so we have to rely on IoErr() result
	bne.s	.fail

	; subtract start from filelen, so filelen represents the maximum
	; number of bytes we can cut from this new position
	sub.l	START,FILELEN

	; now get LENGTH option (same way as START - default = 0)
	move.l	length(a5),LENGTH
	beq.s	.lenok
	move.l	LENGTH,a0
	move.l	(a0),LENGTH
.lenok

	; if length = 0 ('default') then length = 'however much is remaining'.
	; same if length is more than the amount remaining, or if length is
	; negative (might as well, no other special meaning for length < 0)
	bgt.s	.len1
	move.l	FILELEN,LENGTH
.len1	cmp.l	FILELEN,LENGTH
	ble.s	.len2
	move.l	FILELEN,LENGTH
.len2

	; now try to allocate a block of memory for the read/write loop
	; we try to allocate a block the size of the entire chunk to cut
	; at first, halfing the request each time until we can't get even
	; 1k - we give up at that point if we haven't allocated anything.

	move.l	LENGTH,BLKLEN		; BLKLEN = LENGTH
	move.l	execbase(a5),a6

.again	move.l	BLKLEN,d0		; size = BLKLEN
	moveq	#MEMF_ANY,d1		; any kind of memory
	jsr	_LVOAllocVec(a6)	; try to allocate
	move.l	d0,BLOCK
	tst.l	d0
	bne.s	.gotmem			; if success, exit loop
	cmp.l	#1024,BLKLEN
	blt.s	.fail			; if BLKLEN < 1024, exit and fail
	asr.l	#1,BLKLEN
	bra.s	.again			; otherwise halve BLKLEN and try again

.gotmem	move.l	dosbase(a5),a6

.copy	tst.l	LENGTH
	beq.s	.done
	cmp.l	LENGTH,BLKLEN		; if blklen < length, still more blocks
	blt.s	.more			; to go, otherwise it's the last block,
	move.l	LENGTH,BLKLEN		; so take only remainder of chunk length
.more
	; count and copy one block's worth of the chunk
	sub.l	BLKLEN,LENGTH

	move.l	FH_FROM,d1
	move.l	BLOCK,d2
	move.l	BLKLEN,d3
	jsr	_LVORead(a6)
	tst.l	d0
	bmi.s	.fail

	move.l	FH_TO,d1
	move.l	BLOCK,d2
	move.l	BLKLEN,d3
	jsr	_LVOWrite(a6)
	tst.l	d0
	bmi.s	.fail

	bra.s	.copy

.fail
.done	rts


templat	dc.b	'FROM/A,TO/A,START/N,LENGTH/N',0
dosname	dc.b	'dos.library',0
	dc.b	'$VER: Cut 1.0 (22.11.98)'
