;+
;
; Program to pack/unpack RK05 images from Wilson Labs RK05-to-Zip bridge boxes.
;
; By John Wilson (no relation!).
;
; Copyright (C) 1998 by D Bit.  All rights reserved.
; This program may be freely copied or modified as long as this copyright
; notice appears intact in both the source code, and the banner that's clearly
; visible every time the program begins execution.
;
; 11/04/1998	JMBW	Created.
; 11/14/1998	JMBW	Inserts as well as extracts.
;
; Usage:  RK05WL +/-n zip rk05
;
; +/-	+ means insert (into Zip image), - means extract
; n	decimal partition number (0-23)
; zip	input file (96 MB raw Zip image)
; rk05	output file (2.5 MB RK05 image)
;
; Zip disk format (reverse engineered 11/04/1998 by John Wilson):
;
; Each RK05 track (12 sectors) is stored in 8 KB of space on the Zip disk.
;
; For some reason head 1 is stored before head 0, so to get contiguous data
; you have to reverse the order of each pair of 8 KB tracks.
;
; RK05 volumes start at 4 MB address multiples, so there are 24 of them in a 96
; MB Zip disk ("Zip 100" refers to marketing MBs, not real MBs).
;
; Sectors start at offsets (into the 8 KB track) that are multiples of 0290h.
; RK05s are hard-sectored so the sector preamble begins at these fixed offsets,
; there's no need to search for them.
;
; Each sector contains the decoded head data for that sector, with the bits
; serialized left-to-right (i.e. backwards!) within each byte.  The data are
; as described in the RK11D documentation:
;
;	13. words		;preamble, approximately 13 words of zeros
;				;(N.B. the first byte always seems to be
;				;non-zero on Wilson Labs disks, no idea whose
;				;fault that is but skip a few bytes to be safe)
;	1 bit			;sync bit, the first 1 after the preamble
;	1 word			;header word, contains cylinder number (only,
;				;no head or sector) shifted left 5 bits
;	256. words		;sector data
;	1 word			;checksum, sum of the 256 16-bit data words
;	1 word			;postamble, all zeros
;	(junk until the end of the 0290h sector data)
;
; So to find a sector, you compute a starting disk address like this:
; addr = (volume * 4 MB) + (cyl * 16 KB) + ((NOT head) * 8 KB) + (sec * 0290h)
;
; Add a little to addr to skip the non-zero junk at the beginning of the
; preamble (I skip 8 words just to be safe), then search for the first non-zero
; bit.  Immediately following that bit is the 16-bit header word and the sector
; data/checksum/postamble.
;
;-
	.radix	8
;
	locals			;(requires TASM!)
;
lf=	12
cr=	15
;
secsiz=	0290h			;size of sector dump on disk
;
fatal	macro	txt		;fatal error
	local	a,b
	call	err1
a	db	b,txt
b=	$-a-1
	endm
;
code	segment
	assume	cs:code
	org	100h
start:	cld			;DF=0
	; parse partition # from command line
	mov	si,80h		;point at JCL
	lodsb			;get length
	cbw			;AH=0
	mov	cx,ax		;copy
	call	getw		;get +n or -n
	jc	@@5
	mov	al,[bx]		;get + or -
	inc	bx
	dec	dx		;(count it)
	jnz	@@1		;number is stuck to + or -
	push	ax		;save
	call	getw		;parse number
	pop	ax		;[restore]
	jc	@@5
@@1:	cmp	al,'+'		;inserting?
	je	@@2
	cmp	al,'-'		;extracting?
	jne	@@5		;no
@@2:	mov	ds:func,al	;save
	xor	di,di		;init partition #
@@3:	mov	al,[bx]		;get a char
	inc	bx
	sub	al,'0'		;convert to binary if digit
	cmp	al,9d		;digit?
	ja	@@5		;no, invalid
	cbw			;AH=0
	xchg	ax,di		;save, get old number
	push	dx		;save
	mov	dx,10d		;multiplier
	mul	dx		;make space for new digit
	pop	dx		;restore
	add	di,ax		;add it in
	dec	dx		;loop through all chars
	jnz	@@3
	mov	ds:part,di	;save
	; get Zip image filename
	call	getw		;get it
	jc	@@5
	xchg	bx,dx		;switch ptr, length
	add	bx,dx		;point past end
	xor	al,al		;load 0
	xchg	al,[bx]		;mark end, save old char
	push	ax		;save
	mov	ax,3D00h	;func=open /RONLY
	cmp	byte ptr ds:func,'+' ;inserting?
	jne	@@4
	 inc	ax		;func=open /WONLY
