
;	-----------------------------------------------------------
;
;	"Squeezed" files can be generated by the program SQ.COM,
;	(SIG/M 58.26) and then restored by the program USQ.COM
;	(SIG/M 58.27), both of which appear to have been compiled
;	from BDS "C". The scheme is to generate a Huffman code,
;	after a preliminary pass which replaces all characters
;	repeated three or more times by a special code, namely
;	<char><90H><count>. The squeezed file consists of a marker
;	<FF76H>, checksum, the name of the original file, decoding
;	dictionary, and finally an encoded copy of the original file.
;
;	The squeezed file terminates with the encoded version of an
;	end marker (non-ASCII 100H) which forms part of the bit
;	stream. The tail end of the original file, on past the ^Z,
;	is encoded with the rest; the tail of the squeezed file is
;	generally trash that is discarded after the [end] marker is
;	encountered. Being both coded and part of the bit stream,
;	its presence is not visible in a Hex dump of the squeezed
;	file.
;
;	It appears that the whole file is encoded, without regard
;	to whether it is an ASCII file or not; if a real 90H forms
;	part of the file, it is encoded with a count of zero, thus
;	binary files can also be squeezed and unsqueezed.
;
;	TYSQ.ASM will read a squeezed file, typing it on the console.
;	Executing TYSQ.COM with a null command line will produce a
;	tutorial, describing its operation.
;
;		     TYSQ.ASM  Copyright (C) 1984
;		    Universidad Autonoma de Puebla
;			    July 1, 1984
;
;	[Harold V. McIntosh, 1 July 1984]
;	-----------------------------------------------------------

BDOS	equ	0005H		;BDOS entry point

tfcb	equ	5CH		;CP/M's FCB
bufr	equ	80H		;input buffer address

;	Non-graphic characters

CR	equ	0DH		;carriage return
LF	equ	0AH		;line feed

;	-------------
	org	0100H
;	-------------

begn:	lxi	sp,stak
	lda	tfcb+1		;CP/M's FCB
	cpi	' '
	jz	tuto		;type tutorial message
	lxi	h,tfcb+10	;CP/M's FCB
	mov	a,m
	cpi	'Q'
	jz	yext
	lxi	d,nsqz		;'not a squeezed file'
	call	mssg		;send message to console
	jmp	gbye		;return to CP/M

;	Initializations.

yext:	call	opef		;open input file
	lxi	h,0000
	shld	bctr		;byte counter - input buffer
	shld	smck		;developing checksum
	shld	pctr		;"program counter"
	lxi	h,lbuf		;line buffer
	shld	lptr		;line pointer
	mvi	a,0
	sta	cflg		;z/nz=not/.COM
	mvi	a,1
	sta	roco		;bit rotation counter
	mvi	a,16
	sta	colu		;column counter
	sta	rown		;row number
	lxi	b,0000		;byte counter, kept in BC

;	Set up code table. Squeezed files seem to begin with the
;	hexadecimal word (FF76) stored in Intel byte order, which
;	would not be likely to start an unsqueezed file.

cota:	call	rbyt		;fetch one byte from input stream
	cpi	076H
	jnz	cote		;mssg: 'not squeezed file'
	call	rbyt		;fetch one byte from input stream
	cpi	0FFH
	jz	rchk		;read checksum
cote:	lxi	d,nsqz		;'not a squeezed file'
	call	mssg		;send message to console
	jmp	gbye		;return to CP/M

;	The "squeezed" marker is followed by a two-byte checksum,
;	which is the simple sum of all the one-byte characters in
;	the source file, carried as a two byte sum modulo 2**16.

rchk:	call	rwor		;fetch two bytes from input stream
	shld	cksm		;given checksum

;	Unsqueezed file name. It is an ASCII sequence, may be lower
;	case if SQ.COM received it in response to a prompt, ending
;	with a zero byte. Some trash may be present if SQ.COM wasn't
;	used correctly.

loop:	call	rbyt		;fetch one byte from input stream
	ora	a
	jz	ldic		;load code dictionary
	cpi	'.'
	jnz	loos
	call	cona		;type character at console
	call	rbyt
	mov	b,a
	call	ucfo
	cpi	'C'
	mov	a,b
	jnz	loos
	call	cona		;type character at console
	call	rbyt
	mov	b,a
	call	ucfo
	cpi	'O'
	mov	a,b
	jnz	loos
	call	cona		;type character at console
	call	rbyt
	mov	b,a
	call	ucfo
	cpi	'M'
	mov	a,b
	jnz	loos
	lxi	h,cflg		;z/nz=not/.COM
	mvi	m,0FFH
loos:	call	cona		;type character at console
	jmp	loop

;	Load code dictionary. It is preceded by its two-byte length,
;	and consists of a series of pairs of two-byte addresses. For
;	each bit in the code, select the first element (0) or the
;	second (1) element of the pair. If the pair is positive, it
;	is the table entry (code + 4*index) at which to continue with
;	the next bit. If the pair is negative, it is the complement
;	of the coded ASCII character (low order byte except for [end]).

ldic:	call	rwor		;fetch two bytes from input stream
	mov	c,l
	mov	b,h
	lxi	d,code		;decoding table
looq:	call	rbyt		;fetch one byte from input stream
	stax	d
	inx	d
	call	rbyt		;fetch one byte from input stream
	stax	d
	inx	d
	call	rbyt		;fetch one byte from input stream
	stax	d
	inx	d
	call	rbyt		;fetch one byte from input stream
	stax	d
	inx	d
	dcx	b
	mov	a,c
	ora	b
	jnz	looq

	call	crlf		;type CR,LF

;	Type unsqueezed code. Beware of the [end] marker,
;	and also the repeat marker 90H which occurs in the
;	combination <char><90H><count>. When count is zero,
;	90H itself is intended; otherwise <char> is to be
;	repeated <count> times, including the occurrence just
;	before 90H was seen.

tusq:	call	dnch		;decode next character
	jc	chek		;verify the checksum
	cpi	090H		;repeat last character
	jnz	tusu		;normal character
	call	dnch		;decode next character
	ora	a
	jnz	tusr
	mvi	a,090H
	call	type
	jmp	tusq

tusr:	dcr	a		;get count, adjust it
	mov	b,a		;set count aside
tust:	lda	lach		;last character typed
	call	type		;type char & add to checksum
	dcr	b		;update count
	jnz	tust		;repeat until exhausted
	jmp	tusq		;type unsqueezed code

tusu:	sta	lach		;last character typed
	call	type		;type char & add to checksum
	jmp	tusq		;type unsqueezed code

;	Decode next character.

dnch:	lxi	h,code		;decoding table
dncr:	call	rbit		;read next bit
	jnc	dncs		;skip for 1, stay for 0
	inx	h
	inx	h
dncs:	mov	e,m		;get next offset
	inx	h
	mov	d,m
	mov	a,d
	cpi	0FEH		;FEFF means [end]
	jz	dnct
	ora	a
	jp	dncu		;p means new offset
	mov	a,e		;m means complemented char
	cma
	stc
	cmc
	ret

dnct:	stc			;flag [end] with carry bit
	ret

;	Calculate <code>+4*<offset>.

dncu:	lxi	h,code		;decoding table
	dad	d
	dad	d
	dad	d
	dad	d
	jmp	dncr

;	Check for interrupt request.
;	Clear out interrupting character if present.
;	Can't avoid CP/M echoing it when cleared, but at
;	least it won't be waiting to prefix the command
;	line after exit.

break:	mvi	c,11		;(0B) console status
	call	BDOS
	rrc
	rnc
	lxi	d,dint		;'Dump interrupted.'
	call	mssg		;send message to console
	mvi	c,1		;(01) read console
	call	BDOS
	jmp	gbye		;return to CP/M

;	Type several spaces.

four:	call	dubl
dubl:	call	sngl		;type space
sngl:	mvi	a,' '
	jmp	cona		;type character at console

;	Type CR, LF.

crlf:	mvi	a,CR
	call	cona		;type character at console
	mvi	a,LF
	jmp	cona		;type character at console

;	Type unsqueezed text and accumulate checksum.
;	Don't send ^Z's to the console. Decompose into
;	hexadecimal nibbles if it is a .COM file.

type:	push	h
	push	d
	push	b
	push	psw
	mov	b,a
	lhld	smck		;developing checksum
	add	l
	mov	l,a
	mov	a,h
	aci	0
	mov	h,a
	shld	smck		;developing checksum
	lda	cflg		;z/nz=not/.COM
	ora	a
	mov	a,b
	jz	typp
	call	tyfo
	jmp	tyqq

typp:	cpi	1AH
cnz	cona		;type character at console
tyqq:	call	break		;check for interrupt request
	pop	psw
	pop	b
	pop	d
	pop	h
	ret

;	Type nibbles, bytes, words according to entry point.

twor:	mov	a,h
	call	tbyt		;type byte
	mov	a,l
tbyt:	push	psw
	rrc
	rrc
	rrc
	rrc
	call	tyoo
	pop	psw
tyoo:	ani	0FH
	adi	90H
	daa
	aci	40H
	daa
	jmp	cona		;type character at console

;	Type in the format of a hexadecimal dump.

tyfo:	push	psw
	lda	colu		;column counter
	cpi	16
	jnz	tyfu
	lhld	pctr		;"program" counter
	call	twor		;type word
	lxi	d,0010H
	dad	d
	shld	pctr		;"program" counter
	call	sngl		;type space
tyfu:	call	sngl		;type space
	pop	psw
	lhld	lptr		;line pointer
	mov	m,a
	inx	h
	shld	lptr		;line pointer
	call	tbyt		;type byte
	lxi	h,colu		;column counter
	dcr	m
	rnz
	mvi	m,16
	call	tyal		;type ASCII line
	lxi	h,rown		;row number
	dcr	m
	jnz	crlf		;type CR,LF
	mvi	m,16
	call	crlf		;type CR,LF
	jmp	crlf		;type CR,LF