@@4:	int	21h		;open the file
	jc	@@6
	mov	ds:ziphnd,ax	;save handle
	pop	ax		;catch terminating char
	mov	[bx],al		;restore
	; get RK05 image filename
	call	getw		;get it
	jnc	@@8
@@5:	mov	dx,offset usage	;point at string
	mov	ah,09h		;func=print
	int	21h
	int	20h		;exit
@@6:	fatal	'?Zip image open error'
@@7:	fatal	'?RK05 image open error'
@@8:	xchg	bx,dx		;switch ptr, length
	add	bx,dx		;point past end
	mov	byte ptr [bx],0	;mark end
	mov	ax,3D00h	;func=open /RONLY
	cmp	byte ptr ds:func,'-' ;extracting?
	jne	@@9
	mov	ah,3Ch		;func=create
	xor	cx,cx		;mode=default
@@9:	int	21h		;open/create the file
	jc	@@7
	mov	ds:rkhnd,ax	;save handle
	; seek to beginning of partition
	mov	ax,ds:part	;get it
	mov	cl,6		;total left shift = 22 bits
	sal	ax,cl
	mov	cx,ax		;CX:DX = offset from BOF
	xor	dx,dx
	mov	bx,ds:ziphnd	;handle
	mov	ax,4200h	;func=seek
	int	21h
	jnc	@@10		;success
	fatal	'?Seek error'
@@10:	cmp	byte ptr ds:func,'-' ;extract?
	je	read		;yes, go read RK05 data out of Zip image
	;jmp	short write	;write RK05 data into Zip image
;
write:	; write disk into Zip image
	mov	word ptr ds:header,0 ;init header
@@1:	; read a cylinder in
	mov	dx,offset fmtbuf ;point at buf
	mov	cx,12d*512d*2	;size of data
	mov	bx,ds:rkhnd	;handle
	mov	ah,3Fh		;func=read
	int	21h
	jc	@@2		;error
	cmp	ax,cx		;read all?
	je	@@3
@@2:	fatal	'?Read error'
@@3:	; initialize cyl data to 0
	mov	di,offset cylbuf ;point at cyl data
	xor	ax,ax		;load 0
	mov	cx,16384d/2	;word count
	rep	stosw		;init to all zeros
	; pack cyl
	mov	dx,offset fmtbuf ;point at formated buf
	mov	di,offset cylbuf+8192d ;side 0 comes *second*
	call	pactrk		;pack track 0
	mov	di,offset cylbuf ;point at side 1
	call	pactrk		;pack track 1
	; write a cylinder out
	mov	dx,offset cylbuf ;point at buf
	mov	cx,8192d*2	;length of sector data
	mov	bx,ds:ziphnd	;handle
	mov	ah,40h		;func=write
	int	21h
	jc	@@4		;error
	cmp	ax,cx		;got it all?
	je	@@5		;yes
@@4:	fatal	'?Write error'
@@5:	add	word ptr ds:header,40 ;bump to next cyl
	cmp	word ptr ds:header,200d*40 ;done all?
				;(use 202d*40 if including maint tracks)
	jb	@@1		;loop if not
	jmp	short exit	;done
;
read:	; read disk from Zip image
	mov	word ptr ds:header,0 ;init header
@@1:	; read a cylinder in
	mov	dx,offset cylbuf ;point at buf
	mov	cx,8192d*2	;length of sector data
	mov	bx,ds:ziphnd	;handle
	mov	ah,3Fh		;func=read
	int	21h
	jc	@@2		;error
	cmp	ax,cx		;got it all?
	je	@@3		;yes
@@2:	fatal	'?Read error'
@@3:	mov	di,offset fmtbuf ;point at output buf
	mov	dx,offset cylbuf+8192d ;side 0 comes *second*
	call	unptrk		;unpack track 0
	mov	dx,offset cylbuf ;point at side 1
	call	unptrk		;unpack track 1
	; write a cylinder out
	mov	dx,offset fmtbuf ;point at buf
	mov	cx,12d*512d*2	;size of data
	mov	bx,ds:rkhnd	;handle
	mov	ah,40h		;func=write
	int	21h
	jc	@@5		;error
	cmp	ax,cx		;wrote all?
	je	@@6
@@5:	fatal	'?Write error'
@@6:	add	word ptr ds:header,40 ;bump to next cyl
	cmp	word ptr ds:header,200d*40 ;done all?
				;(use 202d*40 if including maint tracks)
	jb	@@1		;loop if not
exit:	mov	bx,ds:ziphnd	;get Zip handle
	mov	ah,3Eh		;func=close
	int	21h
	mov	bx,ds:rkhnd	;RK05 handle
	mov	ah,3Eh		;func=close
	int	21h
	int	20h
;+
;
; Pack a track.
;
; es:di	raw output buf
; ds:dx	starting addr of track data
;
;-
pactrk:	mov	cx,12d		;# sectors per track
@@1:	mov	si,dx		;point at sector
	push	cx		;save
	push	dx
	push	di
	call	pacsec		;pack a sector
	pop	di		;restore
	pop	dx
	pop	cx
	add	dx,512d		;bump to next sector
	add	di,secsiz
	loop	@@1		;do whole track
	ret
;+
;
; Pack a sector of data.
;
; ds:si	formated input data
; es:di	raw head data (MSB first)
;
; Sector contents:
; ~13. words of zero (officially -- first byte is NZ in Wilson Labs disks)
; one sync bit (1, not padded out to a word)
; header word (containing cylinder number shifted left 5 bits)
; 256. data words
; 1 checksum word
;
;-
pacsec:	xor	cx,cx		;nothing written yet
	xor	ax,ax		;all zeros
	mov	dx,14d*16d+1	;write 14 words +1 bit of zeros
				;(normally 13 words, but this seems to be what
				;the Wilson Labs box gets most often)
	call	putbit
	mov	ax,1		;write 1 bit
	mov	dx,ax
	call	putbit
	mov	ax,ds:header	;get header
	mov	dx,16d		;16 bits
	call	putbit
	mov	dx,256d		;# words to get
	mov	ds:chksum,0	;clear out checksum
@@1:	push	dx		;save
	lodsw			;get a data word
	add	ds:chksum,ax	;add it up
	mov	dx,16d		;write 16 bits
	call	putbit
	pop	dx		;restore count
	dec	dx		;done all?
	jnz	@@1		;loop if not
	mov	ax,ds:chksum	;get checksum
	mov	dx,16d		;write 16 bits
	call	putbit
	xor	ax,ax		;load 0
	mov	dx,8d		;write 8 zeros to flush out CH
	call	putbit		;(postamble + rest of sec already cleared)
	ret
;+
;
; Put DX bits from AX
;
; ch	current byte
; cl	# bits in current byte
; di	buffer ptr
;
;-
putbit:	shr	ax,1		;get a bit
	rcl	ch,1		;into CH
	inc	cx		;count it
	cmp	cl,8d		;filled the byte?
	je	@@1
	dec	dx		;done all?
	jnz	putbit		;loop if not
	ret
@@1:	mov	[di],ch		;save
	inc	di
	xor	cx,cx		;load 0
	dec	dx		;done all?
	jnz	putbit		;loop if not
	ret
;+
;
; Unpack a track.
;
; es:di	output buf
; ds:dx	starting addr of raw track data
;
;-
unptrk:	mov	cx,12d		;# sectors per track
@@1:	mov	si,dx		;point at sector
	push	cx		;save
	push	dx
	call	unpsec		;unpack a sector
	pop	dx		;restore
	pop	cx
	add	dx,secsiz	;bump to next sector
	loop	@@1		;do whole track
	ret
;+
;
; Unpack a sector of data.
;
; ds:si	raw head data (MSB first)
; es:di	formated output data
;
; Sector contents:
; ~13. words of zero (officially -- first byte is NZ in Wilson Labs disks)
; one sync bit (1, not padded out to a word)
; header word (containing cylinder number shifted left 5 bits)
; 256. data words
; 1 checksum word
;
;-
unpsec:	add	si,8d*2		;skip 8 words (to get past non-zero junk)
	mov	dx,10d*16d	;should find sync in 5 more words, allow 10.
	xor	ax,ax		;init buf
	xor	cx,cx		;nothing read yet
@@1:	push	dx		;save
	mov	dx,1		;fetch 1 bit
	call	getbit
	pop	dx		;restore
	test	ax,ax		;sync bit?
	jnz	@@2		;yes
	dec	dx		;keep looking
	jnz	@@1
	; sync bit isn't where it should be, don't get lost looking for it
	fatal	'?Header not found'