;	Type ASCII line.

tyal:	call	four		;type four spaces
	mvi	b,16
	lxi	h,lbuf		;line buffer
	shld	lptr		;line pointer
tyty:	mov	a,m
	call	dorp
	call	cona		;type character at console
	inx	h
	dcr	b
	jnz	tyty
	ret

;	Upper case fold.

ucfo:	cpi	'a'
	rc
	cpi	'{'
	rnc
	ani	5FH
	ret

;	Put a dot in place of non-printables.

dorp:	cpi	' '
	jc	dopp
	cpi	7FH
	rc
dopp:	mvi	a,'.'
	ret

;	Send character in accumulator to console.

cona:	push	h
	push	d
	push	b
	mvi	c,2		;(02) char to console
	mov	e,a
	call	BDOS
	pop	b
	pop	d
	pop	h
	ret

;	Send message to the console.

mssg:	mvi	c,9		;(09) print buffer
	jmp	BDOS

;	Verify the checksum.

chek:	lhld	cksm		;given checksum
	xchg
	lhld	smck		;developing checksum
	mov	a,l
	cmp	e
	jnz	chen		;checksum failed
	mov	a,h
	cmp	d
	jz	exit		;return to CP/M
chen:	lxi	d,chno		;'Checksum failure.'
	call	mssg		;send message to console
gbye:	call	crlf		;type CR,LF
exit:	jmp	0000

;	Open input file.

opef:	xra	a
	sta	tfcb+32		;CP/M's FCB
	lxi	d,tfcb		;CP/M's FCB
	mvi	c,15		;(0F) open file
	call	BDOS
	inr	a
	rnz
	lxi	d,nfil		;'requested file not found'
	call	mssg		;send message to console
	jmp	gbye		;return to CP/M

;	Read one bit at a time.

rbit:	push	h
	lxi	h,roco		;bit rotation counter
	dcr	m
	jnz	rbiu
	mvi	m,8
	call	rbyt		;fetch one byte from input stream
	sta	roby		;rotating byte
rbiu:	lda	roby		;rotating byte
	rar
	sta	roby		;rotating byte
	pop	h
	ret

;	Read one word.

rwor:	call	rbyt		;fetch one byte from input stream
	push	psw
	call	rbyt		;fetch one byte from input stream
	pop	h
	mov	l,h
	mov	h,a
	ret

;	Read one byte, refill buffer as needed.

rbyt:	lhld	bctr		;byte counter - input buffer
	mov	a,l
	ora	h
	jnz	rbyu
	push	b
	push	d
	call	rdsk
	lxi	h,bufr
	shld	bptr		;byte pointer - input buffer
	lxi	h,0080H
	pop	d
	pop	b
rbyu:	dcx	h
	shld	bctr		;byte counter - input buffer
	lhld	bptr		;byte pointer - input buffer
	mov	a,m
	inx	h
	shld	bptr		;byte pointer - input buffer
	ret

;	BDOS disk read of one sector.

rdsk:	push	h
	push	d
	push	b
	lxi	d,tfcb		;CP/M's FCB
	mvi	c,20		;(14) read one record
	call	BDOS
	pop	b
	pop	d
	pop	h
	ret

tuto:	lxi	d,logo		;'ICUAP' logo
	call	mssg		;send message to console
	jmp	gbye		;return to CP/M

nfil:	db	CR,LF,'Requested file not present.$'
dint:	db	CR,LF,'Dump interrupted.$'
nsqz:	db	CR,LF,'Not a squeezed file.$'
chno:	db	CR,LF,'Checksum failure.$'
logo:	db	CR,LF,'Type Squeezed File/ICUAP/July 1, 1984.',CR,LF,CR,LF
	db	'	TYSQ [D:]FILE.EXT',CR,LF,CR,LF
	db	'will unsqueeze FILE.EXT and type it at the',CR,LF
	db	'console. The convention for squeezed files',CR,LF
	db	'is that EXT has the form ?Q?. Press any key',CR,LF
	db	'to stop the display.',CR,LF
	db	'$'

	ds	16
stak:	ds	2
lach:	ds	1		;last character typed
roco:	ds	1		;rotating bit counter
roby:	ds	1		;rotating byte
bctr:	ds	2		;byte counter - input buffer
bptr:	ds	2		;byte pointer - input buffer
cksm:	ds	2		;given checksum
smck:	ds	2		;developing checksum
cflg:	ds	1		;z/nz=not/.COM
rown:	ds	1		;row counter
pctr:	ds	2		;"program" counter
colu:	ds	1		;column counter
lptr:	ds	2		;line pointer
lbuf:	ds	16		;line buffer
code:	ds	1024		;decoding table

	end