@@2:	mov	dx,16d		;found sync bit, get header word
	call	getbit
	cmp	ax,ds:header	;is it what we expect?
	jne	@@4
	mov	dx,256d+1	;# words to get including checksum
	mov	ds:chksum,0	;clear out checksum
@@3:	push	dx		;save
	mov	dx,16d		;get a data word
	call	getbit
	stosw			;save it
	add	ds:chksum,ax	;add it up
	pop	dx		;restore count
	dec	dx		;done all?
	jnz	@@3		;loop if not
	sub	di,2		;back up off checksum
	add	ax,ax		;checksum *2 (since we counted it in CHKSUM)
	cmp	ax,ds:chksum	;match?
	jne	@@5		;no
	ret			;happy return
@@4:	fatal	'?Header error'
@@5:	fatal	'?Checksum error'
;+
;
; Get DX bits into AX
;
; ch	current byte
; cl	# bits left in current byte (zeros shifted in to replace ones we took)
; si	buffer ptr
;
;-
getbit:	jcxz	@@2		;nothing left
@@1:	shl	ch,1		;get a bit
	rcr	ax,1		;into AX
	dec	dx		;count it
	jz	@@3		;done all
	dec	cl		;no, any left in byte?
	jnz	@@1		;yes, get next bit
@@2:	mov	ch,[si]		;fetch next byte
	inc	si
	mov	cl,8d		;reinit bit count
	jmp	short @@1
@@3:	dec	cl		;count final bit
	ret
;+
;
; Parse a word off the command line.
;
; si	cmd line ptr
; cx	# chars left
;
; On return, SI/CX are updated, and:
;
; bx	begn of word
; dx	length
;
; Or CF=1 if nothing left on line.
;
;-
getw:	jcxz	@@2		;EOL already
@@1:	mov	bx,si		;in case word starts here
	lodsb			;get a char
	cmp	al,' '		;blank or ctrl?
	ja	@@4		;no
	loop	@@1		;loop
@@2:	stc			;no luck
	ret
@@3:	lodsb			;get a char
@@4:	cmp	al,' '		;blank or ctrl?
	jbe	@@6		;yes, end of word
	cmp	al,'a'		;lower case?
	jb	@@5
	cmp	al,'z'		;hm?
	ja	@@5
	and	al,not 40	;yes, convert
	mov	[si-1],al	;put back
@@5:	loop	@@3		;loop
	inc	si		;compensate for next inst
@@6:	dec	si		;unget
	mov	dx,si		;calc length
	sub	dx,bx		;CF=0
	ret
;+
;
; Handle fatal error.
;
;-
err1:	pop	si		;catch R.A.
	call	err2		;print it
	mov	si,offset crlf	;point at <CRLF>
	call	err2
	mov	ax,4C01h	;func=punt
	int	21h
;
err2:	lodsb			;get length of string
	cbw			;AH=0
	mov	dx,si		;point at string
	mov	cx,ax		;length
	mov	bx,0002h	;STDERR
	mov	ah,40h		;func=write
	int	21h
	ret
;
crlf	db	2,cr,lf		;<CRLF> as counted string
;
usage	db	'RK05WL Zip disk RK05 image insert/extract program',cr,lf
	db	'Copyright (C) 1998 by D Bit.  All rights reserved.',cr,lf
	db	'This program may be freely copied.',cr,lf
	db	cr,lf
	db	'Usage:  RK05WL +/- vol zip rk05',cr,lf
	db	cr,lf
	db	'+/-     function:  + means insert (into Zip image to RK05)'
	db	cr,lf
	db	'                   - means extract (from Zip image into RK05)'
	db	cr,lf
	db	'vol     volume number as a decimal number, 0-23',cr,lf
	db	'zip     name of 96 MB Zip disk image file',cr,lf
	db	'rk05    name of 2.5 MB RK05 disk image file',cr,lf
	db	cr,lf
	db	'$'
;
func	db	1 dup(?)	;function to perform, +=insert, -=extract
part	dw	1 dup(?)	;partition #
ziphnd	dw	1 dup(?)	;Zip disk image file handle
rkhnd	dw	1 dup(?)	;RK05 disk image file handle
chksum	dw	1 dup(?)	;sector checksum
header	dw	1 dup(?)	;header word for this track (must mask out b4)
;
cylbuf	db	8192d*2 dup(?)	;raw cylinder input data
fmtbuf	db	12d*512d*2 dup(?) ;formated data buf
	dw	1 dup(?)	;(space for checksum word on last sector)
;
code	ends
	end	start
