+ARCHIVE+ chirp.asm     1462 12/21/1990 10:32:20
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	chirp

	.MODEL	LARGE

DeviceDelay  macro  	;Delay a bit to allow time for device to respond 
	jmp $+2		; to successive I/O accesses
	jmp $+2
	jmp $+2
	endm

	.DATA		;initialized near data
;Freq	DW	2280,2031,1809,1709,1521,1355,1207,1139,1015, 904,
;	DW	854, 760, 677, 603, 569, 507
Freq	DW	1139, 1355, 760		;c, a, g   
PortB	equ	61h
CommReg	equ	43h
Latch2	equ	42h

	.CODE
	public _chirp
_chirp  proc
	push bp
	push di
	push si
	cld
	mov al,0b6h	;init channel 2 for mode 3
	out CommReg,al
	in al,PortB	;0 bit enables timer, 1 connects spkr to timer
	or al,3	   	;Enable speaker and timer channel 2
	out PortB,al
	mov si,offset Freq
	mov di,3	;do 3 notes
NextNote: lodsw		;Get Freq to use
	out Latch2,al	;send low byte of freq to latch reg
	DeviceDelay	;Delay so device can respond to successive I/O accesses
	mov al,ah
	out Latch2,al	;send high byte of freq to latch reg

	xor ah,ah
	int 1ah		;get time count
	mov bx,dx	;Low word of BIOS count from DX->BX
	inc bx		;tone duration = 1 tick
Sustain: int 1ah
	cmp dx,bx	;Cmp low word of BIOS count in DX to BX
	jb Sustain	;If below, loop
	dec di
	jg NextNote	;do next note if not zero
	in al,PortB
	and al,0fch	;Turn off speaker bit
	out PortB,al
	pop si
	pop di
	pop bp
	ret
_chirp  endp

	END
+ARCHIVE+ cmsasm.asm    7316  3/14/1991  6:48:08
; Victor Library, Copyright (c) 1990-1991, ALL RIGHTS RESERVED
;	Catenary Systems
;	470 Belleview
;	St Louis, MO 63119
;	(314) 962-7833
; Contents:
;	cmclose_
;	cmcloseall_
;	cmgetrow_
;	cmopen_
;	cmputrow_

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

	extrn _memcpy_:far

MAXHNDLS   EQU    8	;Max no. of convent'l memory (CM) handles available
;Error codes:
NO_ERROR   EQU	  0	;No error
CM_ERR	   EQU	-20	;Conventional memory handle overflow

	.DATA
	EVEN
;Our handle/buffer address table
HandTab  DD  MAXHNDLS dup (0)

	.CODE
;Add a buffer address to the handle/buffer address table and return a
; CM handle (not 0). Function returns NO_ERROR or CM_ERR (no room is
; left in the table). Usage: int cmopen_(int *cmhandle, UCHAR huge *ibuff)
; Uses AX, BX, CX, ES
handl_addr     equ dword ptr @ab[bp]	;CM handle address
ibuff_addr     equ dword ptr @ab[bp+4]	;Buffer address to add to table
	public _cmopen_
_cmopen_  proc  far
	push bp
	mov bp,sp
	push di
	push ds
	mov bx,@Data		;Make sure HandTab is accessible
	mov ds,bx
	les di,ibuff_addr	;ES:DI -> ibuff;
	mov bx,offset HandTab	;DS:BX -> HandTab
	xor cx,cx
CMItop:	cmp cx,MAXHNDLS
	jge CMIby
;Is position in table empty?
	mov ax,[bx]
	or ax,[bx+2]	;HandTab[j] == NULL?
	jz CMStor  	;Jump if yes
;Position is not empty, advance pointer to next potential buffer address
	add bx,4
	inc cx
	jmp short CMItop
;Position is empty, save the buffer address
CMStor:	mov [bx],di		;HandTab[j] = ibuff;
	mov [bx+2],es
;Return the handle value
	les di,handl_addr	;ES:DI -> CM handle address
	inc cx
	mov es:[di],cx		;*handle = cmhandle = j + 1;
	mov ax,NO_ERROR
CMIXt:	pop ds
	pop di
	pop bp
	ret
CMIby:	mov ax,CM_ERR
	jmp short CMIXt
_cmopen_  endp

;int _cmopen(int *cmhandle, UCHAR huge *ibuff)
;{
;   int j;
;   for(j=0; j<MAXHNDLS; j++) {
;      if(HandTab[j] == NULL) {
;         HandTab[j] = ibuff;
;         *cmhandle = j+1;
;         return(NO_ERROR);
;         }
;      }
;   return(CM_ERR);
;}

;Zero the buffer address in the handle/buffer address table if 
; image->ibuff equals the seg:offset value in the table for cmhandle.
; This check is to allow a routine to call cmclose() even if a cm handle
; was not issued. Usage: void cmclose_(imgdes *image, int cmhandle);
; Uses AX, BX, CX, and DX
cmimage	  equ dword ptr @ab[bp]		;Image descriptor
cmhndle   equ  word ptr @ab[bp+4]   	;CM handle value
	public _cmclose_
_cmclose_  proc  far
	push bp
	mov bp,sp
	push ds
;if(inrange(1, cmhndle, MAXHNDLS+1))
	mov ax,cmhndle
	or ax,ax
	jz CMRby
	cmp ax,MAXHNDLS+1
	ja CMRby
;Set DX:CX = cmimage->ibuff seg, offset
	lds bx,cmimage		;DS:BX -> cmimage->ibuff
	mov dx,[bx+2]		;DX = cmimage->ibuff segment
	mov cx,[bx]		;CX = cmimage->ibuff offset
;HandTab[cmhndle-1] = NULL;
	mov bx,@Data		;Make sure HandTab is accessible
	mov ds,bx
	mov bx,ax
	dec bx			;BX = cmhandle - 1
	shl bx,1
	shl bx,1		;Use BX as index into HandTab
	add bx,offset HandTab	;ES:BX -> &HandTab[cmhandle-1]
;Compare image->ibuff to HandTab[cmhandle-1])
	cmp dx,[bx+2]		;Compare segments
	jne CMRby
	cmp cx,[bx]		;Compare offsets
	jne CMRby
;image->ibuff == HandTab[cmhandle-1]), set the entry to zero
	mov word ptr [bx],0	;Zero entry in HandTab
	mov word ptr [bx+2],0
CMRby:	pop ds
	pop bp
	ret
_cmclose_  endp

;void cmclose_(imgdes *image, int cmhandle)
;{
;   if(inrange(1, cmhandle, MAXHNDLS+1) && image->ibuff==HandTab[cmhandle-1])
;      HandTab[cmhandle-1] = NULL;
;}

;Reset all handle buffer addresses to NULL
; Usage: void cmcloseall_(void); Uses AX, CX, ES
	public _cmcloseall_
_cmcloseall_  proc  far
	push bp
	push di
	mov di,@Data		;Make sure HandTab is accessible
	mov es,di
	mov di,offset HandTab	;ES:DI-> HandTab
	mov cx,MAXHNDLS*2
	xor ax,ax
	rep stosw
	pop di
	pop bp
	ret
_cmcloseall_  endp

;void cmcloseall_(void)
;{
;   memset_(HandTab, 0, MAXHNDLS*sizeof(UCHAR far *);
;}

;Copy 'cols' bytes from a row in conventional memory (CM) to conventional
; memory. Buffer to receive the row must be large enough to hold 'cols'
; bytes. Does not check that cmhandle owns any CM. Call cmopen_ before
; calling this function to set up cmhandle properly. Returns NO_ERROR.
; Usage: int cmgetrow_(des, cmhandle, stx, sty, cols, width);
; Uses AX, BX, CX, DX, and ES.
desofs	    equ		  @ab[bp]	;Destination for data
desseg	    equ		  @ab[bp+2]
cm_handle   equ  word ptr @ab[bp+4]	;Handle issued by cmopen_()
cm_stx      equ  word ptr @ab[bp+6]	;X,Y coords in source buffer
cm_sty      equ  word ptr @ab[bp+8]
cm_cols     equ  word ptr @ab[bp+10]	;Bytes to retrieve
cm_width    equ  word ptr @ab[bp+12]	;CM buffer width
	public _cmgetrow_
_cmgetrow_  proc  far
	push bp        
	mov bp,sp
	call calc_addr		;Return source address in DX:AX
;memcpy_(des, src, cols);
	push cm_cols		;Push bytes to copy
	push dx			;Push source seg
	push ax			;Push source ofs
	push desseg		;Push dest seg
	push desofs		;Push dest ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
	mov ax,NO_ERROR
	pop bp
	ret
_cmgetrow_  endp

;int cmgetrow_(des, cmhandle, stx, sty, cols, width);
;{
;   long saddr=stx+sty*(long)width;
;   UCHAR huge *src;
;   src = HandTab[cmhandle-1][saddr];
;   memcpy_(des, src, cols);
;   return(NO_ERROR);
;}

;Copy 'cols' bytes from a row in conventional memory (CM) to conventional
; memory. Does not check that cmhandle owns any CM. Call cmopen_() before
; calling this function to set up cmhandle properly. Returns NO_ERROR.
; Usage: int cmputrow(src, cmhandle, stx, sty, cols, width);
; Uses AX, BX, CX, DX, and ES.
srcofs	    equ		  @ab[bp]	;Where to get the row of image data
srcseg	    equ		  @ab[bp+2]
cm_handle   equ  word ptr @ab[bp+4]	;Handle issued by cmopen_()
cm_stx      equ  word ptr @ab[bp+6]	;X,Y coords in source buffer
cm_sty      equ  word ptr @ab[bp+8]
cm_cols     equ  word ptr @ab[bp+10]	;Bytes to retrieve
cm_width    equ  word ptr @ab[bp+12]	;CM buffer width
	public _cmputrow_
_cmputrow_  proc  far
	push bp        
	mov bp,sp
	call calc_addr		;Return dest address in DX:AX
;memcpy_(des, src, cols);
	push cm_cols		;Push bytes to copy
	push srcseg		;Push source seg
	push srcofs		;Push sourceofs
	push dx			;Push des seg
	push ax			;Push des ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
	mov ax,NO_ERROR
	pop bp
	ret
_cmputrow_  endp

;int cmputrow(src, cmhandle, stx, sty, cols, width)
;{
;   long daddr=stx+sty*(long)width;
;   UCHAR huge *des;
;   des = HandTab[cmhandle-1][daddr];
;   memcpy_(des, src, cols);
;   return(NO_ERROR);
;}

;Return source address in DX:AX. Uses AX, BX, CX, DX, and ES.
calc_addr  proc  near
	mov ax,cm_sty
	mul cm_width
	add ax,cm_stx
	adc dx,0		;DX:AX = saddr
;src = HandTab[cmhandle-1][saddr];
	mov bx,cm_handle
	dec bx
	shl bx,1
	shl bx,1		;CX = (cmhandle-1) * 4
	mov cx,@Data		;So HandTab is accessible
	mov es,cx
	add bx,offset HandTab	;DS:BX -> HandTab[cmhandle-1]
;src = HandTab[cmhandle-1][saddr];
	add ax,es:[bx]		;AX = saddr ofs + ibuff ofs
	adc dx,0		;DX:AX = saddr
;Convert DX:AX to an address
	mov cl,4		;0000 0000 0000 0aaa ->
	ror dx,cl		;0aaa 0000 0000 0000
	add dx,es:[bx+2]	;DX = ibuff seg + 1000h(0)
	ret
calc_addr  endp

	END
+ARCHIVE+ cproc.asm     3007  5/24/1991 12:48:34
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;		Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
;   Contents:
;	find_palno_	Search thru RGB table and return best color number

	.MODEL	LARGE
@ab	  equ	  6	;Equate for large model

	.CODE
;Search thru RGB table, calc minimum error, and return best color number
; Usage: int _pascal find_palno_(int red, int grn, int blu,
; UCHAR *rgbtab, int pals);
pals	equ  word ptr @ab[bp]	;Number entries in palette table, usually 768
rgbtab	equ dword ptr @ab[bp+2]	;Original palette table to match to
blu	equ  word ptr @ab[bp+6]
grn	equ  word ptr @ab[bp+8]
red	equ  word ptr @ab[bp+10]

minerr	equ  word ptr [bp-2]	;Minimum error value
colno	equ  word ptr [bp-4]	;Color number
minpal	equ  word ptr [bp-6]	;Color number with min error
	public FIND_PALNO_
FIND_PALNO_  proc  far
	push bp        
	mov bp,sp     
	sub sp,8
	push di
	push si
	push ds
	cld
	mov minerr,768
; pals /= 3;
	xor dx,dx
	mov ax,pals	;Usually 48 or 768
	mov bx,3
	div bx
	mov pals,ax	;Pals usually = 16 or 48
	lds si,rgbtab	;DS:SI -> rgb table
	mov bx,red
	mov dx,grn
	mov cx,blu
	xor di,di
;Find minimum error
FPTop:	mov colno,di
	lodsb		;Get value from rgbtab
	xor ah,ah
	sub ax,bx	;BL = redval
	jge RdPlus 	;If value is negative, negate
	neg ax		;AX = reddiff = abs(*rgbtab++ - redval);
RdPlus:	mov di,ax	;Put sum of errors in DI
	lodsb		;Get value from rgbtab
	xor ah,ah
	sub ax,dx	;DX = grnval
	jge GnPlus 	;If value is negative, negate
	neg ax		;AX = grndiff = abs(*rgbtab++ - grnval);
GnPlus:	add di,ax	;Put sum of errors in DI
	lodsb		;Get value from rgbtab
	xor ah,ah
	sub ax,cx	;CX = bluval
	jge BlPlus 	;If value is negative, negate
	neg ax		;AX = bludiff = abs(*rgbtab++ - bluval);
BlPlus:	add ax,di	;Put sum of errors in AX
	mov di,colno	;Use DI to hold colno
	or ax,ax	;Error == 0?
	jz Match	;If error == 0, we're done
	cmp ax,minerr
	jae ContIt	;if(error < minerr) {
	mov minerr,ax	;   minerr = error;
	mov minpal,di	;   minpal = colno;
ContIt:	inc di
	cmp di,pals	;Usually 16 or 256
	jae FPdone	;Jump if we finished our search thru the RGB table
	jmp short FPTop
Match:	mov minpal,di	;   minpal = colno;
FPdone:	mov ax,minpal
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret 12
FIND_PALNO_  endp

;int _pascal find_palno_(int red, int grn, int blu, UCHAR *rgbtab, int pals)
;{
;   int k, min_k, rerr, gerr, berr, err, minerr=768;
;   /* Find best palette entry */
;   for(k=0; k<pals/3; k++) {
;      /* Find minimum error */
;      if((rerr = red - *rgbtab++) < 0)
;         rerr = -rerr;
;      if((gerr = grn - *rgbtab++) < 0)
;         gerr = -gerr;
;      if((berr = blu - *rgbtab++) < 0)
;         berr = -berr;
;      if((err = rerr + gerr + berr) == 0) {
;         min_k = k;
;         break;
;         }
;      if(err < minerr) {
;         min_k = k;
;         minerr = err;
;         }
;      }
;   return(min_k);
;}
	END
+ARCHIVE+ cpu186.asm    1084 12/21/1990 10:32:22
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	detectcpu186	;Determine if the computer's CPU is > 8088/8086

	.MODEL	LARGE
@ab	equ   6		;Equate for large model

;Error codes:
NO_ERROR   equ	  0		;No error
BAD_RANGE  equ   -1 		;Range error
BAD_DSK	   equ	 -3		;Disk full, file not written
TIMEOUT    equ  -34		;Function timed out

	.CODE
;Determine if the computer's CPU is an 8088/8086 or better. If CPU is an
; 80186 or better, return 1, else 0. Uses AX and BX.
	public  _detectcpu186
_detectcpu186  proc  far
;Test for 86/88
	pushf
	xor ax,ax
	xor bx,bx	;Assume an 8088/8086 (BX = 0)
	push ax		;try to load FLAGS with 0
	popf
	pushf		;now test contents of FLAGS
	pop ax		;FLAGS -> AX
	and ax,0f000h	;Isolate high bits
	cmp ax,0f000h	;Test if bits were set
	je CIXit	;Bits set indicate 86/88
	inc bx		;It's a 186, 286, or 386, set BX = 1
CIXit:	mov ax,bx	;Ret 0 if it's an 8086, ret 1 if 80x86
	popf
	ret
_detectcpu186  endp

	END
+ARCHIVE+ crtsrc.asm   20539  1/24/1991  9:43:12
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	crt_src_
;	crt_grcp_
;	set_attr_
;	setvideomode
;	set_wcol_
;	strcpyn_
;	memmove_
;	memcpy_
;	memset_
;	set_raw_
;	set_vid_regs_
;	restore_vregs_
;	getvgapalette
;	setvgapalette
;	setegapalette

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

;Error codes:
NO_ERROR   EQU	  0		;No error
BAD_RANGE  EQU	 -1 		;Range error
VMODE_ERR  EQU	-14		;Video mode was not successfully set
;Register addresses
READ_PIXEL   EQU  3c7h		;Read pixel addr reg
WRITE_PIXEL  EQU  3c8h		;Write pixel addr reg

	extrn _timeout:far

	.CODE
;Set cursor row, column position. Uses page 0.
; Usage: void crt_src_(int row, int col);
cs_row	equ  byte ptr @ab[bp]
cs_col	equ  byte ptr @ab[bp+2]
	public _crt_src_
_crt_src_  proc  far
	push bp
	mov bp,sp
	mov ax,0200h
	mov dh,cs_row		;Row
	mov dl,cs_col		;Column
	xor bh,bh		;Use video page 0
	int 10h
	pop bp
	ret
_crt_src_  endp

;Obtain current cursor position. Return it in AX, row in AH, column in AL.
; Usage: rwcol = crt_grcp_(void);
	public _crt_grcp_
_crt_grcp_  proc  far
	push bp
	mov ah,3
	xor bh,bh	;BH = page number
	int 10h
	mov ax,dx	;Return results in AX (AH has row, AL column)
	pop bp
	ret
_crt_grcp_  endp

;Set attribute bytes in a row
; Usage: void set_attr_(int attrib); Uses AX, BX, CX, DX, and ES.
attrib  equ  byte ptr @ab[bp]	;Attribute to set for the row
	public _set_attr_
_set_attr_  proc  far
	push bp
	mov bp,sp
	push di
	cld
;Get row we're on
	push cs			  ;Push CS so we can use a near call
	call near ptr _crt_grcp_  ;Returns row in AH, col in AL
	mov al,ah
	xor ah,ah	;AX has row
	mov dl,160
	mul dl		;AX = row * 160
	mov di,ax	;DI = row * 160
	mov ax,0b800h
	mov es,ax
	mov al,attrib	;AL gets attribute to use
	mov cx,80
StTop:	inc di
	stosb		;Write every other byte
	loop StTop
	pop di
	pop bp
	ret
_set_attr_  endp

;Set the fore/background color of a window and move the cursor to the 
; upper left corner of window.
; Usage: void set_wcol(int wxs, int wys, int wxe, int wye, int attrib);
	public _set_wcol_
win_xs	 equ  byte ptr @ab[bp]		;Window coordinates
win_ys	 equ  byte ptr @ab[bp+2]
win_xe	 equ  byte ptr @ab[bp+4]
win_ye	 equ  byte ptr @ab[bp+6]
win_attr equ  byte ptr @ab[bp+8]	;Color to set
_set_wcol_  proc  far
	push bp
	mov bp,sp
	mov bh,win_attr		;Window color
	mov cl,win_xs		;wxs -> CL
	mov ch,win_ys		;wys -> CH
	mov dl,win_xe		;wxe -> DL
	mov dh,win_ye		;wye -> DH
	mov ax,0600h
	int 10h
;Now set the cursor position to the left corner
	mov dx,cx
	xor bh,bh		;Page 0 !!!
	mov ah,2
	int 10h
	pop bp
	ret
_set_wcol_  endp

	.DATA
VgaPalRegs  label  byte		;Palette regs to reprogram
	DB  00h, 01h, 02h, 03h, 04h, 05h, 14h, 07h
	DB  38h, 39h, 3ah, 3bh, 3ch, 3dh, 3eh, 3fh
EgaConTab  DB  0, 8, 1, 9	;Values used to convert EGA palette
OldOmode   DB  0		;Old output mode value

	.CODE
;Set raw output mode for a device to ignore ctrl-z's, etc.
;Usage: void set_raw_(int mode, int handle);
otmode	 equ  byte ptr @ab[bp]	 ;If 1, set raw output mode
othandle equ  word ptr @ab[bp+2] ;Device handle
	public _set_raw_
_set_raw_  proc  far
	push bp
	mov bp,sp
	mov bx,othandle		;Device handle to use
	cmp otmode,0		;Set or restore the output mode?
	jz RsCook
;Set raw output mode
	mov ax,4400h		;Get device data
	int 21h
	mov OldOmode,dl		;Save current mode
	or dl,20h		;Set raw mode bit
SRWcnt:	xor dh,dh
	mov ax,4401h		;Set device data
	int 21h
	pop bp
	ret
;Restore original output mode
RsCook:	mov dl,OldOmode		;Restore old mode */
	jmp short SRWcnt
_set_raw_  endp

;Set a video mode. Returns NO_ERROR or VMODE_ERR if mode was not
; sucessfully set. Usage: int setvideomode(vmode);
vid_mode  equ  @ab[bp]
	public _setvideomode
_setvideomode  proc  far
	push bp
	mov bp,sp
	mov ax,word ptr vid_mode
	int 10h		;Set the display mode
;Make sure mode was successfully set
	mov ah,0fh
	int 10h		;Read the display mode
	cmp al,vid_mode	;Mode read = mode we tried to set?
	jne SVBdMd	;Jump if not
	mov ax,0500h
	int 10h		;Set video page 0
	mov ax,200	;Wait .2 sec for display to stabilize
	push ax
	call _timeout
	inc sp
	inc sp
	mov ax,NO_ERROR	 ;Success 
SVXit:	pop bp
	ret
SVBdMd:	mov ax,VMODE_ERR ;Mode read was not what we asked for
	jmp short SVXit
_setvideomode  endp

;Copy up to count chars of srcstr to deststr and append a null. Ret addr
; of deststr. Just returns if count = 1 or 0.
; Usage: char *strcpyn(char *deststr, char *srcstr, int count);
des_str equ dword ptr @ab[bp]
src_str equ dword ptr @ab[bp+4]
st_cnt	equ  word ptr @ab[bp+8]
	public  _strcpyn_
_strcpyn_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	push ds
	les di,des_str	;Get addr of dest string
	lds si,src_str	;Addr of source string
	mov bx,di	;Save dest offset for return
	mov cx,st_cnt	;Get no. of bytes to move
	dec cx		;Move up to count-1 bytes (leave room for null)
	jle ScpVV	;Protect against count = 0 or 1
ScpTop:	lodsb
	or al,al
	jz ScpWW	;Exit if null found
	stosb
	loop ScpTop	; or count exceeded
ScpWW:	xor al,al
	stosb		;Append a null
ScpVV:	mov ax,bx	;Return the address of string in DX:AX
	mov dx,es
	pop ds
	pop si
	pop di
	pop bp
	ret
_strcpyn_  endp

;Copy count chars of srcstr to deststr. Works for huge pointers!
; Return the address of deststr. 
; Usage: void *memcpy_(void huge *dest, void huge *src, unsigned count);
des_str equ dword ptr @ab[bp]
src_str equ dword ptr @ab[bp+4]
st_cnt	equ  word ptr @ab[bp+8]
	public  _memcpy_	;LARGE MODEL!!
_memcpy_  proc  far
	push bp
	mov bp,sp     
	push di
	push si
	push ds
	mov cx,st_cnt	;CX = bytes to copy
	jcxz McNoCt    	;Exit CX = 0 (no bytes to move)
	les di,des_str	;ES:DI -> Dest
	lds si,src_str	;DS:SI -> Source
McTp:	mov ax,cx	;AX = CX = count
	dec ax   	;AX = count-1
;Calc no. of bytes we can copy safely without crossing a segment boundary
; (based on how close DI and SI are to a seg boundary)
	mov dx,di	;DX = dest ofs
	not dx   	;DX = 0ffffh-ofs = bytes we can write
	sub ax,dx	;AX = count-1 - writable_bytes
			;CF = 1 if count < room available
	sbb bx,bx	;BX = 0 if there's NOT enough room
			;BX = ffff if count <= room available
	and ax,bx
	add ax,dx	;AX = bytes we can safely move based on DI
;Adjust for source ofs
	mov dx,si	;DX = source ofs
	not dx		;DX = 0ffffh-ofs = bytes we can read
	sub ax,dx	;AX = count-1 - readable_bytes
	sbb bx,bx
	and ax,bx
	add ax,dx
	inc ax		;AX = byts2copy based on DI and SI
	xchg cx,ax	;CX = byts2copy, AX = bytes we must copy (count)
	sub ax,cx	;AX = count - byts2copy = bytes left to be copied
	shr cx,1	;CX = byts2copy/2
	repz movsw	;Write byts2copy/2 words
	adc cx,cx	;Pick up carry
	repz movsb	;Write any odd bytes
	mov cx,ax     	;CX = bytes left to be copied
	jcxz McNoCt	;If CX = 0, no bytes left to copy, exit
	or si,si	;If SI = 0, SI crossed a seg, adjust DS
	jnz McSmSg	;Else, check DI
	mov ax,ds	;Adjust source seg for crossing seg boundary
	add ax,1000h
	mov ds,ax
McSmSg:	or di,di 	;If DI = 0, DI crossed a seg, adjust ES
	jnz McTp 	;Else copy next set of bytes
	mov ax,es	;Adjust dest seg for crossing seg boundary
	add ax,1000h
	mov es,ax
	jmp short McTp	;Copy next set of bytes
McNoCt:	lds ax,des_str	;Ret des_str in DX:AX
	mov dx,ds
	pop ds
	pop si
	pop di
	pop bp
	ret 
_memcpy_  endp

;Fill count characters starting at dest with memchr. Works for huge pointers!
; Return the address of deststr. 
; Usage: void *memset_(void huge *des, int memchr, unsigned count);
des_str equ dword ptr @ab[bp]
mem_chr equ  word ptr @ab[bp+4]
st_cnt	equ  word ptr @ab[bp+6]
	public  _memset_	;LARGE MODEL!!
_memset_  proc  far
	push bp
	mov bp,sp     
	push di
	mov cx,st_cnt	;CX = bytes to copy
	jcxz MsNoCt    	;Exit if CX = 0 (no bytes to move)
	les di,des_str	;ES:DI -> Dest
;Calc no. of bytes we can safely fill without crossing a segment boundary
; (based on how close DI is to a seg boundary)
	mov dx,di	;DX = dest ofs
	neg dx   	;DX = 10000h-ofs = bytes we can fill
	jz MStMx	;Jump if DI was 0 (enough room, even if CX = 0ffffh)
	sub dx,cx
	sbb bx,bx
	and dx,bx
	add dx,cx
	xchg dx,cx
	sub dx,cx
MStMx:	mov ax,mem_chr
	mov ah,al
	shr cx,1
	repz stosw
	adc cx,cx
	repz stosb
	mov cx,dx     	;CX = bytes left to be filled
	jcxz MsNoCt
	mov bx,es
	add bx,1000h
	mov es,bx
	shr cx,1
	repz stosw
	adc cx,cx
	repz stosb
MsNoCt:	les ax,des_str	;Ret des_str in DX:AX
	mov dx,es
	pop di
	pop bp
	ret
_memset_  endp

;Copy count characters of srcstr to deststr. Return address of dest.
; Usage: void *memmove_(void huge *dest, void huge *src, unsigned count);
des_str equ dword ptr @ab[bp]
src_str equ dword ptr @ab[bp+4]
st_cnt	equ  word ptr @ab[bp+8]
	public  _memmove_	;LARGE MODEL!!
_memmove_  proc  far
	push bp
	mov bp,sp
	push ds
	push di
	push si
	mov cx,st_cnt
	or cx,cx
	jnz MVcnt
	jmp MMbye     		;Exit if none to move
MVcnt:	les di,des_str
	lds si,src_str
	push ds			;Push srcseg
	push si			;Push srcofs
	push es			;Push desseg
	push di			;Push desofs
	call ptrdiff		;Calc pos or neg diff
	mov cx,st_cnt
	or dx,dx
	js MMTop		;Jump if DX < 0
	sub ax,cx
	sbb dx,0
	jnb MMTop
	dec cx
	add si,cx
	jnb MSSSeg
	mov ax,ds
	add ax,1000h
	mov ds,ax
MSSSeg:	add di,cx
	jnb MSDSeg
	mov ax,es
	add ax,1000h
	mov es,ax
MSDSeg:	inc cx
MSDsg1:	mov ax,cx
	dec ax
	sub ax,di
	sbb bx,bx
	and ax,bx
	add ax,di
	sub ax,si
	sbb bx,bx
	and ax,bx
	add ax,si
	inc ax
	xchg cx,ax
	sub ax,cx
	std
	repz movsb
	cld
	xchg cx,ax
	jcxz MMbye
	cmp si,-1
	jnz MDSSeg
	mov ax,ds
	sub ax,1000h
	mov ds,ax
MDSSeg:	cmp di,-1
	jnz MSDsg1
	mov ax,es
	sub ax,1000h
	mov es,ax
	jmp short MSDsg1 
;If DX<0 (desseg<srcseg)
MMTop:	mov ax,cx		;AX = CX = count
	dec ax
	mov dx,di
	not dx
	sub ax,dx
	sbb bx,bx
	and ax,bx
	add ax,dx
	mov dx,si
	not dx
	sub ax,dx
	sbb bx,bx
	and ax,bx
	add ax,dx
	inc ax
	xchg cx,ax
	sub ax,cx
	shr cx,1
	repz movsw
	adc cx,cx
	repz movsb
	xchg cx,ax
	jcxz MMbye
	or si,si
	jnz MVDsg
	mov ax,ds
	add ax,1000h
	mov ds,ax
MVDsg:	or di,di
	jnz MMTop
	mov ax,es
	add ax,1000h
	mov es,ax
	jmp short MMTop
MMbye:	lds ax,des_str		;Ret des_str
	mov dx,ds
	pop si
	pop di
	pop ds
	pop bp
	ret
_memmove_  endp

;Calculate positive or negative difference in pointers
; Usage: ptrdiff(string1, string2);
str1ofs  equ  word ptr [bp+4]
str1seg  equ  word ptr [bp+6]
str2ofs  equ  word ptr [bp+8]
str2seg  equ  word ptr [bp+10]
ptrdiff  proc  near
	push bp
	mov bp,sp
	mov ax,str1seg
	sub ax,str2seg	;AX = str1seg - str2seg
	sbb dx,dx	;Pick up borrow (DX = 0 or 0xffff)
	add ax,ax	;ffff,ffff + CF=1 -> ffff, ffff,ffff + CF=0 -> 0
	adc dx,dx	;Mult diff by 16 and keep CY
	add ax,ax
	adc dx,dx
	add ax,ax
	adc dx,dx
	add ax,ax
	adc dx,dx	;ffff,ffff + CF=1 -> ffff, ffff,ffff + CF=0 -> fffe	
	add ax,str1ofs
	adc dx,0	;ffff,0 + CF=1 -> 0, ffff,0 + CF=0 -> ffff	
	sub ax,str2ofs
	sbb dx,0	;ffff,0 + CF=1 -> fffe, ffff,0 + CF=0 -> ffff	
	mov sp,bp
	pop bp
	ret 8
ptrdiff  endp

;VGA/EGA register equates.
GDC_INDEX	equ	3ceh	;GDC index register
GDC_SET_RESET	equ	0	;GC set/reset register
GDC_ROTATE	equ	3	;GDC data rotate/logical function reg index
GDC_ENB_SRESET  equ	1	;GC enable set/reset register
GDC_MODE	equ	5	;GDC Mode reg
GDC_BIT_MASK	equ	8	;GDC bit mask register index

TS_INDEX	equ	3c4h	;TS index register
TS_MAP_MASK	equ	2	;TS map mask register

;Initialize various VGA/EGA registers.
; Usage: void set_vid_regs_(int color);
pic_color  equ  byte ptr @ab[bp]	;Color to set
	PUBLIC  _set_vid_regs_
_set_vid_regs_  proc  far
	push bp
	mov bp,sp
	mov dx,GDC_INDEX
	mov al,GDC_SET_RESET
	out dx,al
	inc dx
	mov al,pic_color
	out dx,al
	dec dx
	mov al,GDC_ENB_SRESET
	out dx,al
	inc dx
	mov al,0fh			;Enable 4 planes
	out dx,al
	pop bp
	ret
_set_vid_regs_  endp

	.DATA
	EVEN
ResetValues  DB  GDC_MODE,0,  GDC_BIT_MASK,0ffh,  GDC_ROTATE,0
	     DB  GDC_ENB_SRESET,0,  GDC_SET_RESET,0
	.CODE
;Restore various VGA/EGA registers to default settings
; Usage: void restore_vregs_(void);
	PUBLIC  _restore_vregs_
_restore_vregs_  proc  far
	push bp
	push si
	cld
	mov si,offset ResetValues
	mov dx,GDC_INDEX
	mov cx,5		;Restore 5 GDC regs
RstGDC:	lodsw
	out dx,al
	inc dx
	mov al,ah
	out dx,al
	dec dx			;DX -> GDC_INDEX
	loop RstGDC

	mov dx,TS_INDEX		;Restore TS reg
	mov al,TS_MAP_MASK
	out dx,al
	inc dx
	mov al,0fh
	out dx,al
	pop si
	pop bp
	ret
_restore_vregs_  endp

;Read current EGA/VGA palette settings and store results in paltab (paltab
; must be able to hold up to 768 bytes). NOTE: Only works for a VGA/MCGA
; display adapter (an EGA adapter can't be read). Paltab entries are 0->255.
; Returns no. of palette table entries -- 0, 48, or 768.
; Usage: int getvgapalette(UCHAR *paltab);
paltab  equ dword ptr @ab[bp]	;Where to store palette values
tmptab  equ  byte ptr [bp-20]	;Place 17 byte palette table here
	public _getvgapalette
_getvgapalette  proc  far
	push bp
	mov bp,sp
	sub sp,20
	push di
	push si
;Determine if a VGA/MCGA display adapter is present -- Look for VGA BIOS via
; int 10h, fctn 1ah. If VGA is present, on return AL = 1ah.
	mov ax,1a00h
	int 10h
	cmp al,1ah
	jne NoVGA		;if fctn not supported, no MCGA, VGA present, test for EGA
;Determine the video mode
	mov ah,0fh	;Get current video mode
	int 10h
	cmp al,010h	;640x350x16 mode
	je EGAmod
	cmp al,012h	;640x480x16 mode
	je EGAmod
;Assume VGA 320x200x256, 640x480x256, etc., mode
;Read current palette regs and save values
	mov dx,READ_PIXEL	;read pixel addr reg
	les di,paltab		;Where to store palette values
	xor ax,ax	;AL = first palette reg to read = 0
	out dx,al	;read pixel addr reg
	inc dx		;Point at Color value reg
	inc dx
	mov cx,256*3	;Read 256*3 regs
SvTop:	in al,dx	;Get a red, green, or blue value
	shl al,1	;Convert 0-63 values to 0-252
	shl al,1
	stosb		;Save it
	loop SvTop
	mov ax,768
GVPBy:	pop si
	pop di
	mov sp,bp
	pop bp
	ret
;EGA/VGA 640x350x16 mode -- read and convert 16 palette regs
; Works only for a VGA display adapter!
EGAmod:	mov ax,ss
	mov es,ax
	lea dx,tmptab	;Where to store 17 palette values (ES:DX)
	mov ax,1009h
	int 10h		;17 pal regs -> EgaPalRgs
;tmptab contains 16 palette regs to read
	les di,paltab	;Where to store 48 palette values to be derived
	mov cx,16
	xor si,si
	mov dx,READ_PIXEL ;Read pixel addr reg
GpTpp:	mov al,tmptab[si] ;Get palette reg to read
	inc si
	out dx,al	;read pixel addr reg
	inc dx		;Point at Color value reg
	inc dx
	in al,dx	;get red value (0-63)
	shl al,1	;Convert 0-63 value to 0-252
	shl al,1
	stosb		;save it in paltab
	in al,dx	;get green value
	shl al,1	;Convert 0-63 value to 0-252
	shl al,1
	stosb
	in al,dx	;get blue value
	shl al,1	;Convert 0-63 value to 0-252
	shl al,1
	stosb
	dec dx		;Point at Color value reg
	dec dx
	loop GpTpp	;Save 3*16 regs
	mov ax,48
	jmp short GVPBy
;If no MCGA/VGA present, can't read palette, return 0
NoVGA:	xor ax,ax
	jmp short GVPBy
_getvgapalette  endp

;Set VGA palette settings from table paltab (need 768 bytes).
; Usage: void setvgapalette(UCHAR *paltab); Table values should be 0-255.
paltab  equ dword ptr @ab[bp]		;Where to get palette values
	public _setvgapalette
_setvgapalette  proc  far
	push bp        
	mov bp,sp     
	push si        
	mov dx,WRITE_PIXEL	;write pixel addr reg
	push ds        
	lds si,paltab	;Where to get pal values
	xor ax,ax	;AL = first palette reg to read = 0
	out dx,al	;write pixel addr reg
	inc dx		;Point to Color value reg
	mov cx,256*3	;Write 256*3 regs
ReTop:	lodsb		;Get a value from the table
	shr al,1	;Convert 0-255 value to 0-63
	shr al,1
	out dx,al	;Set a red, green, or blue palette reg
	loop ReTop
	pop ds
	pop si
	pop bp
	ret
_setvgapalette  endp

;Set EGA/VGA palette settings from table paltab (need 48 bytes).
; Usage: void setegapalette(UCHAR *paltab); Table values should be 0-255.
; Use stack space as an array to hold 17 byte table
paltab  equ dword ptr @ab[bp]	;Where to get palette values
tmptab  equ  byte ptr [bp-20]	;Place 17 byte palette table here
	public _setegapalette
_setegapalette  proc  far
	push bp        
	mov bp,sp     
	sub sp,20
	push si
	push di
	mov ax,ds
	mov es,ax	;ES = DS
	push ds        
	lds si,paltab	;Source of 48 palette values
;Determine the video mode 
	mov ah,0fh	;Get current video mode
	int 10h
	cmp al,010h	;Jump if EGA 640x350x16 mode
	je SEega
;Mode 12h (640x480x16), etc. -- interpret 48 byte table at DS:SI
; as VGA data. This is VgaPcx2Pal().
	mov bx,offset VgaPalRegs ;ES:BX -> Palette regs to reprogram
	mov dx,WRITE_PIXEL	 ;Write pixel addr reg -- 3c8h
	mov cx,16		 ;Write 16 VGA palette regs
;Value is 0 -> 0ffh and may have 16 discrete values.
PVtop:	mov al,es:[bx]	;Pal reg to write
	out dx,al	;write pixel addr reg
	inc dx		;3c9h = color value reg
	lodsb		;AL= value to write
	shr al,1	;Convert 0-255 value to 0-63
	shr al,1
	out dx,al	;write red value
	lodsb
	shr al,1	;Convert 0-255 value to 0-63
	shr al,1
	out dx,al	;write green value
	lodsb
	shr al,1	;Convert 0-255 value to 0-63
	shr al,1
	out dx,al	;write blue value
	inc bx		;Advance reg pointer
	dec dx		;DX = 3c8h = pixel addr reg
	loop PVtop
SEbye:	pop ds
	pop di
	pop si
	mov sp,bp
	pop bp
	ret
;Mode 10h (640x350x16) -- interpret 48 byte table at DS:SI
; as EGA data. This is EgaPcx2Pal().
SEega:	xor ah,ah
	xor di,di		;DI is index into table array
SEtop:	call get_pal_byte	;Get RED byte of palette data from DS:SI
	shl al,1		;For RED, <<2
	shl al,1
	mov dh,al		;Save result
	call get_pal_byte	;Get GRN byte of palette data from DS:SI
	shl al,1		;For GRN, <<1
	or dh,al		;Update intermediate value
	call get_pal_byte	;Get BLU byte of palette data from DS:SI
	or al,dh		;AL = result
	mov tmptab[di],al	;Store in table (need space for 17 bytes)
	inc di			;Bump pointer
	cmp di,16		;Write 16 EGA palette regs
	jb SEtop
	mov byte ptr tmptab[di],0 ;Put 0 in position 17 for overscan reg
;Point ES:DI (SS:tmptab[0]) at the table
	lea dx,tmptab		;DX -> start of 17 byte table
	mov ax,ss
	mov es,ax
	mov ax,1002h		;ES:DX->palette table to use
	int 10h
	jmp short SEbye
_setegapalette  endp

;Get byte of palette data from DS:SI, reduce it to 0-3, and
; return 0, 8, 1, or 9 in AL. Uses AL, CL, BX. Advances SI.
get_pal_byte  proc  near
	lodsb		 ;AL = value to convert
	mov cl,6	 ;Need CL = 6 for division by 64
	shr al,cl	 ;Convert value (0-0ffh) to 0-3 by dividing by 64
	mov bx,offset EgaConTab ;ES:BX -> 0, 8, 1, or 9
	add bx,ax
	mov al,es:[bx]	 ;Get 0, 8, 1, or 9
	ret
get_pal_byte  endp

;void EgaPcx2Pal(UCHAR *paltab) {
;   static UCHAR EgaConTab[] = {0,8,1,9}; /* Used to convert EGA palette */
;   UCHAR tmpbuf[18];
;   int colval, j, k=0;
;   /* Convert value (0-0ffh) to 0-3 by dividing by 64, then use EgaConTab[]
;      to convert value to 0, 8, 1, or 9, then shift result << 2 for red,
;      << 1 for green, << 0 for blue. */
;   for(j=0; j<16; j++) {
;      colval  = (EgaConTab[paltab[k++] >> 6]) << 2; /* For RED, << 2 */
;      colval |= (EgaConTab[paltab[k++] >> 6]) << 1; /* For GRN, << 1 */
;      colval |=  EgaConTab[paltab[k++] >> 6];	    /* For BLU, << 0 */
;      tmpbuf[j] = colval; /* Store in table (need space for 17 bytes) */
;      }
;   tmpbuf[j] = 0; /* Put 0 in position 17 for overscan reg */
;   /* Use BIOS function to set the palette */
;   inregs.x.ax = 0x1002; /* ES:DX -> 17 byte palette table to use */
;   inregs.x.dx = FP_OFF(tmpbuf);
;   segregs.es = FP_SEG(tmpbuf);
;   int86x(0x10, &inregs, &outregs, &segregs);
;}

;void VgaPcx2Pal(UCHAR *paltab) {
;  static UCHAR VgaPalRegs[] = { /* Palette regs to reprogram */
;     0,1,2,3,4,5,0x14,7,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f};
;  int j, k=0, port=0x3c9;	/* Color value reg */
;/* PCX value is 0 -> 0ffh and may have 16 discrete values. Convert to 0->0x3f */
;  for(j=0; j<16; j++) {
;     outp(port-1, VgaPalRegs[j]);	/* Write pixel address reg */
;/* Write red value to the color value reg for the selected palette reg */
;     outp(port, paltab[k++] >> 2);
;     outp(port, paltab[k++] >> 2);	/* Write green */
;     outp(port, paltab[k++] >> 2);	/* Write blue */
;     }
;}
	END
+ARCHIVE+ egahisto.asm 17641  1/02/1991 12:27:46
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	EGAhisto_
;	CGAhisto_
;	HGChisto_
;	writHGC_
;	writEGA_
;	set_ega_window_
;	EGADispBar
;	HGCDispChar
;	EGADispChar
;	CalcScrAddr
;	CalcHGCAddr
;	set_writEGA_regs
;	reset_writEGA_regs
;	LoadRomFont

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

EGASEG	equ  0a000h
CGASEG	equ  0b800h
MDASEG	equ  0b000h
; EGA register equates.
GDC_INDEX	equ	3ceh	;GDC index register
GDC_SET_RESET	equ	0	;GC set/reset register
GDC_ENABLE_SET_RESET equ	1	;GC enable set/reset register
GDC_ROTATE	equ	3	;GDC data rotate/logical function reg index
GDC_MODE	equ	5	;GDC Mode reg
GDC_BIT_MASK	equ	8	;GDC bit mask register index

TS_INDEX	equ	3c4h	;TS index register
TS_MAP_MASK	equ	2	;TS map mask register

	.DATA
FontPointer DD  0	;addr of 8x14 ROM font
ScreenWidth DW	0	;save screen width in bytes here
FntLoaded   DB  0	;1 if font already loaded
	.CODE
;BH = color to use
set_writEGA_regs  proc  near
	mov dx,TS_INDEX
	mov al,TS_MAP_MASK
	out dx,al
	inc dx
	mov al,0fh	;map mask setting enables planes 0-3
	out dx,al
	mov dx,GDC_INDEX
	mov al,GDC_ENABLE_SET_RESET
	out dx,al
	inc dx
	mov al,0fh	;cpu data to all planes will be written
	out dx,al
	dec dx
	mov al,GDC_SET_RESET
	out dx,al
	inc dx
	mov al,bh	;BH = color to use
	out dx,al
	ret
set_writEGA_regs  endp

reset_writEGA_regs  proc  near
	mov dx,TS_INDEX
	mov al,TS_MAP_MASK
	out dx,al
	inc dx
	mov al,0fh	;map mask setting enables planes 0-3
	out dx,al
	mov dx,GDC_INDEX
	mov al,GDC_BIT_MASK
	out dx,al
	inc dx
	mov al,0ffh
	out dx,al
	dec dx
	mov al,GDC_ENABLE_SET_RESET
	out dx,al
	inc dx
	xor al,al
	out dx,al
	ret
reset_writEGA_regs  endp

EGA_BAR_HEIGHT	EQU   2 		;do 127 bars of ht 2
EGA_BAR_MAX	EQU 127		;Max value for ctr (must be<=127)
;EGA_BAR_DEC	EQU   1
EGA_HISTO_YBEG	EQU  20		;Start histo this many pixels from top
HISTO_XBEG	EQU  34		;Start histo this many pixels from left edge

;EGAhisto(char *tab,int bar_col,int 0);
;Display histo on EGA/VGA screen
hist_tab  equ dword ptr @ab[bp]
bar_col   equ  byte ptr @ab[bp+4]
vga_mode  equ  byte ptr @ab[bp+6]
eg_taboff equ  word ptr [bp-2]
eg_hrow   equ  word ptr [bp-4]
	public _EGAhisto_
_EGAhisto_  proc  far
	push bp
	mov  bp,sp
	sub sp,8
	push di
	push si
	cld
	mov dx,EGASEG
	mov es,dx
	mov bh,bar_col			;BH = color to use
	call set_writEGA_regs
	push ds
	lds si,hist_tab
	mov eg_taboff,si		;Store offset addr of table here
	mov eg_hrow,EGA_HISTO_YBEG	;Start histo here
	mov dl,EGA_BAR_MAX	  	;for(ln=127;ln>=0;ln-=2)
EGAHOutter:
	mov si,eg_taboff		;Load offset addr of table
	xor bx,bx	;for(i=0;i<=255;i++), BX = bin number
EGAHInner:
	lodsb
	cmp al,dl
	jbe EGAHNoBar	;if(bins[i]>ln)  disp_bar(i,row);
	call EGADispBar	;fputc(ch,stdout);
EGAHNoBar:	
	inc bx
	cmp bx,255+1
	jb EGAHInner
	add word ptr eg_hrow,EGA_BAR_HEIGHT
	dec dl
	jge EGAHOutter
	pop ds
	call reset_writEGA_regs		; Reset the registers.
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_EGAhisto_  endp

;EGADispBar - write bar to screen memory
; Starting addr to write = 80*HiRow + bin/4
; Display a bar at bin/4,HiRow.  Char = BarChar0-3 = bin&3.
; Enter with BX = bin no., HiRow set

;  AL = char, BX = row, CX = col of where to draw character
;
EGADispBar  proc  near
	push bx
	push dx
	call CalcScrAddr	; Calculate screen addr that character starts in = 80*HiRow + bin/4
	push ax			;save byte to write
	mov dx,GDC_INDEX
 	mov al,GDC_BIT_MASK
	out dx,al		;access Bit Mask reg
	inc dx			;point to data reg
	pop ax
	mov cx,EGA_BAR_HEIGHT
EGAHiDrawLoop:		; Write 8 bytes to display memory.
	out dx,al	; Set the byte as the bit mask
	mov ah,es:[di]		;load latches
	stosb			;write character byte
	add di,79		; Point to next line of display memory.
	loop EGAHiDrawLoop
	pop dx
	pop bx
	ret
EGADispBar  endp

;FONT_CHARACTER_SIZE	equ	8	;# bytes in each font char
FONT_CHARACTER_SIZE	equ	14	;# bytes in each font char
TEXT_WIDTH		equ	8	;width of a character in pixels

;Write string on the screen at ROW,COL.
;Usage:  writEGA(row*14,col*8,txtcol,string);
w_row	equ  word ptr @ab[bp]	;Row
w_col	equ  word ptr @ab[bp+2]	;Col
w_attr	equ  byte ptr @ab[bp+4]	;Color to use
w_str	equ dword ptr @ab[bp+6]	;Addr of string to display
str_seg equ  word ptr [bp-2]	;Seg of string to display
	public _writEGA_
_writEGA_  proc  far
 	push bp
	mov bp,sp
	sub sp,4
	push di
	push si
	mov bh,w_attr	;BH = color to use
	call set_writEGA_regs
	les si,w_str	 		;addr of string to process = es:si
	mov str_seg,es
	mov dx,EGASEG
	mov es,dx			;point to display memory
;Col = col*8 and Row = row*14
	mov cx,w_col			;get column (CX = col*8)
	shl cx,1
	shl cx,1
	shl cx,1
	mov ax,w_row			;get row
	mov bl,14
	mul bl
	mov bx,ax			;BX = row * 14
	xor ah,ah
StringOutLoop:
	push ds
	mov ds,str_seg
	lodsb				;get char to display from string
	pop ds
	or al,al
	jz StringOutDone
	call EGADispChar
	add cx,TEXT_WIDTH		;move to next column
	jmp short StringOutLoop
StringOutDone:
;	mov _Hcol,cx			;save exit column (0-639)
	call reset_writEGA_regs		; Reset the registers.
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_writEGA_  endp	 

; Display the char in AL at CX,BX.  Font used is stored at font_seg:font_off
; For modes 0Dh, 0Eh, 0Fh, 010h, 012h
;  AL = char, BX = row, CX = col of where to draw character
EGADispChar  proc  near
	push bx			;Save Row
	push cx			;Save Col
	push si			;Save start addr
;Load Font addr into FontPointer, if not previously done
	cmp FntLoaded,1		;1 if font already loaded
	jne LoadFnt
; Calculate screen address of byte character starts in.
FntLd:	mov di,ScreenWidth 	; Set DI = screen width in bytes
	xchg ax,bx		;save char and put row num in AX
	mul di			;calculate offset of start of row
	mov dx,di		;save ScreenWidth
	mov di,cx		;column number = X/8
	mov cl,3
	shr di,cl
	add di,ax	;final screen byte addr in DI
;
; Calculate font address of character
	push ds
	lds si,FontPointer	; Point DS:SI at font data
	mov ax,bx	;AX = char
	mov cx,FONT_CHARACTER_SIZE	;cx is ctr for bytes/char
	mul cl		;loc = char * 14
	add si,ax	;offset in font of character
;
; Draw the character
	mov bx,dx	;put screen width in BX
	dec bx		;amount to move down = screen width - 1
;	mov cx,FONT_CHARACTER_SIZE	;cx is ctr for bytes/char
	mov dx,GDC_INDEX
 	mov al,GDC_BIT_MASK
	out dx,al		;access Bit Mask reg
	inc dx			;point to data reg
; Set the Font data as the bit mask
ChrLp:	lodsb			;get font data
	out dx,al		;font data goes in as Bit Mask
; Write character to display memory.
	mov ah,es:[di]		;load latches
	stosb			;write character byte
; Point to next line of character in display memory.
	add di,bx		;normally, bx=79
	loop ChrLp
	pop ds
	pop si
	pop cx
	pop bx
	ret
LoadFnt: call LoadRomFont	;Load FontPointer with addr of 8x8 font
	jmp short FntLd
EGADispChar  endp

; Calculate screen addr that character starts in = 80*HiRow + bin/4
; BX has bin number, ret with byte to write in AL.  Uses DI,BX,CX,AX,DX
CalcScrAddr  proc  near		
	mov ax,eg_hrow		;ax gets row
	mov di,80
	mul di		;AX=row*80.  Uses DX !!!
	mov di,ax
	add bx,HISTO_XBEG ;Add 2 to bin no. to align with '.'
	mov cx,bx	;save bin num in CX
	shr bx,1
	shr bx,1	;col=bin/4
	add di,bx	;final screen byte addr in DI
; Generate byte to use, 0xC0, 0x30, 0x0C, or 0x03
	and cx,3
	shl cx,1
	mov al,0c0h
	shr al,cl	;AL = byte to write = 0xc0>>((bin num & 3)*2)
	ret
CalcScrAddr  endp

CGA_BAR_MAX     EQU 127		;Max value for ctr (must be<=127)
CGA_BAR_DEC	EQU   2		;2
CGA_HISTO_YBEG  EQU  15		;Start histo this many pixels from top

;CGAhisto(char *tab,int bar_col,int 0);
;Ignores bar_col and vmode
;Display histo on CGA screen
hist_tab  equ dword ptr @ab[bp]
bar_col   equ  byte ptr @ab[bp+4]
vga_mode  equ  byte ptr @ab[bp+6]
eg_taboff equ  word ptr [bp-2]
eg_hrow   equ  word ptr [bp-4]
	public _CGAhisto_
_CGAhisto_  proc  far
	push bp
	mov  bp,sp
	sub sp,10
	push di
	push si
	cld
	mov dx,CGASEG
	mov es,dx
	push ds
	lds si,hist_tab
	mov eg_taboff,si		;Store offset addr of table here
	mov eg_hrow,CGA_HISTO_YBEG	;Start histo here
	mov dl,CGA_BAR_MAX	  	;for(ln=127;ln>=0;ln-=2)
CGAHOutter:
	mov si,eg_taboff		;Load offset addr of table
	xor bx,bx	;for(i=0;i<=255;i++), BX = bin number
CGAHInner:
	push bx		;Starting addr to write = 80*HiRow + bin. Display a bar at bin,HiRow.
	push dx
	call CalcScrAddr	;BX=bin no., on ret AL=byte 2 wrt.  Uses DI,BX,CX,AX
	pop dx
;First, erase the bar
	mov ah,al		;AL = byte to write (0c0,30,0c,or 03)
	not al			;Not mask
	and es:[di],al	; Zero the byte value
	and es:[di+2000h],al	; Zero the byte value
;Now draw it, if necessary
	lodsb
	cmp al,dl
	jbe NoBar	;if(bins[i]>ln)  disp_bar(i,row);
	or es:[di],ah	; Write char to display memory.
	or es:[di+2000h],ah	; Write char to display memory.
NoBar:	pop bx
	inc bx
	cmp bx,255+1
	jb CGAHInner
	inc eg_hrow
	sub dl,CGA_BAR_DEC
	jge CGAHOutter
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_CGAhisto_  endp

HGC_BAR_MAX     EQU 127		;Max value for ctr (must be<=127)
HGC_BAR_DEC	EQU   2		;2
HGC_HISTO_YBEG  EQU   4		;Start histo this many pixels from top

;HGChisto(char *tab,int bar_col,int 0);
;Ignores bar_col and vmode
;Display histo on HGC adapter
hist_tab  equ dword ptr @ab[bp]
bar_col   equ  byte ptr @ab[bp+4]
vga_mode  equ  byte ptr @ab[bp+6]
eg_taboff equ  word ptr [bp-2]
eg_hrow   equ  word ptr [bp-4]
	public _HGChisto_
_HGChisto_  proc  far
	push bp
	mov  bp,sp
	sub sp,10
	push di
	push si
	cld
	mov dx,MDASEG
	mov es,dx
	push ds
	lds si,hist_tab
	mov eg_taboff,si		;Store offset addr of table here
	mov eg_hrow,HGC_HISTO_YBEG	;Start histo here
	mov dl,HGC_BAR_MAX	  	;for(ln=127;ln>=0;ln-=2)
HGCOutter:
	mov si,eg_taboff		;Load offset addr of table
	xor bx,bx	;for(i=0;i<=255;i++), BX = bin number
HGCInner:
;First erase the bar
	push bx		;Starting addr to write = 80*HiRow + bin. Display a bar at bin,HiRow.
	call CalcHGCAddr ;BX=bin no., on ret AL=byte 2 wrt.  Uses DI,BX,CX,AX
	mov ah,al	;AL = byte to write (0c0,30,0c,or 03)
	not al		;Not mask
	and es:[di],al	; Zero the byte values
	and es:[di+2000h],al
	and es:[di+4000h],al
	and es:[di+6000h],al
;Now draw it, if necessary
	lodsb
	cmp al,dl
	jbe HNoBar	;if(bins[i]>ln)  disp_bar(i,row);
	or es:[di],ah	; Write char to display memory.
	or es:[di+2000h],ah	; Write char to display memory.
	or es:[di+4000h],ah
	or es:[di+6000h],ah
HNoBar:	pop bx
	inc bx
	cmp bx,255+1
	jb HGCInner
	inc eg_hrow
	sub dl,HGC_BAR_DEC
	jge HGCOutter
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_HGChisto_  endp

; Calculate screen addr that character starts in = 90*HiRow + bin/4
; BX has bin number, ret with byte to write in AL.  Uses DI,BX,CX,AX,DX
CalcHGCAddr  proc  near
	push dx
	mov ax,eg_hrow		;ax gets row
	mov di,90
	mul di		;AX=row*80
	mov di,ax
	add bx,HISTO_XBEG ;Add 2 to bin no. to align with '.'
	mov cx,bx	;save bin num in CX
	shr bx,1
	shr bx,1	;col=bin/4
	add di,bx	;final screen byte addr in DI
; Generate byte to use, 0xC0, 0x30, 0x0C, or 0x03
	and cx,3
	shl cx,1
	mov al,0c0h
	shr al,cl	;AL = byte to write = 0xc0>>((bin num & 3)*2)
	pop dx
	ret
CalcHGCAddr  endp

;Calculate the buffer addr of a pixel in 720x348 mode.  Enter with Y coord
; in AX and X coord in BX.  Return BX=byte addr in buffer,
; CL=no. of bits to shift left, AH=unshifted bit mask
;Uses AX,BX,CX
;CalcHGCAddr  proc  near
;
;	mov cl,bl	;cl = low order byte of X
;	shr ax,1	;ax = y/2
;	rcr bx,1	;bx = 8000h*(y&1) + x/2
;	shr ax,1	;ax = y/4
;	rcr bx,1	;bx = 4000h*(y&3) + x/4
;	shr bx,1	;bx = 2000h*(y&3) + x/8
;	mov ah,90
;	mul ah		;AX = 90*(y/4)
;	add bx,ax	;BX = 2000*(y&3) + x/8 +90*(y/4)
;;	add bx,OriginOffset	;BX = byte addr in video buffer
;	and cl,7	;if bit mask = 80h, CL=bitno = no. of bits to shift rt
;	xor cl,7	;CL = no. of bits to shift left
;	mov ah,1	;AH = unshifted bit mask
;	clc
;	ret
;RangeErr:
;	stc		;out of range, set CY
;	ret
;CalcHGCAddr  endp

;WritHGC -- display text string in graphics mono mode (720x348) at ROW,COL
; Usage:  writHGC(row,col,color,string);
w_row	    equ  word ptr @ab[bp]	;row, col in pixels. Should be
w_col	    equ  word ptr @ab[bp+2]	; byte aligned (row=n*8)
w_colr	    equ  word ptr @ab[bp+4]	;color not used here
w_string    equ dword ptr @ab[bp+6]
string_seg  equ  word ptr [bp-2]

	public _writHGC_
_writHGC_  proc  far
 	push bp
	mov bp,sp
	sub sp,4
	push di
	push si
;Save string seg and set SI = string offset	
	les si,w_string  	;addr of string to process = es:si
	mov string_seg,es
;Set video seg
	mov cx,MDASEG
	mov es,cx			;point to display memory
;Col = col*8 and Row = row*14
	mov cx,w_col			;get column (CX = col*8)
	shl cx,1
	shl cx,1
	shl cx,1
	mov ax,w_row			;get row
	mov bl,14
	mul bl
	mov bx,ax			;BX = row * 14
	xor ah,ah
StrOut:	push ds
	mov ds,string_seg
	lodsb				;get char to display from string
	pop ds
	or al,al
	jz StrOutBye
	push bx		 	;Y
	push cx		 	;X
	push ax		 	;ch
	call HGCDispChar 	; Usage: HGCDispChar(ch,X,Y,fattr,bkattr);
	pop ax		 	;ch
	pop cx		 	;X
	pop bx		 	;Y
	add cx,8	 	;TEXT_WIDTH, move to next column
	jmp short StrOut
StrOutBye:
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_writHGC_  endp

;DispCharHGC -- display a char in graphics mode using ROM char table
; Usage: HGCDispChar(ch,X,Y,fattr,bkattr);
; ** Version uses character table at f000:fa76 **
g_chr	equ  word ptr [bp+4]	;ASCII char
g_xx	equ  word ptr [bp+4+2]	;X,Y starting position (not byte aligned)
g_yy	equ  word ptr [bp+4+4]

g_mask	equ  word ptr [bp-2]
toggle  equ  word ptr [bp-4]

HGCDispChar  proc  near
	push bp
	mov bp,sp
	sub sp,6
	push si
	push di
	push ds
;Calculate addr to put char in 720x348 mode (BX gets addr)
	mov bx,g_xx	;BX = X coord
	mov cl,bl	;cl = low order byte of X
	mov ax,g_yy	;AX = y	coord
	shr ax,1	;ax = y/2
	rcr bx,1	;bx = 8000h*(y&1) + x/2
	shr ax,1	;ax = y/4
	rcr bx,1	;bx = 4000h*(y&3) + x/4
	shr bx,1	;bx = 2000h*(y&3) + x/8
	mov ah,90
	mul ah		;AX = 90*(y/4)
	add bx,ax	;BX = 2000*(y&3) + x/8 +90*(y/4)
;Set up char definition table addressing
	mov ax,g_chr	;char code
	cmp al,80h
	jae ExtChr	;Jump if not a normal ASCII char
	mov si,0f000h	;Normal ASCII char, use CGA char definition table
	mov ds,si
	mov si,0fa6eh	;DS:SI -> start of char table
	jmp short LdFnt
ExtChr:	push bx		;Extended ASCII char (Note: Graphics chars must
	xor bx,bx	; be loaded and int 1fh updated for this to work)
	mov ds,bx	;DS = seg of BIOS video disp data area
	mov bx,1fh*4	;Char>=80h, load int 1fh vect
	sub al,80h	;Adjust char for table
	lds si,[bx]	;DS:SI -> start of char table
	pop bx		;restore screen addr
LdFnt:	shl ax,1
	shl ax,1
	shl ax,1	;Offset within table = 8*char_code
	add si,ax	;SI = addr of char def
;Erase and set pixels in the video buffer
	mov cx,8	;8 bytes of pixel data
DPMask:	xor ax,ax
	and es:[bx],al	;zero characters' pixels
	lodsb		;AL = bit pattern for next pixel row
;Assumes char is 8 bits wide and byte aligned
	or es:[bx],al	;write pixels
	add bx,2000h	;inc to next portion of interleave
	jns NxLeaf
	add bx,90-8000h	;inc to first portion of interleave
NxLeaf:	loop DPMask
DPexit:	pop ds
	pop di
	pop si
	mov sp,bp
	pop bp
	ret
HGCDispChar  endp

;Set the color of a window.
; Usage: void set_ega_window(int wxs, int wys, int wxe, int wye, int color);
	public _set_ega_window_
win_xs	  equ  @ab[bp]		;in cols (0-80)
win_ys	  equ  @ab[bp+2]	;in pixels (0-350,480)
win_xe	  equ  @ab[bp+4]	;in cols (0-80)
win_ye	  equ  @ab[bp+6]	;in pixels (0-350,480)
win_color equ  @ab[bp+8]	;color

_set_ega_window_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	mov ax,EGASEG
	mov es,ax
	mov bh,win_color	;window attribute
	call set_writEGA_regs
;Set bit mask reg
	dec dx			;DX = GDC_INDEX reg
 	mov al,GDC_BIT_MASK
	out dx,al		;access Bit Mask reg
	inc dx			;point to data reg
	mov al,0ffh		;byte to write
	out dx,al		;Set the byte as the bit mask
;Calc starting addr -> DI
	mov ax,win_ys		;ax gets row
	mov di,80
	mul di			;AX=row*80.  Uses DX !!!
	add ax,win_xs
	mov di,ax    		;DI = row*80 + col
;Calc cols to write -> DL
	mov ax,win_xe		;ax gets last col
	sub ax,win_xs
	inc al
	mov dl,al		;DL = cols to write
;Calc rows to write -> BX
	mov ax,win_ye		;ax gets last row
	sub ax,win_ys
	inc ax
	mov bx,ax		;BX = rows to write
EWTop:	push di			;Save starting x,y
	xor ch,ch
	mov cl,dl		;CX = cols to write
	rep movsb		;-> ES:DI
	pop di
	add di,80		;goto next row
	dec bx
	jg EWTop
	call reset_writEGA_regs		; Reset the registers.
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_set_ega_window_  endp

LoadRomFont  proc  near	;Load pointer to 8x8 font into FontPointer
	push bp
	push ax
	push bx
	push cx
	push dx
	push es
;But first, get ScreenWidth for BIOS data area
	xor ax,ax
	mov es,ax
	mov bx,044ah			;get screen width in bytes
	mov ax,es:[bx]
	mov ScreenWidth,ax		;save screen width in bytes
;EGA BIOS character generator function,
	mov ax,1130h	; return info subfunction
;	mov bh,3	;get 8x8 font pointer
	mov bh,2	;get 8x14 font pointer
	int 10h		;ES:BP points to font addr
	mov word ptr FontPointer,bp	;save pointer
	mov word ptr FontPointer[2],es
	mov FntLoaded,1	;Set font loaded flag
	pop es
	pop dx
	pop cx
	pop bx
	pop ax
	pop bp
	ret
LoadRomFont  endp

	END
+ARCHIVE+ emsasm.asm   24744  3/04/1991 13:10:20
; Victor Library, Copyright (c) 1990-1991, ALL RIGHTS RESERVED
;	Catenary Systems
;	470 Belleview
;	St Louis, MO 63119
;	(314) 962-7833
; Contents:
;	emallocate	Allocate pages of expanded memory
;	emavailable	Return available pages of EM
;	emdetect	Check that the EMM is installed and functional
;	emerror		Report latest EMM error code
;	emfree		Free expanded memory owned by handle
;	emgetframeseg	Get page frame segment
;	emgetrow	Copy a row in expanded memory to conventional memory
;	emmap		Map a logical page of EM to a physical page
;	empagesowned	Return number of pages owned by handle
;	emputrow	Copy a row in conventional memory to expanded memory
;	emgetbyte_	Return a byte value from EM buffer at (stx,sty)
;	emsetbyte_	Write a byte value to EM buffer at (stx,sty)
;	emversion	Get EMM version number

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

	extrn _memcpy_:far

;Error codes:
NO_ERROR   EQU	  0	;No error
NO_EMM	   EQU	-21	;Expanded memory manager not found
EMM_ERR	   EQU	-22	;Expanded memory manager error
;Native 3.2 EMM error codes:
;80h Internal error in EMM software
;81h Malfunction in EMM hardware
;82h Memory manager busy
;83h Invalid handle
;84h Function not defined
;85h Handles exhausted
;86h Error in save/restore of mapping context
;87h Allocation request for more pages than physically available
;88h Allocation request for more pages than currently available
;89h Zero pages cannot be allocated
;8ah Requested logical page not owned by handle
;8bh Illegal physical page number specified
;8ch Page mapping save area is full
;8dh Mapping context save failed, save area already contains context
;8eh Mapping context restore failed, save area does not contain context
;8fh Subfunction parameter not defined

	.DATA
EMMname	     db	'EMMXXXX0',0	;Logical device name for EMS driver
emm_version  db  0		;EMS version number (30 -> 40)

	.CODE
emm_errcode  db  0 	;EMM native error codes are stored here
emm_flag     db  0	;Nonzero if EMM is installed and hardware's functional
;Report latest EMM error code. Usage: errcode = emerror_(void);
	public _emerror
_emerror  proc  far
	push bp
	mov al,cs:emm_errcode
	xor ah,ah
	pop bp
	ret
_emerror  endp

;Allocate pages of expanded memory. Return NO_ERROR, NO_EMM, or EMM_ERR.
; Additional error information is available from emerror().
; Usage: int emallocate(int *handle, int pages);
handle_addr  equ dword ptr @ab[bp]   	;EMM handle value
emm_pages    equ  word ptr @ab[bp+4]	;Pages to allocate
	public _emallocate
_emallocate  proc  far
	push bp
	mov bp,sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz EAMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne EAXit		;EMM not installed
EAMan:	mov ah,43h
	mov bx,emm_pages
	int 67h
	or ah,ah		;AH = 0 if success
	jnz EAErr		;EMM error
	les bx,handle_addr
	mov es:[bx],dx		;DX = EMM handle value
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in emm_errcode
EAXit:	mov cs:emm_errcode,bl	;Store EMM error code
	pop bp
	ret
EAErr:	mov bl,ah		;Store EMM error code in BL
	mov ax,EMM_ERR		;EMM error
	jmp short EAXit
_emallocate  endp

;Return the number of 16Kb pages of expanded memory available (0 - 2048),
; NO_EMM or EMM_ERR. Additional error information is available from emerror()
; if EMM_ERR is returned. Usage: pages = emavailable(void);
	public _emavailable
_emavailable  proc  far
	push bp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz PAMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne PAXit		;EMM not installed
;Get number of available 16Kb pages
PAMan:	mov ah,42h
	int 67h
	or ah,ah		;AH = 0 if success
	jnz PAErr		;Jump if error
	mov ax,bx		;Return available EMM pages (0 - 2048)
	xor bl,bl		;Store 0 in emm_errcode
PAXit:	mov cs:emm_errcode,bl	;Store EMM error code
	pop bp			;Return pages value (0 -> 2048)
	ret
PAErr:	mov bl,ah		;Store EMM error code
	mov ax,EMM_ERR		;EMM error
	jmp short PAXit
_emavailable  endp

;Check that the EMM is installed, that EMS is version 3.2 or greater, and 
; the hardware is functional. Return NO_ERROR, NO_EMM or EMM_ERR in AX, 
; native EMM errcode in BL. Additional error information is available 
; from emerror() if EMM_ERR is returned. Uses AX, BX, CX, ES.
; Usage: int emdetect(void);
	public _emdetect
_emdetect  proc  far
	push bp
	push di
	push si
	push ds
	cld
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz EMDxt1		;No need to continue
;Make sure EMMname is accessed correctly
	mov ax,@Data
	mov ds,ax
;Use EMS int vector to check for EMM driver
	mov ax,3567h		;Get addr of EMS ISR -> ES:BX
	int 21h			;ES:BX -> ISR
	mov di,10		;ES:DI = addr of device name field
	mov si,offset EMMname	;DS:SI = expected EMM name
	mov cx,8		;Length of device name field
	repz cmpsb		;Compare name field to EMMname
	jnz NoEMM		;EMM not present, exit
;Test EMM status
	mov ah,40h		
	int 67h
	or ah,ah		;AH = 0 if success
	jnz EDErr		;Jump if not functional
;Check that EMS version >= 3.2 and save value
	mov ah,46h
	int 67h			;AL gets version number (should be 30,
	or ah,ah		; 32, or 40 hex)
	jnz EDErr		;Jump if error
	mov emm_version,al	;Save EMS version
	cmp al,032h		;Value should be >= 32
	jb NoEMM		;Return NO_EMM if version is < 3.2
	mov cs:emm_flag,1	;EMM present, set the flag
EMDxt1:	mov ax,NO_ERROR		;Success, return NO_ERROR
EMDxt2:	xor bl,bl		;Store 0 in emm_errcode
EMDxt3:	mov cs:emm_errcode,bl	;Store EMM error code
	pop ds
	pop si
	pop di
	pop bp
	ret
NoEMM:	mov ax,NO_EMM		;No EMM, set EMM errcode = 0
	jmp short EMDxt2
EDErr:	mov bl,ah		;Store EMM error code
	mov ax,EMM_ERR		;EMM error
	jmp short EMDxt3
_emdetect  endp

;Free expanded memory owned by handle. Return NO_ERROR, NO_EMM, or EMM_ERR.
; Additional error information is available from emerror().
; Usage: int emfree(int handle);
emm_handle equ	word ptr @ab[bp]	;EMM handle
trynum     equ  word ptr [bp-2]		;Counter
	public _emfree
_emfree  proc  far
	push bp
	mov bp,sp
	dec sp
	dec sp
	mov trynum,255		;Try releasing memory 255 times
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz EFrNxt		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne FreXit		;EMM not installed
EFrNxt:	mov ah,45h
	mov dx,emm_handle	;EMM handle
	int 67h
	or ah,ah		;AH = 0 if success
	jnz FreErr		;If EMM error, try again - up to 255 times
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in emm_errcode
FreXit:	mov cs:emm_errcode,bl	;Store EMM error code
	mov sp,bp
	pop bp
	ret
FreErr:	dec trynum		;Try releasing memory 255 times
	jnz EFrNxt		;If not zero, try again
	mov bl,ah		;Store EMM error code in BL
	mov ax,EMM_ERR		;EMM error
	jmp short FreXit
_emfree  endp

;Get page frame segment. Return NO_ERROR, NO_EMM, or EMM_ERR.
; Additional error information is available from emerror().
; Usage: int emgetframeseg(unsigned *frameseg);
frame_seg  equ dword ptr @ab[bp]	;Segment to be used as page frame
	public _emgetframeseg
_emgetframeseg  proc  far
	push bp
	mov bp,sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz GSMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne GSgXit		;EMM not installed
GSMan:	mov ah,41h
	int 67h
	or ah,ah		;AH = 0 if success
	jnz GetSg		;EMM error
	mov ax,bx		;AX = BX = page frame segment
	les bx,frame_seg
	mov es:[bx],ax
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in emm_errcode
GSgXit:	mov cs:emm_errcode,bl	;Store EMM error code
	pop bp
	ret
GetSg:	mov bl,ah		;Store EMM error code in BL
	mov ax,EMM_ERR		;EMM error
	jmp short GSgXit
_emgetframeseg  endp

;Map a logical page of expanded memory to a physical page. Return NO_ERROR
; NO_EMM, or EMM_ERR. Additional error info is available from emerror().
; Usage: int emmap(int handle, int phys_page, int log_page);
emm_handle  equ  word ptr @ab[bp]   	;EMM handle value
emm_ppage   equ  byte ptr @ab[bp+2]   	;Physical page (0-3)
emm_lpage   equ  word ptr @ab[bp+4]   	;Logical page (0-2047)
	public _emmap
_emmap  proc  far
	push bp
	mov bp,sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz MMMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne MMXit		;EMM not installed
MMMan:	mov ah,44h
	mov al,emm_ppage	;Physcial page we're using
	mov bx,emm_lpage	;Logical page to map
	mov dx,emm_handle
	int 67h
	or ah,ah		;AH = 0 if success
	jnz MMErr		;EMM error
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in emm_errcode
MMXit:	mov cs:emm_errcode,bl	;Store EMM error code
	pop bp			;Return pages value (0 -> 2048)
	ret
MMErr:	mov bl,ah	;Store EMM error code
	mov ax,EMM_ERR		;EMM error
	jmp short MMXit
_emmap  endp

;Return number of pages owned by handle. Return pages (0-2048), NO_EMM, 
; or EMM_ERR. Additional error information is available from emerror().
; Usage: int empagesowned(int handle);
emm_handle  equ  word ptr @ab[bp]   	;EMM handle value
	public _empagesowned
_empagesowned  proc  far
	push bp
	mov bp,sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz POMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne POXit		;EMM not installed
POMan:	mov ah,4ch
	mov dx,emm_handle
	int 67h
	or ah,ah		;AH = 0 if success
	jnz POErr		;EMM error
	mov ax,bx		;BX = 16Kb pages owned by handle
	xor bl,bl		;Store 0 in emm_errcode
POXit:	mov cs:emm_errcode,bl	;Store EMM error code
	pop bp			;Return pages value (0 -> 2048)
	ret
POErr:	mov bl,ah		;Store EMM error code
	mov ax,EMM_ERR		;EMM error
	jmp short POXit
_empagesowned  endp

;Get EMS version number. Valid version number should be 3.0, 3.2, or 4.0.
; Return the version number as a BCD number, NO_EMM, or EMM_ERR. Additional 
; error information is available from emerror().
; Usage: version = emversion(void);
	public _emversion
_emversion  proc  far
	push bp
	push ds
;Make sure emm_version is accessed correctly
	mov ax,@Data
	mov ds,ax
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz EVMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
;If rcode = EMM_ERR, just exit (AX and emm_errcode is already set up)
	cmp ax,EMM_ERR
	je EVbye
	cmp ax,NO_ERROR
	je EVMan
;Rcode = NO_EMM. If rcode = NO_EMM and emm_version = 0, exit
; If rcode = NO_EMM and emm_version != 0, return version in AX
EVnoem:	cmp emm_version,0
	je EVbye
;Rcode = NO_ERROR, put version in AX and exit (emm_errcode already set up)
EVMan:	mov al,emm_version
	xor ah,ah	;Valid version number, return it
	mov cs:emm_errcode,0	;Store 0 in EMM error code
EVbye:	pop ds
	pop bp			;Value should be >= 30
	ret
_emversion  endp

PAGE0    equ  0
PAGE1    equ  1
EMPAGE0  equ  0
EMPAGE1  equ  4000h

;Get the value of a byte in EM at (stx,yctr). Does not check that EM exists
; or that ehandle owns any EM. Rets the byte's value (0-255), NO_EMM, or
; EMM_ERR. Usage: int emgetbyte_(int ehandle, int stx, int yctr, int xwidth);
gehandle  equ  word ptr   @ab[bp]	;EM handle
gestx     equ  word ptr @ab[bp+2]
gesty     equ  word ptr @ab[bp+4]
gewidth   equ  word ptr @ab[bp+6]	;EM buffer width
framsg    equ  word ptr [bp-2]		;EM frame segment
	public _emgetbyte_
_emgetbyte_  proc  far
	push bp        
	mov bp,sp     
	dec sp
	dec sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz GXMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne GxXit		;EMM not installed
GXMan:	call emsetup	;Set ES:BX -> byte to read/write
	jc GxErr	;If error, CF is set
;Read the byte and return it in AX
	mov al,es:[bx]
	xor ah,ah
	xor bl,bl		;Store 0 in emm_errcode
GxXit:	mov cs:emm_errcode,bl	;Store EMM error code
	mov sp,bp
	pop bp
	ret
;Error occured in emsetup()
GxErr:	mov bl,ah		;Store EMM error code
	mov ax,EMM_ERR		;General EMM error
	jmp short GxXit
_emgetbyte_  endp

;Set the value of a byte in EM at (stx,sty). Does not check that EM exists
; or that ehandle owns any EM. Returns NO_ERROR, NO_EMM, or EMM_ERR.
; Usage: int emsetbyte_(int ehndle, int stx, int stx, int xwidth, int color);
gehandle  equ  word ptr   @ab[bp]	;EM handle
gestx     equ  word ptr @ab[bp+2]
gesty     equ  word ptr @ab[bp+4]
gewidth   equ  word ptr @ab[bp+6]	;EM buffer width
gecolor   equ  byte ptr @ab[bp+8]
framsg    equ  word ptr [bp-2]		;EM frame segment
	public _emsetbyte_
_emsetbyte_  proc  far
	push bp        
	mov bp,sp     
	dec sp
	dec sp
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz SPMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne SpXit		;EMM not installed
SPMan:	call emsetup	;Set ES:BX -> byte to read/write
	jc SpErr	;If error, CF is set
;Write the byte 
	mov al,gecolor
	mov es:[bx],al
	mov ax,NO_ERROR		;Else, return NO_ERROR and exit
	xor bl,bl		;Store 0 in emm_errcode
SpXit:	mov cs:emm_errcode,bl	;Store EMM error code
	mov sp,bp
	pop bp
	ret
;Error occured in emsetup()
SpErr:	mov bl,ah		;Store EMM error code
	mov ax,EMM_ERR		;General EMM error
	jmp short SpXit
_emsetbyte_  endp

;Get EM frame segment, calc saddr, map logical to physical page, and 
; return ES:BX -> byte to read/write. If error, return with CF set.
emsetup  proc  near
;Get EM frame segment value
	mov ah,41h
	int 67h
	or ah,ah		;AH = 0 if success
	jnz EStEr		;EMM error, exit
	mov framsg,bx		;BX = page frame segment
;Calculate saddr = stx + sty * (long)width;
	mov ax,gesty
	mul gewidth
	add ax,gestx
	adc dx,0		;DX:AX = saddr
;Calc offset = saddr % 0x4000; (byte to access)
	mov bx,ax
	and bx,3fffh		;BX = saddr % 0x4000
	push bx			;Save remainder
;Calc slpage = saddr / 0x4000; (EM page to access)
	mov cx,2		;DX:AX >> 14
ESRot:	shl ah,1
	rcl dx,1	
	loop ESRot
;Map logical to physical page: emmap(handle, PAGE0, slpage);
	mov bx,dx	;BX = DX = logical page to map (saddr / 0x4000)
	mov dx,gehandle	;DX gets handle
	mov ax,4400h	;AH = 44h, AL = physical page = PAGE0
	int 67h
	or ah,ah 	;AH = 0 if success
	pop bx		;BX = remainder (saddr % 0x4000)
	jnz EStEr	;EMM error
	mov es,framsg	;ES -> frame segment
;	add bx,EMPAGE0	;ES:BX -> byte to read/write (EMPAGE0 = 0)
	clc		;No error
	ret
EStEr:	stc		;CF set => EMM_ERR
	ret
emsetup  endp

;Copy 'cols' bytes from a row in expanded memory (EM) to conventional
; memory (CM). Buffer to receive the row must be large enough to hold
; 'cols' bytes. Does not check that EM exists or that handle owns any
; EM. Uses EM Page 0. Returns NO_ERROR, NO_EMM, or EMM_ERR.
; Usage:int emgetrow(void huge *des, int handle, int stx, int sty,
; int cols, int width);
desofs	    equ		  @ab[bp]	;Destination for data
desseg	    equ		  @ab[bp+2]
gr_handle   equ  word ptr @ab[bp+4]
gr_startx   equ  word ptr @ab[bp+6]	;EM buffer dimensions:
gr_starty   equ  word ptr @ab[bp+8]
gr_cols     equ  word ptr @ab[bp+10]
gr_width    equ  word ptr @ab[bp+12]

framsg      equ  word ptr [bp-2]	;EM frame segment
safebyts    equ  word ptr [bp-4]
	public _emgetrow
_emgetrow  proc  far
	push bp        
	mov bp,sp     
	sub sp,6
	push di
	cld
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz GRMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne GRxit		;EMM not installed
;Get EM frame segment value
GRMan:	push ss			;Push variable seg
	lea ax,framsg
	push ax			;Push variable ofs
	push cs			;Push CS so we can use a near call
	call near ptr _emgetframeseg
	add sp,4
	cmp ax,NO_ERROR
	jne GRxit		;Exit if error
;Calculate saddr = stx + sty * (long)width;
	mov ax,gr_starty
	mul gr_width
	add ax,gr_startx
	adc dx,0	;DX:AX = saddr
;Calc slpage = saddr / 0x4000; (EM page to access)
	push ax		;Save lsw of address
	shl ax,1	;Quotient = DX:AX >> 14
	rcl dx,1
	shl ax,1
	rcl dx,1	;Quotient is in DX
	mov di,dx	;DI = logical page to set
;Map logical to physical page: emmap(handle, PAGE0, slpage);
	push di		;Push logical page
	mov ax,PAGE0
	push ax		;Push physical page
	push gr_handle	;Push handle
	push cs		;Push CS so we can use a near call
	call near ptr _emmap
	add sp,6
	cmp ax,NO_ERROR
	pop dx		;DX = lsw of saddr
	jne GRxit	;Exit if error
	and dx,3fffh	;Calc remainder (saddr % 0x4000)
;Move as many bytes as possible without crossing a page boundary
	mov ax,4000h
	sub ax,dx		;AX = safebyts = 0x4000 - scnt
	cmp ax,gr_cols		;If safebyts <= cols, jump,
	jle SetSBs
	mov ax,gr_cols		; else safebyts = cols
SetSBs:	mov safebyts,ax
	push ax			;Push bytes to copy (safebyts)
	push framsg		;Push source seg
	add dx,EMPAGE0		;DX = scnt + EMPAGE0
	push dx			;Push source ofs
	push desseg		;Push dest seg
	push desofs		;Push dest ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
;Did we copy all the cols? If cols != 0, we must cross an EM page boundary
	mov ax,gr_cols
	sub ax,safebyts
	jnz GRmore		;Jump if all the bytes weren't written
GRxt:	mov ax,NO_ERROR		;Else, return NO_ERROR and exit
GRxit:	pop di
	mov sp,bp
	pop bp
	ret
;Write the rest of the bytes
GRmore: mov gr_cols,ax
;Otherwise, adjust EM page
	inc di		;DI = logical page
	push di		;Push logical page
	mov ax,PAGE0
	push ax		;Push physical page
	push gr_handle	;Push handle
	push cs		;Push CS so we can use a near call
	call near ptr _emmap
	add sp,6
	cmp ax,NO_ERROR
	jne GRxit	;Exit if error
;Adjust des: des += safebyts
	mov bx,desofs
	mov dx,desseg
	add bx,safebyts
	adc dx,0
	push gr_cols
	push framsg		;Push source seg
	mov ax,EMPAGE0
	push ax			;Push source ofs
	push dx			;Push dest seg
	push bx			;Push dest ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
	jmp short GRxt
_emgetrow  endp

;/* Copy 'cols' bytes from a row in expanded memory (EM) to conventional
;   memory (CM). Buffer to receive the row must be large enough to hold
;   'cols' bytes. Does not check that EM exists or that handle owns any
;   EM. Uses EM Page 0. Returns NO_ERROR, NO_EMM, or EMM_ERR.
;*/
;int emgetrow(des, handle, stx, sty, cols, width)
;void huge *des;	/* Where to store the row of image data */
;int handle;		/* Previously issued EM handle */
;int stx, sty;		/* Starting position in the EM image */
;int cols;		/* Bytes to retrieve */
;int width;		/* EM image width */
;{
;   int errcode, slpage, safebyts, scnt;
;   unsigned frameseg;
;   long saddr;
;
;   /* Get EM frame seg */
;   if((errcode=emgetframeseg(&frameseg)) == NO_ERROR) {
;      saddr = stx + sty * (long)width;
;      slpage = saddr / 0x4000;	/* Calc EM page to access */
;      /* Map logical to physical page */
;      if((errcode=emmap(handle, PAGE0, slpage)) == NO_ERROR) {
;         /* Make pointer to page frame */
;         scnt = saddr % 0x4000;
; /* Move as many bytes as possible without crossing a page boundary */
;         safebyts = (0x4000-scnt > cols) ? cols : (0x4000-scnt);
;         memcpy_(des, MAKE_FPTR(frameseg, EMPAGE0+scnt), safebyts);
;         /* Did we copy all the cols? If cols != 0, we must cross an EM page
;            boundary -- adjust EM page, des, and move the rest of the bytes
;         */
;         if(cols -= safebyts) {
;            if((errcode=emmap(handle, PAGE0, ++slpage)) == NO_ERROR)
;               memcpy_(des+=safebyts, MAKE_FPTR(frameseg, EMPAGE0), cols);
;            }
;         }
;      }
;   return(errcode);
;}

;Copy 'cols' bytes from a row in conventional memory (CM) to expanded
; memory (EM). Does not check that EM exists or that 'handle' owns any
; EM. Uses EM Page 1. Returns NO_ERROR, NO_EMM, or EMM_ERR.
; Usage:int emputrow(void huge *src, int handle, int stx, int sty,
; int cols, int width);
srcofs	    equ		  @ab[bp]	;Destination for data
srcseg	    equ		  @ab[bp+2]
pr_handle   equ  word ptr @ab[bp+4]
pr_startx   equ  word ptr @ab[bp+6]	;EM buffer dimensions:
pr_starty   equ  word ptr @ab[bp+8]
pr_cols     equ  word ptr @ab[bp+10]
pr_width    equ  word ptr @ab[bp+12]

framsg      equ  word ptr [bp-2]	;EM frame segment
safebyts    equ  word ptr [bp-4]
	public _emputrow
_emputrow  proc  far
	push bp        
	mov bp,sp     
	sub sp,6
	push di
	cld
;Check that EMM is installed
	cmp cs:emm_flag,0
	jnz PRMan		;EMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _emdetect	;Try to detect EM
	cmp ax,NO_ERROR
	jne PRXit		;EMM not installed
;Get EM frame segment value
PRMan:	push ss			;Push variable seg
	lea ax,framsg
	push ax			;Push variable ofs
	push cs			;Push CS so we can use a near call
	call near ptr _emgetframeseg
	add sp,4
	cmp ax,NO_ERROR
	jne PRXit	;Exit if error
;Calculate daddr = stx + sty * (long)width;
	mov ax,pr_starty
	mul pr_width
	add ax,pr_startx
	adc dx,0	;DX:AX = saddr
;Calc dlpage = daddr / 0x4000; (EM page to access)
	push ax		;Save lsw of address
	shl ax,1	;Quotient = DX:AX >> 14
	rcl dx,1
	shl ax,1
	rcl dx,1	;Quotient is in DX
	mov di,dx	;DI = logical page to set
;Map logical to physical page: emmap(handle, PAGE0, dlpage);
	push di		;Push logical page
	mov ax,PAGE0
	push ax		;Push physical page
	push pr_handle	;Push handle
	push cs		;Push CS so we can use a near call
	call near ptr _emmap
	add sp,6
	cmp ax,NO_ERROR
	pop dx		;DX = lsw of daddr
	jne PRXit	;Exit if error
	and dx,3fffh	;Calc remainder (daddr % 0x4000)
;Move as many bytes as possible without crossing a page boundary
	mov ax,4000h
	sub ax,dx		;AX = safebyts = 0x4000 - dcnt
	cmp ax,pr_cols		;If safebyts <= cols, jump,
	jle StSBs
	mov ax,pr_cols		; else safebyts = cols
StSBs:	mov safebyts,ax
	push ax			;Push bytes to copy (safebyts)
	push srcseg		;Push source seg
	push srcofs		;Push source ofs
	push framsg		;Push dest seg
	add dx,EMPAGE0		;DX = dcnt + EMPAGE0
	push dx			;Push dest ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
;Did we copy all the cols? If cols != 0, we must cross an EM page boundary
	mov ax,pr_cols
	sub ax,safebyts
	jnz pr_more		;Jump if all the bytes weren't written
PRXt:	mov ax,NO_ERROR		;Else, return NO_ERROR and exit
PRXit: pop di
	mov sp,bp
	pop bp
	ret
;Write the rest of the bytes
pr_more: mov pr_cols,ax
;Otherwise, adjust EM page
	inc di		;DI = logical page
	push di		;Push logical page
	mov ax,PAGE0
	push ax		;Push physical page
	push pr_handle	;Push handle
	push cs		;Push CS so we can use a near call
	call near ptr _emmap
	add sp,6
	cmp ax,NO_ERROR
	jne PRXit	;Exit if error
;Adjust src: src += safebyts
	mov bx,srcofs
	mov dx,srcseg
	add bx,safebyts
	adc dx,0
	push pr_cols
	push dx			;Push source seg
	push bx			;Push source ofs
	push framsg		;Push dest seg
	mov ax,EMPAGE0
	push ax			;Push dest ofs
	call far ptr _memcpy_	;Move the bytes
	add sp,10
	jmp short PRXt
_emputrow  endp

;int emputrow(src, handle, stx, sty, cols, width)
;void huge *src;	/* Where to get the row of image data */
;int handle;			/* Previously issued EM handle */
;/* EM buffer dimensions: */
;int stx, sty;		/* Starting position in the EM image */
;int cols;			/* Bytes to store */
;int width;			/* EM image width */
;{
;   int errcode, dlpage, safebyts, dcnt;
;   unsigned frameseg;
;   long daddr;
;
;   /* Get EM frame seg */
;   if((errcode=emgetframeseg(&frameseg)) == NO_ERROR) {
;      daddr = stx + sty * (long)width;
;      dlpage = daddr /0x4000;	/* Calc EM page to access */
;      /* Map logical to physical page */
;      if((errcode=emmap(handle, PAGE0, dlpage)) == NO_ERROR) {
;         /* Make pointer to page frame */
;         dcnt = daddr % 0x4000;
; /* Move as many bytes as possible without crossing a page boundary */
;         safebyts = (0x4000-dcnt>cols) ? cols : (0x4000-dcnt);
;         memcpy_(MAKE_FPTR(frameseg, EMPAGE0+dcnt), src, safebyts);
;         /* Did we copy all the cols? If cols != 0, we must cross an EM page
;            boundary -- adjust EM page, des, and move the rest of the bytes
;         */
;         if(cols -= safebyts) {
;            if((errcode=emmap(handle, PAGE0, ++dlpage)) == NO_ERROR)
;               memcpy_(MAKE_FPTR(frameseg, EMPAGE0), src+=safebyts, cols);
;            }
;         }
;      }
;   return(errcode);
;}
	END
+ARCHIVE+ getkey.asm    2194 12/21/1990 10:33:00
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	getkey	Return a single, unique code for single and combo keystrokes

	.MODEL	LARGE
@ab	equ	6

	.CODE
;getkey() - Return a single, unique code for single and combo keystrokes.
; No echo and ignores "Ctrl-Break" input. Include file is keydefs.h
	public _getkey
_getkey  proc  far
	push bp
	mov ah,7
	int 21h
	or al,al	;If 0, extended key
	jz ext_key
	xor ah,ah	;alphanumeric key, just return it in ax
	jmp short by_by
ext_key:
	int 21h		;extended key, get next byte
	mov ah,1	; and return scan code OR'd with 0x100
by_by:
	pop bp
	ret
_getkey endp

IFDEF EXTRA
;Push a keycode gotten by calling getkey() back into the keyboard buffer.
; Usage: void ungetkey(key); Extended keystrokes are defined in keydefs.h.
keyval  equ  word ptr @ab[bp]	;key value in getkey format
	public _ungetkey
_ungetkey  proc  far
	push bp
	mov bp,sp
;Convert key to extn and scancode
	mov ax,keyval		;key value in getkey format
	or ah,ah		;If >=256, extract extn
	jz UNrmKy
;If it's a normal ASCII key=> 0x00yy -> 0x00yy
;Else it's an extended key => 0x01yy -> 0xyy00
	mov ah,al		;0x01yy = 0xyy00 -- Convert to two bytes
	xor al,al
;Put extn and scancode into keyboard buffer
UNrmKy:	call InsKeycode
	pop bp
	ret
_ungetkey endp

;Insert keycode in AX into keyboard buffer. Uses AX, BX, CX
InsKeycode  proc  near
	push di
	push ds
	mov bx,40h
	mov ds,bx
	mov bx,1ch		;BX = nexton
	mov di,1ah		;DI = nextoff
	mov cx,[bx]		;CX = *nexton
	inc cx
	inc cx
;Return if buffer is full
	cmp cx,[di]		;if(*nexton+2 == *nextoff)  return;
	je InKBye		; If tail+2 = head, buffer is full
	cmp word ptr [bx],3ch	;if(*nexton == 3ch && *nextoff == 1eh)  return;
	jne InCnt		; If tail at 3c and head at 1e, buffer is full
	cmp word ptr [di],1eh
	je InKBye
InCnt: 	mov di,[bx]		;poke(40h, *nexton, ch);
	mov [di],ax
	inc di			;*nexton += 2;
	inc di
	cmp di,3eh		;if(*nexton == 3eh)
	jb BuOk			;	*nexton = 1eh;
	mov di,1eh
BuOk:	mov [bx],di
InKBye:	pop ds
	pop di
	ret
InsKeycode  endp
ENDIF
	END
+ARCHIVE+ matavg.asm    2108 12/21/1990 10:33:00
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	matrix_avg_	Average 9 pixels. Used by blur();

	.MODEL	LARGE
@ab	equ	6

	.CODE
;Average 9 pixels. Fill buff[0] to buff[cols]. Used by blur().
; void matrix_avg_(UCHAR *sbuff, UCHAR *dbuff, int cols, int iwidth,
; int *dummyaddr); Used by blur();
sbuff      equ dword ptr @ab[bp]	;Addr of pic central pixel
dbuff	   equ dword ptr @ab[bp+4]	;Buffer to use
cols	   equ  word ptr @ab[bp+8]	;Columns to process
iwidth     equ  word ptr @ab[bp+10]	;Image width
dummyaddr  equ dword ptr @ab[bp+12]	;Not used here

	PUBLIC _matrix_avg_
_matrix_avg_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	cld
	push ds
	lds si,sbuff		;DS:SI = central pixel addr
;Just store first pixel value at sbuff[0]
	les di,dbuff
	movsb			;*dbuff++ = *sbuff++
	sub cols,2		;Adjust columns for first and last points
;Sum = pic[SI-(iwidth+1)]
MTTop:	xor ch,ch		;CX gets sum
	xor ah,ah
	mov bx,si
	sub bx,iwidth
	dec bx
;Accumulate sum in CX
	mov cl,[bx]		;BX = SI - (iwidth+1) = SI - iwidth - 1
;Add in pic[SI-iwidth]
	inc bx
	mov al,[bx]		;BX = SI - iwidth
	add cx,ax
;Add in pic[SI-(iwidth-1)]
	inc bx
	mov al,[bx]		;BX = SI - (iwidth-1) = SI - iwidth + 1
	add cx,ax
;Add in pic[SI-1]
	mov al,[si-1]
	add cx,ax
;Add in pic[SI]
	mov al,[si]
	add cx,ax
;Add in pic[SI+1]
	mov al,[si+1]
	add cx,ax
;Add in pic[SI+(i_width-1)]
	mov bx,si
	add bx,iwidth
	dec bx			;BX = iwidth - 1
	mov al,[bx]		;BX = SI + iwidth - 1
	add cx,ax
;Add in pic[SI+i_width]
	inc bx
	mov al,[bx]		;BX = SI + iwidth
	add cx,ax
;Add in pic[SI+(i_width+1)]
	inc bx
	mov al,[bx]		;BX = SI + iwidth + 1
	add ax,cx		;AX gets sum
;Divide by 9
 	mov bl,9
	div bl			;Result is in AL
;Quotient is in al, store it in buff
	stosb			;buff[indx] = al
	inc si			;sbuff++
	dec cols		;Are we done?
	jg MTTop
;Just store last pixel value
	movsb			;*dbuff++ = *sbuff**
	pop ds
	pop si
	pop di
	pop bp
	ret
_matrix_avg_  endp

	END

+ARCHIVE+ matdif.asm    2766 12/21/1990 10:33:00
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	matrix_dif_	Average 9 pixels. Used by blurthres();

	.MODEL	LARGE
@ab	equ	6

	.CODE
;Average 9 pixels. Fill buff[0] to buff[cols].
; void matrix_dif_(UCHAR *sbuff, UCHAR *dbuff, int cols, int iwidth,
; int diff); Used by blurthres().
; EQUATION: *des = (abs(*sbuff - avg))>diff) ? *src : avg;
sbuff      equ dword ptr @ab[bp]	;Addr of pic central pixel
dbuff	   equ dword ptr @ab[bp+4]	;Buffer to use
cols	   equ  word ptr @ab[bp+8]	;Columns to process
iwidth     equ  word ptr @ab[bp+10]	;Image width
diffaddr   equ dword ptr @ab[bp+12]	;Address of threshold to use

diff       equ  word ptr [bp-2]

	PUBLIC _matrix_dif_
_matrix_dif_  proc  far
	push bp
	mov bp,sp
	dec sp
	dec sp
	push di
	push si
	cld
	push ds
	lds si,diffaddr
	lodsw
	mov diff,ax		;Put threshold into diff
	lds si,sbuff		;DS:SI = central pixel addr
;Just store first pixel value at buff[0]
	les di,dbuff
	movsb			;*dbuff++ = *sbuff++
	sub cols,2		;Adjust columns for first and last points
;sum = pic[SI-(i_width+1)]
MTTop:	xor ch,ch		;CX gets sum
	xor ah,ah
	mov bx,si
	sub bx,iwidth
	dec bx
;Accumulate sum in CX
	mov cl,[bx]		;BX = SI - (iwidth+1) = SI - iwidth - 1
;Add in pic[SI-iwidth]
	inc bx
	mov al,[bx]		;BX = SI - iwidth
	add cx,ax
;Add in pic[SI-(iwidth-1)]
	inc bx
	mov al,[bx]		;BX = SI - (iwidth-1) = SI - iwidth + 1
	add cx,ax
;Add in pic[SI-1]
	mov al,[si-1]
	add cx,ax
;Add in pic[SI+1]
	mov al,[si+1]
	add cx,ax
;Add in pic[SI+(i_width-1)]
	mov bx,si
	add bx,iwidth
	dec bx			;BX = iwidth - 1
	mov al,[bx]		;BX = SI + iwidth - 1
	add cx,ax
;Add in pic[SI+i_width]
	inc bx
	mov al,[bx]		;BX = SI + iwidth
	add cx,ax
;Add in pic[SI+(i_width+1)]
	inc bx
	mov al,[bx]		;BX = SI + iwidth + 1
	add cx,ax		;AX gets sum
;Add in pic[SI]
	mov al,[si]
	mov bx,ax		;Save *sbuff in BX
	add ax,cx		;AX gets sum
;Divide by 9
 	mov dl,9
	div dl			;Result is in AL
;EQUATION: *des = (abs(*sbuff - avg))>diff) ? *src : avg;
;Average value is in al, *sbuff in BX, compare the two
	xor ah,ah
	mov cx,ax		;CX = AX = avg
	sub ax,bx		;AX = avg - *sbuff
	cwd			;Compute abs value of (avg - *sbuff)
	xor ax,dx
	sub ax,dx		;AX = Abs(avg - *sbuff); (DX gets trashed)
	cmp ax,diff		;if(abs(avg - *sbuff) <= diff)
	ja GTdif		; *des = avg (AX)
	mov al,cl		;CL = avg
GTcmb:	stosb			;*dbuff++
	inc si			;sbuff++
	dec cols		;Are we done?
	jg MTTop
;Just store last pixel value
	movsb			;*dbuff++ = *sbuff**
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
GTdif:	mov al,bl		;else  *des = *sbuff (BL)
	jmp short GTcmb
_matrix_dif_  endp
	END
+ARCHIVE+ matsharp.asm  3907 12/21/1990 10:33:30
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	matrix_sharp_
;	matrix_gsharp_

	.MODEL	LARGE
@ab	equ	6

	.CODE
; void matrix_sharp_(UCHAR *sbuff, UCHAR *dbuff, int cols, int iwidth,
; int multiplier); Fill buff[0] to buff[cols].
; For sharpen use char kernal[9]={-1, -1, -1, -1, 9, -1, -1, -1, -1};
; For outline use char kernal[9]={-1, -1, -1, -1, 8, -1, -1, -1, -1};
sbuff      equ dword ptr @ab[bp]	;Addr of pic central pixel
dbuff	   equ dword ptr @ab[bp+4]	;Buffer to use
cols	   equ  word ptr @ab[bp+8]	;Columns to process
iwidth     equ  word ptr @ab[bp+10]	;Image width
multraddr  equ dword ptr @ab[bp+12]	;Multiplier -- must be 0-255

multr	   equ           [bp-2]		;Multiplier -- must be 0-255

	PUBLIC _matrix_sharp_
_matrix_sharp_  proc  far
	push bp
	mov bp,sp
	dec sp
	dec sp
	push di
	push si
	cld
	push ds
	lds si,multraddr
	lodsb			;AL = multiplier
	mov byte ptr multr,al
	lds si,sbuff		;DS:SI = central pixel addr
;Just store first pixel value at buff[0]
	les di,dbuff
	movsb			;*dbuff++ = *sbuff++
	sub cols,2		;Adjust columns for first and last points
	xor ch,ch
; multr * pic[SI]
MrTop:	mov al,[si]		;Value -> DX
	mul byte ptr multr
	mov dx,ax		;DX = AX*9
;Accumulate sum in DX
; - pic[SI-(iwidth+1)]
	mov bx,si		;BX = SI
	sub bx,iwidth
	dec bx
	mov cl,[bx]		;BX = SI - (iwidth+1) = SI - iwidth - 1
	sub dx,cx
; -pic[SI-iwidth]
	inc bx
	mov cl,[bx]		;BX = SI - iwidth
	sub dx,cx
; -pic[SI-(iwidth-1)]
	inc bx
	mov cl,[bx]		;BX = SI - (iwidth-1) = SI - iwidth + 1
	sub dx,cx
; -pic[SI-1]
	mov cl,[si-1]
	sub dx,cx
; -pic[SI+1]
	mov cl,[si+1]
	sub dx,cx
; -pic[SI+(iwidth-1)]
	mov bx,si
	add bx,iwidth
	dec bx			;BX = iwidth - 1
	mov cl,[bx]		;BX = SI + iwidth - 1
	sub dx,cx
; -pic[SI+iwidth]
	inc bx
	mov cl,[bx]		;BX = SI + iwidth
	sub dx,cx
; -pic[SI+(iwidth+1)]
	inc bx
	mov cl,[bx]		;BX = SI + iwidth + 1
	sub dx,cx		;Is result negitive or zero?
	jle SLoLimBd		;Jump if yes
	sub dx,255		;DX > 0
	sbb ax,ax		;DX = MaSum = (MaSum>255) ? 255 : MaSum;
	and dx,ax
	add dx,255
;Value is in dx, store it in buff
StorIt:	mov ax,dx
	stosb			;ES:DI -> buff
	inc si			;sbuff++
	dec cols		;Are we done?
	jg MrTop
;Store last pixel value
	movsb			;*dbuff++ = *sbuff**
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
SLoLimBd: xor dx,dx
	jmp short StorIt
_matrix_sharp_  endp

; void matrix_gsharp_(UCHAR *sbuff, UCHAR *dbuff, int cols, int iwidth,
; int dummy); Fill buff[0] to buff[cols].
; Uses kernal[9]={0, 0, 0, -1, 3, -1, 0, 0, 0};
sbuff      equ dword ptr @ab[bp]	;Addr of pic central pixel
dbuff	   equ dword ptr @ab[bp+4]	;Buffer to use
cols	   equ  word ptr @ab[bp+8]	;Columns to process
iwidth     equ  word ptr @ab[bp+10]	;Image width
dummyaddr  equ dword ptr @ab[bp+12]	;Not used here
	PUBLIC _matrix_gsharp_
_matrix_gsharp_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	cld
	push ds
	lds si,sbuff		;DS:SI = central pixel addr
;Just store first pixel value at buff[0]
	les di,dbuff
	movsb			;*dbuff++ = *sbuff++
	sub cols,2		;Adjust columns for first and last points
	xor ch,ch
; 3 * pic[SI]
GsTop:	mov al,[si]		;Value -> DX
	xor ah,ah
	mov dx,ax
	shl ax,1
	add dx,ax		;DX = AX*2 + AX
;Accumulate sum in DX
; - pic[SI-1]
	mov cl,[si-1]		;BX = SI - 1
	sub dx,cx
; - pic[SI+1]
	mov cl,[si+1]
	sub dx,cx
	jle GLoLmBd		;Jump if result is <= 0
	sub dx,255  		;If result > 255, result = 255
	sbb ax,ax
	and dx,ax
	add dx,255
;Value is in dx, store it in buff
StrIt:	mov ax,dx
	stosb			;ES:DI -> buff
	inc si			;sbuff++
	dec cols		;Are we done?
	jg GsTop
;Store last pixel value
	movsb			;*dbuff++ = *sbuff**
	pop ds
	pop si
	pop di
	pop bp
	ret
GLoLmBd: xor dx,dx
	jmp short StrIt
_matrix_gsharp_  endp

	END
+ARCHIVE+ matsum.asm    5183  3/04/1991 14:46:18
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	matrix_sum_

	.MODEL	LARGE
@ab	equ	6

	.CODE
;Usage: void matrix_sum_(UCHAR *sbuff, UCHAR *dbuff, int cols, int iwidth,
; UCHAR *tabaddr); Used by matrixconv(); NOTE: Tabaddr carries as its 10th
; element the divisor to use. This permits using matrix elements such as:
;	1/36,  4/36, 1/36
;	4/36, 16/36, 4/36
;	1/36,  4/36, 1/36
;**********************
sbuff      equ dword ptr @ab[bp]	;Addr of central pixel
dbuffofs   equ           @ab[bp+4]	;Where to store the result
dbuffseg   equ           @ab[bp+6]
cols	   equ  word ptr @ab[bp+8]	;Columns to process
iwidth     equ  word ptr @ab[bp+10]	;Image width
kernofs    equ           @ab[bp+12]	;Addr of kernel table
kernseg    equ           @ab[bp+14]

MaSum	   equ  word ptr [bp-2]
MaSum1	   equ  word ptr [bp-4]
	PUBLIC _matrix_sum_
_matrix_sum_  proc  far
	push bp
	mov bp,sp
	sub sp,4
	push di
	push si
	cld
;Just store first pixel value at dbuff[0]
	push ds
	les di,sbuff		;ES:DI -> central pixel addr
	lds si,dword ptr dbuffofs
	mov al,es:[di]
	mov [si],al		;*dbuff++ = *sbuff++
	inc word ptr dbuffofs
	inc di
	sub cols,2		;Adjust columns for first and last points
	xor ch,ch
	mov ds,word ptr kernseg	;DS = kernel seg
;kernel[0] * pic[DI-(iwidth+1)]
MTTops:	mov si,word ptr kernofs	;DS:SI -> kernel[0]
	mov bx,di		;ES:DI -> sbuff
	sub bx,iwidth
	dec bx
	mov cl,es:[bx]		;BX = DI - (iwidth+1) = DI - iwidth - 1
	lodsb			;AL = kernel[0]
	cbw
	imul cx			;kernel[0] * pic[DI-(iwidth+1)]
	mov MaSum,ax		;MaSum gets sum
	mov MaSum1,dx
;kernel[1] * pic[DI-iwidth]
	inc bx
	mov cl,es:[bx]		;BX = DI - iwidth
	lodsb			;AL = kernel[1]
	cbw
	imul cx			;kernel[1] * pic[DI-iwidth]
	add MaSum,ax
	adc MaSum1,dx
;kernel[2] * pic[DI-(iwidth-1)]
	inc bx
	mov cl,es:[bx]		;BX = SI - (iwidth-1) = SI - iwidth + 1
	lodsb			;AL = kernel[2]
	cbw
	imul cx			;kernel[2] * pic[DI-(iwidth-1)]
	add MaSum,ax
	adc MaSum1,dx
;kernel[3] * pic[DI-1]
	mov cl,es:[di-1]
	lodsb			;AL = kernel[3]
	cbw
	imul cx			;kernel[3] * pic[DI-1)]
	add MaSum,ax
	adc MaSum1,dx
;kernel[4] * pic[DI]
	mov cl,es:[di]
	lodsb			;AL = kernel[4]
	cbw
	imul cx			;kernel[4] * pic[DI]
	add MaSum,ax
	adc MaSum1,dx
;kernel[5] * pic[DI+1]
	mov cl,es:[di+1]
	lodsb			;AL = kernel[5]
	cbw
	imul cx			;kernel[5] * pic[DI+1]
	add MaSum,ax
	adc MaSum1,dx
;kernel[6] * pic[DI+iwidth-1]
	mov bx,di
	add bx,iwidth
	dec bx			;BX = iwidth - 1
	mov cl,es:[bx]		;BX = SI + iwidth - 1
	lodsb			;AL = kernel[6]
	cbw
	imul cx			;kernel[6] * pic[DI+iwidth-1]
	add MaSum,ax
	adc MaSum1,dx
;kernel[7] * pic[DI+iwidth]
	inc bx
	mov cl,es:[bx]		;BX = SI + iwidth
	lodsb			;AL = kernel[7]
	cbw
	imul cx			;kernel[7] * pic[DI+iwidth]
	add MaSum,ax
	adc MaSum1,dx
;kernel[8] * pic[DI+iwidth+1]
	inc bx
	mov cl,es:[bx]		;BX = SI + iwidth + 1
	lodsb			;AL = kernel[8]
	cbw
	imul cx			;kernel[8] * pic[DI+iwidth+1]
	add ax,MaSum		;DX:AX gets sum
	adc dx,MaSum1
;Divide sum by divisor (kernel[9])
	mov bx,ax		;Save lsw of sum in BX
	lodsb			;AL = divisor
	cbw
	xchg ax,bx		;BX = divisor
	or dx,dx		;Is dividend msw == 0?
	jnz NotZro		; No, overflow is possible
	idiv bx			;Overflow is not possible, OK to use idiv
	jmp short Cnt
;Call with dividend in DX:AX and divisor in BX
NotZro:	call ldiv		;Divide signed long by signed int
	or dx,dx		;If MSW is neg, AX = 0
	js MLoLimBd
Cnt:	sub ax,255  		;If LSW>255 AX = 255
	sbb dx,dx
	and ax,dx
	add ax,255		;AX = MaSum = (MaSum>255) ? 255 : MaSum;
;Value is in al, now store it in buff
StorIt:	mov bx,es		;Save sbuff address
	mov dx,di
	les di,dword ptr dbuffofs
	stosb			;*dbuff = value
	inc word ptr dbuffofs
	mov di,dx		;Restore sbuff address
	mov es,bx		;Save sbuff address
	inc di			;sbuff++
	dec cols		;Are we done?
	jle EndORw
	jmp MTTops
;Just store last pixel value
EndORw:	mov al,es:[di]		;AL = *sbuff
	les di,dword ptr dbuffofs
	stosb			;*dbuff++ = AL
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
MLoLimBd:
	xor ax,ax
	jmp short StorIt
_matrix_sum_  endp

;Divide signed long int by a signed int. On entry dividend is in DX:AX and
; divisor is in BX. Return with quotient in DX:AX. Uses AX, BX, CX, and DX.
; Used to ensure there will be no division overflow.
ldiv  proc  near
	push si
	xor si,si		;Sign flag
;Check sign of dividend
	or dx,dx
	jns DivPos		;If dividend is positive, jump
;Dividend is negative
	inc si
	neg dx			;Calc 2s complement of dividend
	neg ax
	sbb dx,0		;Dividend = DX:AX
;Check sign of divisor
DivPos:	or bx,bx
	jns DisPos
;Divisor is negative
	inc si
	neg bx			;Calc 2s complement of divisor
DisPos:	push ax			;Save dividend offset
	mov ax,dx
	xor dx,dx
	div bx			;Dividend seg / divisor
	mov cx,ax		;Quotient -> CX
	pop ax			;AX = dividend offset
	div bx			;Rem:dividend ofs / divisor
	mov dx,cx		;Quotient = DX:AX
	test si,1		;Calc 2s comp if SI is odd
	jz NotNeg
	neg dx			;Calc 2s complement of quotient
	neg ax
	sbb dx,0
NotNeg:	pop si
	ret
ldiv  endp

	END

+ARCHIVE+ readega.asm   6559  3/21/1991 15:59:10
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314) 962-7833
; Contents:
;	readegapixel_
;	wrtegainit_	Setup for wrtegapixel_
;	wrtegareset_	Reset EGA regs after wrtegapixel_
;	wrtegapixel_	
;	pixel_addr	Calc the buffer addr of a pixel in 640x350/480 modes
;	wrt1egarow_	Write a row of pixels
	
	.MODEL	LARGE
@ab	equ	6	;Equate for large model

;Error codes:
NO_ERROR     EQU  0		;No error
BAD_RANGE    EQU  -1 		;Range error
;Register addresses
READ_PIXEL   EQU  3c7h		;Read pixel addr reg
WRITE_PIXEL  EQU  3c8h		;Write pixel addr reg
GDC_INDEX    EQU  3ceh		;GDC index register
GDC_ROTATE   EQU  3		;GDC data rotate/logical function reg index
GDC_MAP_SEL  EQU  4		;GDC Read Map Select reg
GDC_MODE     EQU  5		;GDC Mode reg
GDC_BIT_MASK EQU  8		;GDC bit mask register index
RMWbits	     EQU  00011000h	;read-modify-write bits (XOR operation)
REPLACE	     EQU  0
EGASEG	     EQU  0a000h

	.CODE
; Usage: color = readegapixel_(xx, yy); (xx,yy) is coord pair. Return an
; EGA/VGA pixel's color (0-240).
xcoord  equ  word ptr @ab[bp]
ycoord  equ  word ptr @ab[bp+2]
	PUBLIC _readegapixel_
_readegapixel_  proc  far
	push bp
	mov bp,sp
	push si
	mov bx,xcoord		;BX = X coord
	mov ax,ycoord		;AX = Y coord
	call pixel_addr		;Call with AX=Y, BX=X. On return,
				; BX=byte addr, CL=bit no., AH=bit mask
	mov ch,ah		;CH = bit mask
	mov si,bx		;SI = byte addr
	xor bl,bl		;Use bl to accumulate value
	mov dx,EGASEG
	mov es,dx
	mov dx,GDC_INDEX	;Graphics controller port
	mov al,GDC_MODE
	out dx,al
	inc dl
	xor al,al
	out dx,al	  	;Read mode 0
	dec dl
	mov al,GDC_MAP_SEL
	out dx,al
	inc dl
	mov al,3 	;Initial bit plane to read
rdlp:	out dx,al	;Select bit plane to read
	mov bh,es:[si]	;BH gets byte from current bit plane
	and bh,ch	;Apply mask
	neg bh		;if bh!=0, neg bh=1xxx xxx, if bh=0, neg bh=0000 0000
	rol bx,1	;if bh!=0, bl->xxxx xxx1, if bh was 0, bl->xxxx xxx0
;Ultimately, bl=0000 abcd, a=contribution from bit plane 3, etc.
	dec al	 	;Select next bit plane no.
	jge rdlp
	mov ah,bl
	xor al,al	;Return color value in AX = abcd 0000
	pop si
	pop bp
	ret
_readegapixel_  endp

;Setup for wrtegapixel_(), wrt1egarow_().
; Usage: wrtegainit_(); Returns nothing.
	PUBLIC _wrtegainit_
_wrtegainit_  proc  far
;Set Graphics Controller Mode reg
	mov dx,GDC_INDEX	;graphics controller port
	mov al,GDC_MODE
	out dx,al
	inc dl
	mov al,2		;Write Mode 2, read Mode 0
	out dx,al
;Set Data Rotate/Function Select reg
	dec dl
	mov al,GDC_ROTATE
	out dx,al
	inc dl
	mov al,REPLACE		;AH=Read-Modify-Write bits
	out dx,al
	ret
_wrtegainit_  endp

;Reset EGA regs after wrtegapixel_(), wrt1egarow_().
; Usage: wrtegareset_(); Returns nothing.
	PUBLIC _wrtegareset_
_wrtegareset_  proc  far
;Restore default Graphics Controller regs
	mov dx,GDC_INDEX	;graphics controller port
	mov al,GDC_BIT_MASK
	out dx,al
	inc dl
	mov al,0ffh		;Default Bit mask
	out dx,al
	dec dl
	mov al,GDC_MODE
	out dx,al
	inc dl
	xor al,al
	out dx,al		;Default Mode reg
	dec dl
	mov al,GDC_ROTATE
	out dx,al
	inc dl
	xor al,al
	out dx,al		;Default Function Select reg
	ret
_wrtegareset_  endp

; Usage: wrtegapixel_(xx, yy, color); (xx,yy) is coord pair. Returns nothing.
; Uses write mode 2.
xcoord  equ  word ptr @ab[bp]
ycoord  equ  word ptr @ab[bp+2]
wcolor  equ  byte ptr @ab[bp+4]
	PUBLIC _wrtegapixel_
_wrtegapixel_  proc  far
	push bp
	mov bp,sp
;Set Graphics Controller Mode reg
	push cs
	call near ptr _wrtegainit_	
;Calc byte addr
	mov bx,xcoord		;BX = X coord
	mov ax,ycoord		;AX = Y coord
	call pixel_addr		;Call with AX=Y, BX=X. On return,
				; BX=byte addr, CL=bit no., AH=bit mask
;Set Graphics Controller Bit Mask reg
	mov dx,GDC_INDEX	;graphics controller port
	mov al,GDC_BIT_MASK
	inc dl
	mov al,ah		;AL gets mask value
	out dx,al
;Set up ES
	mov ax,EGASEG
	mov es,ax
;Set the pixel value
	mov al,es:[bx]		;Latch one byte from each plane
	mov al,wcolor		;AL = color
	mov es:[bx],al		;Update all bit planes
;Restore default Graphics Controller regs
	push cs
	call near ptr _wrtegareset_
	pop bp
	ret
_wrtegapixel_  endp

;Write a row of "cols" pixels in EGA/EEGA mode starting at stx,sty
; Usage: void wrt1egarow_(int stx, int sty, UCHAR *buff, int cols)
wstx     equ  word ptr @ab[bp]  	;Starting X position (0 - 639)
wsty     equ  word ptr @ab[bp+2]	;Starting Y position (0 - 479)
wbuff    equ dword ptr @ab[bp+4]	;Source of pixel data (0 - 255)
wcols    equ  word ptr @ab[bp+8]	;Number of pixels to write
	PUBLIC _wrt1egarow_
_wrt1egarow_  proc  far
	push bp
	mov bp,sp
	push si
	push ds
;Calc byte addr
	mov bx,wstx		;BX = X coord
	mov ax,wsty		;AX = Y coord
	call pixel_addr		;Call with AX=Y, BX=X. On return,
	mov dx,EGASEG		; BX=byte addr, CL=bit no., AH=bit mask
	mov es,dx		;ES:BX -> screen address to write
	lds si,wbuff		;DS:SI -> source buffer
;Set Graphics Controller to access Bit Mask reg
	mov dx,GDC_INDEX	;graphics controller port
	mov al,GDC_BIT_MASK	;AH=Bit Mask, AL=8 (Bit Mask reg)
	out dx,al
	inc dl			;DX -> data reg
WRwTop:	dec wcols		;while(cols--) {
	jl WRwDn
;Set Graphics Controller Bit Mask reg
	mov al,ah		;AH=Bit Mask, AL=8 (Bit Mask reg)
	out dx,al
;Set the pixel value
	mov ch,es:[bx]		;Latch one byte from each plane
	lodsb			;(*buff / 16)++
	shr al,1
	shr al,1
	shr al,1
	shr al,1		;Convert 0 - 255 -> 0 - 15
	mov es:[bx],al		;Update all bit planes
	ror ah,1		;Shift bit mask, 0x80 -> 0x40, etc.
	adc bx,0		;scraddr++
	jmp short WRwTop
WRwDn:	pop ds
	pop si
	pop bp
	ret
_wrt1egarow_  endp

;void wrt1egarow_(int stx, int sty, UCHAR *buff, int cols)
;{
;   while(cols--) {
;      wrtegapixel_(stx++, sty, *buff>>4);
;      buff++;
;   }
;}

;Calculate the buffer addr of a pixel in 640x350 or 640x480 modes. Enter
; with Y coord in AX and X coord in BX. Returns BX=byte addr in buffer,
; CL=no. of bits to shift right (bit no.), AH=bit mask
pixel_addr  proc  near
	shl ax,1
	shl ax,1
	shl ax,1
	shl ax,1	;AX = 16*Y
	mov cx,ax	;AX = CX = 16*Y
	shl ax,1
	shl ax,1	;AX = 64*Y
	add ax,cx	;AX = 64*Y + 16*Y = 80*Y

	mov cl,bl	;CL=lower byte of X
	shr bx,1
	shr bx,1
	shr bx,1	;bx=X/8
	add bx,ax	;BX = 80*Y + X/8
;	add bx,OriginOffset	;BX = byte addr in video buffer
	and cl,7	;if bit mask = 80h, CL=bitno = no. of bits to shift rt
	mov ah,80h	;AH = unshifted bit mask
	shr ah,cl	;AH = bit mask
	ret
pixel_addr  endp

	END

+ARCHIVE+ setgray.asm   7349  1/26/1991  7:41:34
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
;   Contents:
;	setpixelgray
;	getpixelgray

	.MODEL	LARGE
@ab	  equ	  6	;Equate for large model

XLIMIT	  equ  4047	;Limited by size of Hbuff_
YLIMIT    equ 32767
NO_ERROR  equ     0
BAD_RANGE equ    -1
BAD_FAC	  equ    -5
EMM_ERR	  equ   -22
XMM_ERR	  equ   -24

	extrn _emgetbyte_:far
	extrn _xmgetbyte_:far
	extrn _emsetbyte_:far
	extrn _xmsetbyte_:far

;Image descriptor
imgdes_  struc
ibuff	  DD  ?	;Image buffer address in conventional memory
ehandle	  DW  ?	;Expanded memory handle
xhandle	  DW  ?	;Extended memory handle
stx	  DW  ?	;Image area to be processed
sty	  DW  ?
endx	  DW  ?
endy	  DW  ?			
iwidth	  DW  ?	;Image width
ilength	  DW  ?	;Image length
palette   DD  ?	;Address of palette associated with the image
palsize   DW  ? ;Palette size in bytes
imgdes_  ends

	.CODE
;Set gray level value at (xx,yy). Returns NO_ERROR, BAD_FAC, BAD_RANGE,
; EMM_ERR, or XMM_ERR.
; Usage: int setpixelgray(imgdes *desimg, int xx, int yy, int value);
desimg      equ dword ptr @ab[bp]	;Destination image structure
sxx	    equ  word ptr @ab[bp+4]	;X coordinate
syy	    equ  word ptr @ab[bp+6]	;Y coordinate
scolor	    equ           @ab[bp+8]	;Color to set pixel to
setroutseg  equ           [bp-2]	;setbyte() routine to use
setroutofs  equ           [bp-4]
	public _setpixelgray
_setpixelgray  proc  far
	push bp        
	mov bp,sp     
	sub sp,4
	push di
;if(xx >= desimg->iwidth || desimg->iwidth > XLIMIT+1)
;   return(BAD_RANGE);
	push ds
	lds di,desimg
	mov bx,[di].iwidth	;BX = [di].iwidth
	cmp sxx,bx
	jae SPBdRg
	cmp bx,XLIMIT+1
	ja SPBdRg
;if(outrange(0, value, 255))
;   return(BAD_FAC);
	cmp word ptr scolor,255
	ja SPBdFc
;if(desimg->ibuff) {	/* Image is in CM */
;   daddr = xx + yy * (long)desimg->iwidth;
;   desimg->ibuff[daddr] = value;
;   return(NO_ERROR);
;   }
	mov ax,[di]
	or ax,[di+2]	;Is ibuff = NULL?
	jz SPemxm	;If yes, we're using EM or XM
	call calc_addr	;Calc address to read/write (ES:DI)
	mov al,byte ptr scolor
	stosb		;desimg->ibuff[daddr] = value;
	mov ax,NO_ERROR
SPby:	pop ds
SPby2:	pop di
	mov sp,bp
	pop bp
	ret
;Since desimg->ibuff == NULL, image is in EM or XM
; To save time, this function does not check if EM or XM exists */
;if(desimg->ehandle) {
;  handle = desimg->ehandle;
;  setbyte = emsetbyte_;
;  }
SPemxm:	mov cx,[di].ehandle
	or cx,cx	;Is desimg->ehandle == 0?
	jz SPinXM	;If = 0, des is in XM
;des is in EM
	mov ax,offset _emsetbyte_
	mov dx,seg _emsetbyte_
	jmp short SPinEM
;ibuff and ehandle = 0, des must be in XM
SPinXM:	mov cx,[di].xhandle
	mov ax,offset _xmsetbyte_
	mov dx,seg _xmsetbyte_
SPinEM:	pop ds		;Restore DS before calling XMS function!
	mov setroutofs,ax
	mov setroutseg,dx
;rcode = setbyte(handle, xx, yy, desimg->iwidth, value);
	push scolor
	push bx		;BX = [di].iwidth
	push syy
	push sxx
	push cx		;CX = handle
	call dword ptr setroutofs	;Any error code is returned in AX
	add sp,10
	jmp short SPby2
SPBdRg:	mov ax,BAD_RANGE
	jmp short SPby
SPBdFc:	mov ax,BAD_FAC
	jmp short SPby
_setpixelgray  endp

;int setpixelgray(imgdes *desimg, int xx, int yy, int value)
;{
;   int (*setbyte)(int,int,int,int,int);
;   int handle;
;   long daddr;
;   if(xx >= desimg->iwidth || desimg->iwidth > XLIMIT+1)
;      return(BAD_RANGE);
;   if(outrange(0, value, 255))
;      return(BAD_FAC);
;   if(desimg->ibuff) {	/* Image is in CM */
;      daddr = xx + yy * (long)desimg->iwidth;
;      desimg->ibuff[daddr] = value;
;      return(NO_ERROR);
;      }
;   /* Image is in EM or XM */
;   /* To save time, this function does not check that EM or XM exists */
;   if(desimg->ehandle) {
;      handle = desimg->ehandle;
;      setbyte = emsetbyte_;
;      }
;   else {
;      handle = desimg->xhandle;
;      setbyte = xmsetbyte_;
;      }
;   return(setbyte(handle, xx, yy, desimg->iwidth, value));
;}

;Return gray level value at (xx,yy), BAD_RANGE, EMM_ERR or XMM_ERR.
; Usage: int getpixelgray(imgdes *srcimg, int xx, int yy)
srcimg      equ dword ptr @ab[bp]	;Destination image structure
sxx	    equ  word ptr @ab[bp+4]	;X coordinate
syy	    equ  word ptr @ab[bp+6]	;Y coordinate

getroutseg  equ           [bp-2]	;setbyte() routine to use
getroutofs  equ           [bp-4]
	public _getpixelgray
_getpixelgray  proc  far
	push bp        
	mov bp,sp     
	sub sp,4
	push di
;if(xx >= srcimg->iwidth || srcimg->iwidth > XLIMIT)
;   return(BAD_RANGE);
	push ds
	lds di,srcimg
	mov bx,[di].iwidth	;BX = [di].iwidth
	cmp sxx,bx
	jae GPBdRg
	cmp bx,XLIMIT+1
	ja GPBdRg
;if(srcimg->ibuff) {	/* Image is in CM */
;   saddr = xx + yy * (long)desimg->iwidth;
;   return(desimg->ibuff[daddr]);
;   }
	mov ax,[di]
	or ax,[di+2]	;Is ibuff = NULL?
	jz GPemxm	;If yes, we're using EM or XM
	call calc_addr	;Calc address to read/write (ES:DI)
	mov al,es:[di]
	xor ah,ah	;Return value in AX
GPby:	pop ds
GPby2:	pop di
	mov sp,bp
	pop bp
	ret
;Image is in EM or XM */
; To save time, this function does not check that EM or XM exists */
;if(srcimg->ehandle) {
;  handle = srcimg->ehandle;
;  setbyte = emgetbyte_;
;  }
GPemxm:	mov cx,[di].ehandle
	or cx,cx	;Is srcimg->ehandle == 0?
	jz GPinXM	;If = 0, src is in XM
;src is in EM
	mov ax,offset _emgetbyte_
	mov dx,seg _emgetbyte_
	jmp short GPinEM
;ibuff and ehandle = 0, src must be in XM
GPinXM:	mov cx,[di].xhandle
	mov ax,offset _xmgetbyte_
	mov dx,seg _xmgetbyte_
GPinEM:	pop ds		;Restore DS before calling XMS function!
	mov getroutofs,ax
	mov getroutseg,dx
;rcode = getbyte(handle, xx, yy, desimg->iwidth);
	push bx		;BX = [di].iwidth
	push syy
	push sxx
	push cx		;CX = handle
	call dword ptr getroutofs	;Any error code is returned in AX
	add sp,8
	jmp short GPby2
GPBdRg:	mov ax,BAD_RANGE
	jmp short GPby
_getpixelgray  endp

;int getpixelgray(imgdes *srcimg, int xx, int yy)
;{
;   int handle;
;   long saddr;
;   if(xx >= srcimg->iwidth || srcimg->iwidth > XLIMIT+1)
;      return(BAD_RANGE);
;   if(srcimg->ibuff) {		/* Image is in CM */
;      saddr = xx + yy * (long)srcimg->iwidth;
;      return(srcimg->ibuff[saddr]);
;      }
;   /* Image is in EM or XM */
;   /* To save time, function doesn't check that EM or XM exists */
;   if(srcimg->ehandle) {
;      handle = srcimg->ehandle;
;      getbyte = emgetbyte_;
;      }
;   else {
;      handle = srcimg->xhandle;
;      getbyte = xmgetbyte_;
;      }
;   return(getbyte(handle, xx, yy, srcimg->iwidth));
;}

;Address to read/write is returned in ES:DI (= image->ibuff[addr])
; On entry, DS:DI -> image descriptor, BX = [di].iwidth, 
; ibuff seg at [DI+2]. Uses AX, CX, DX, ES, DI.
calc_addr  proc  near
;Calc daddr = xx + yy * (long)desimg->iwidth;
	mov ax,syy		
	mul bx		;DX:AX = yy * iwidth
	add ax,sxx
	adc dx,0	;DX:AX = xx + yy * iwidth
;desimg->ibuff[daddr] = value;
	add ax,[di]	;AX = daddr ofs + ibuff ofs
	adc dx,0	;DX:AX = xx + yy * iwidth
;Convert DX:AX to an address
	mov cl,4	;0000 0000 0000 0aaa ->
	ror dx,cl	;0aaa 0000 0000 0000 
	add dx,[di+2]	;CX = ibuff seg + 1000h(0)
	mov es,dx
	mov di,ax	;ES:DI = desimg->ibuff[daddr]
	ret
calc_addr  endp

	END

+ARCHIVE+ sjet.asm      2942  3/28/1991  8:36:26
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	check_ioctl_
;	SJiofct

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

;Error codes:
NO_ERROR   EQU	  0		;No error
BAD_RANGE  EQU	 -1 		;Range error
SCAN_ERR   EQU  -19		;Scanner error line asserted

;Data format used by IOCTL call.
io_packet  struc ;Command to execute (0 - 4 are valid), 0: Reset,
command	  DB  ?	 ; 1: Read error line status, 2: Read swing buff size,
		 ; 3: Read swing buff seg, 4: Read I/O base addr
err_stat  DB  ?	 ;Ret status, bit 0=1 => scanner error, bit 7=1 => invalid cmd
io_data	  DW  ?  ;Requested data for commands 2 - 4
io_packet  ends

	.DATA
SJbuff  DB  4 dup (0)		;Info gets copied into this buffer
	.CODE
;Access scanner control functions. Return value, NO_ERROR, SCAN_ERR
; or BAD_RANGE. For SJ, SJPlus.
; Usage: unsigned SJiofct(int sjhandle, UCHAR sjcmd);
sjhandle  equ  word ptr @ab[bp]		;Handle
sjcmd     equ  byte ptr @ab[bp+2]	;Command code
	public _SJiofct
_SJiofct  proc  far
	push bp
	mov bp,sp
	mov al,sjcmd		;AX = Command code
;if(cmd > 4)  return(BAD_RANGE);
	cmp al,4
	ja SJioBR
	push ds
	mov bx,@Data		;Make sure SJbuff is accessible
	mov ds,bx
	mov bx,offset SJbuff	;DS:BX -> SJbuff
;*buff = cmd;
	mov [bx].command,al	;Put command code in buffer
	mov dx,sjhandle		;DX = sjhandle
	mov ax,4403h		;IOCTL send control strings function
	mov cx,4		;Command is 4 bytes long
	xchg dx,bx		;BX = handle, DS:DX = buffer
	int 21h			;Device IOCTL write
;Scanner copys info into bytes 1-3 of SJbuff
	mov bx,offset SJbuff	;DS:BX -> SJbuff
	test [bx].err_stat,1	;AL = status byte = buff[1];
	jnz ScEr		;Bit 0 = 1 => scanner error,
	mov ax,NO_ERROR		; bit 7 = 1 => invalid command
	cmp sjcmd,1		;Commands 0 and 1 return nothing
	jbe SIOby		;Else return a value in AX
	mov ax,[bx].io_data	;AX = *(unsigned *)&buff[2];
SIOby:	pop ds
SIOby2:	pop bp
	ret
;Scanner error line asserted, return SCAN_ERR
ScEr:	mov ax,SCAN_ERR
	jmp short SIOby
;Command out of range, return BAD_RANGE
SJioBR:	mov ax,BAD_RANGE
	jmp short SIOby2
_SJiofct  endp

;Check if a handle represents a device or a file.
; Return 0xff if it's a device and ready, else return 0.
; Usage: int check_ioctl_(int handle);
dvhandle  equ  word ptr @ab[bp]	 ;Handle
	public _check_ioctl_
_check_ioctl_  proc  far
	push bp
	mov bp,sp
;Make sure handle belongs to a char device
	mov bx,dvhandle		;Handle
	mov ax,4400h		;Get device info
	int 21h
	jc CoErr
      	and dx,80h		;DX has device info
	jz CoErr		;If bit 7 != 1, it's a file
	mov ax,4407h		;Get output status
	int 21h
	jc CoErr		;If AX = 0xff, driver ready for output
	xor ah,ah		;Return AX = 0xff, if driver available
CoXit: 	pop bp
	ret
CoErr:	xor ax,ax		;Return AX = 0, if driver not available
	jmp short CoXit
_check_ioctl_  endp
	END
+ARCHIVE+ timeout.asm   1814 12/21/1990 10:33:32
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	TimeOut		Delay for X milliseconds (1000 = 1 sec)

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

DeviceDelay  macro  	;Delay a bit to allow time for device to respond 
	jmp $+2		; to successive I/O accesses
	jmp $+2
	jmp $+2
	endm

	.CODE
;Usage: TimeOut(count); Count is in milliseconds (1000 = 1 sec).
; For explanation of timer chips use, see R. Jourdain's Programmer's
; Problem Solver, p.48,65 and Willen and Krantz, 8088 Assembler Language
; Programming, p.103
PortB	equ	61h	;8255 PIA
CommReg	equ	43h
Latch2	equ	42h
Count	equ  word ptr @ab[bp]		;Count * .001 = seconds to delay
	public _timeout
_timeout  proc  far
	push bp
	mov bp,sp
	in al,PortB
	DeviceDelay
	push ax			;Save value
	and al,not 2		;Clear bit 1 => Disconnect speaker
	or al,1			;Set bit 0 => Enable timer channel 2
	out PortB,al
	DeviceDelay
CountLeft:
	cmp Count,0
	jz TObye
	mov al,10110000b	;Init channel 2 for mode 0 (10 11 000 0)
	out CommReg,al
	DeviceDelay
;Load counter
 	mov ax,1193		;Times out in 1 ms (.001 sec)
	out Latch2,al		;send low byte to latch reg
	DeviceDelay
	mov al,ah
	out Latch2,al		;send high byte
	DeviceDelay
; ** Counter is now running **
;Read counter value to detect zero crossing
Above0:	mov al,10000000b	;Latch the count
	out CommReg,al
	DeviceDelay
	in al,Latch2		;get low byte from latch reg
	DeviceDelay
	mov ah,al
	in al,Latch2		;get high byte from latch reg
	DeviceDelay
	xchg ah,al		;value is in AX
	or ax,ax
	jg Above0		;As long as value is >0, continue
	dec Count
	jmp short CountLeft
;Restore PortB value
TObye:	pop ax
	out PortB,al
	pop bp
	ret
_timeout  endp

	END
+ARCHIVE+ vesa.asm      5070  1/23/1991 17:11:48
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	setvesamode
;	vesamodeinfo
;	vesavgainfo_

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

;Error codes:
NO_ERROR   EQU	  0		;No error
BAD_OPN    EQU	 -4		;VESA BIOS extensions not found
VMODE_ERR  EQU	-14		;Video mode was not successfully set

;VGA mode info block (mandatory information)
ModeInfo  struc
ModeAttr     dw ?	;D0=1 => mode supported, D1=1 => extended info
WinAAttr     db ?	;D0=1 => window supported, D1=1 => window readable,
WinBAttr     db ?	; D2=1 => window writeable
WinGran      dw ?	;Window granularity in Kb
WinSize	     dw ?	;In Kb
WinASeg	     dw ?	;Address of window A
WinBSeg	     dw ?	;Address of window B
WinFuncPtr   dd ?	;Pointer to page switching function
BPScanLine   dw ?
ModeInfo  ends

;Super VGA info block
VgaInfo  struc
VESASig	     db 'VESA'     ;4 signature bytes
VESAVers     dw ?	   ;VESA version number
OEMStrPtr    dd ?	   ;Pointer to OEM string
Capabilities db 4 dup (?)  ;Capabilities of the video environment
VModePtr     dd ?	   ;Pointer to supported Super VGA modes
VgaInfo      ends

	extrn _Hbuff_:BYTE	;General purpose buffer (see viccore.c)

	.CODE
SUCCESS     equ  004fh	;Return value if a VESA function worked
GETSVGAINFO equ  4f00h
GETMODEINFO equ  4f01h
SETVESAMODE equ  4f02h

;Load super VGA info into Hbuff_. Returns NO_ERROR if VESA BIOS extensions
; were found and function was successful, else return BAD_OPN.
; Usage: int vesavgainfo_(void); Uses AX and Hbuff_.
	public _vesavgainfo_
_vesavgainfo_  proc  far
	push es
	push di
;Use Hbuff_ to store 256 bytes of VGA info we'll get
	mov di,offset _Hbuff_
	mov ax,seg _Hbuff_
	mov es,ax
	mov ax,GETSVGAINFO
	push di		;Save offset of Hbuff_
	int 10h		;ES:DI -> Where to copy VESA data to
	pop di
	cmp ax,SUCCESS	;Error if AH != 0 or AL != 4f
	jne VsiNVs	;VESA extensions not supported
;Return NO_ERROR to indicate that VESA BIOS extensions are present
	mov ax,NO_ERROR
VsiXit:	pop di
	pop es
	ret
;VESA BIOS extensions are not present, return BAD_OPN
VsiNVs:	mov ax,BAD_OPN
	jmp short VsiXit
_vesavgainfo_  endp

;Load VESA video mode info into Hbuff_. Returns NO_ERROR if VESA BIOS
; extensions were found and function was successful. Returns BAD_OPN
; if VESA BIOS extensions not found and VMODE_ERR if the mode is not 
; supported by the video adapter installed. Uses AX, ES and Hbuff_.
; Usage: int vesamodeinfo(int vmode);
vid_mode  equ  @ab[bp]
	public _vesamodeinfo
_vesamodeinfo  proc  far
	push bp
	mov bp,sp
	push di
	push si
	push ds
	cld
;Check for VESA BIOS extensions support and put VGA info into Hbuff_
	push cs	 	;Push CS to allow near call
	call near ptr _vesavgainfo_
	cmp ax,NO_ERROR
	jne VmiXt
;Super VGA info is now in Hbuff_
;Use Hbuff_ to store 256 bytes of mode info we'll get
	mov di,offset _Hbuff_
	mov ax,seg _Hbuff_
	mov es,ax	;ES:DI -> Hbuff_
	lds si,es:[di].VModePtr	;Pointer to supported Super VGA modes
;Search thru the list of supported modes until we find -1 or the
; one we're trying to set
	mov cx,0fffh	;Just in case
VmiTp:	lodsw		;Get the next mode in the list
	cmp ax,vid_mode	;Is the mode we're to set in the list?
	je VmFdMd	;Jump if yes
	cmp ax,0ffffh	;List is terminated with -1
	je VmiBMd	;Found -1 before vid_mode => mode requested
	loop VmiTp	; is not supported
VmiBMd:	mov ax,VMODE_ERR ;Mode read was not what we asked for
	jmp short VmiXt
;Mode requested was found in the list, get specific info about the mode
VmFdMd:	mov cx,vid_mode
	mov ax,GETMODEINFO	;ES:DI -> Hbuff_
	int 10h
	cmp ax,SUCCESS	;Error if AH != 0 or AL != 4f
	jne VmiBMd	;VESA extensions not supported
;Check mode attribute to make sure that the mode's supported
	test es:[di].ModeAttr,1	;Check that bit 0 is 1
	jz VmiBMd	;Mode not supported in current configuration
;Return NO_ERROR to indicate success
	mov ax,NO_ERROR
VmiXt:	pop ds 	
	pop si
	pop di
	pop bp
	ret
_vesamodeinfo  endp

;Set a VESA video mode. Returns NO_ERROR or VMODE_ERR if VESA extensions
; are not found or the video mode is not supported or was not sucessfully
; set. Usage: int setvesamode(vmode);
vid_mode  equ  @ab[bp]
	public _setvesamode
_setvesamode  proc  far
	push bp
	mov bp,sp
;Make sure VESA BIOS extensions are present and the video mode is supported
	mov bx,vid_mode
	push bx		;Push video mode
	push cs	 	;Push CS to allow near call
	call near ptr _vesamodeinfo
	pop bx		;BX = video mode
	cmp ax,NO_ERROR
	jne VsBdMd	;Mode not supported in current configuration
;VESA mode info is now in Hbuff_, set the mode
	mov ax,SETVESAMODE
	int 10h		;BX still has the video mode number
	cmp ax,SUCCESS	;Error if AH != 0 or AL != 4f
	jne VsBdMd	;Error occurred
	mov ax,NO_ERROR	 ;Success 
VsXit:	pop bp
	ret
;Mode not supported in current configuration or VESA extensions not present
VsBdMd:	mov ax,VMODE_ERR
	jmp short VsXit
_setvesamode  endp

	END
+ARCHIVE+ vidinfo.asm  16723  3/20/1991 19:17:16
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;		Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314) 962-7833
; Contents:
;	pcvideoinfo	Determine if VGA, MGCA, EGA, CGA, MDA, HGC, EVGA
;			adapter(s) is present
;	ck_ATI
;	ck_Paradise
;	ck_Trident
;	ck_Tseng
;	ck_Vega
;	Find6845
;	FindHGC
;	TridType_
;	TridMemory_
;	TsengType_
;	Tseng4Memory_

	.MODEL	LARGE
@ab	equ	6

	extrn _Hbuff_:BYTE	;General purpose buffer (see viccore.c)

;Display adapter flags structure
DispAdap  STRUC
VGAflag    DW	?
MCGAflag   DW	?
EGAflag	   DW	?
CGAflag	   DW	?
MDAflag    DW	?
HGCflag	   DW	?
EVGAflag   DW	?
DispAdap  ENDS

DisAdByts  equ  size DispAdap	;No. of bytes in DispAdap structure
NO_ERROR   equ  0
VMODE_ERR  equ	-14
CRTC_RG	   equ  3d4h
EGASEG	   equ  0a000h
TSENG	   equ  1
ATI	   equ  2
PARADISE   equ  3
VEGA	   equ  4
TRIDENT	   equ  5
TSENG4	   equ  6

;VGA mode extended info block (optional information based on ModeAttr bit 1)
ExtendedInfo  struc
XRes         dw ?	;X,Y resolution of the mode, in pixels
YRes         dw ?
XCharSize    db ?	;Character cell size, in pixels
YCharSize    db ?
NumPlanes    db ?	;Number of planes in the video mode (4 or 1)
BPPixel      db ?	;Number of bits that define 1 pixel
NumBanks     db ?	;Number of banks - 1 for VGA modes
MemModel     db ?	;Memory organization used by mode - 3 => 4 planar,
			; 4 => packed pixel, 5 => non-chain 4, 256 color
BankSize     db ?	;0 for VGA modes
ExtendedInfo  ends

	.DATA
;For use by FindVGA, translation table for int 10h, fctn 1ah
DCCtbl	DB	0	;no monitor attached
	DB	0	;MDA,MDADisplay
	DB	0	;CGA,CGADisplay
	DB	0
	DB	0	;EGA,EGAColorDisplay
	DB	0	;EGA,MDADisplay
	DB	0
	DB	1	;VGA,PS2MonoDisplay
	DB	1	;VGA,PS2ColorDisplay
	DB	0
	DB	0	;MCGA,EGAColorDisplay
	DB	1	;MCGA,PS2MonoDisplay
	DB	1	;MCGA,PS2ColorDisplay
;For use by FindEGA, valid displays for setting EGAflag
; (EGA card switch settings/2)
EGADsps	DB	0	;CGADisplay, 0000b or 0001b (0-1)
	DB	1	;EGAColDisp, 0010b or 0011b (2-3)
	DB	0	;MDADisplay, 0100b or 0101b (4-5)
	DB	0	;CGADisplay, 0110b or 0111b (6-7)
	DB	1	;EGAColDisp, 1000b or 1001b (8-9)
	DB	0	;MDADisplay, 1010b or 1011b (10-11)

	.CODE
;Determine if VGA, MGCA, EGA, CGA, MDA, HGC adapter is present
;Usage: pcvideoinfo(&DispAdap);	Set display adapter flags
;struct DispAdapter {int VGAflag,MCGAflag,EGAflag,CGAflag,MDAflag,HGCflag,EVGAflag;
;	};
DisAdAddr  equ dword ptr @ab[bp]
	PUBLIC _pcvideoinfo
_pcvideoinfo  proc  far
	push bp
	mov bp,sp
	push si
	push di
	cld
	xor ax,ax
	les di,DisAdAddr ;ES:DI->Flags structure
	push di		 ;Save start addr
	mov cx,DisAdByts ;Bytes in DispAdap struc
	rep stosb	 ;Zero flags
	pop di		 ;Reset di to flags.VGA
;Look for VGA via int 10h, fctn 1ah.  If VGA is present, on return AL = 1ah.
	mov ax,1a00h
	int 10h
	cmp al,1ah
	jne NoVGA	;if fctn not supported, no MCGA, VGA present, test for EGA
;Check for VGA compatible monitor by converting BIOS DCCs into specific
; subsystems and displays (BL = active display code)
	xor bh,bh
	mov al,[bx+offset DCCtbl]
	or al,al
	jz NoVGA
;VGA compatible monitor found, Set the VGA flag
	mov byte ptr es:[di].VGAflag,1
	call ck_ATI    ;Check for ATI Wonder VGA chipset
	or ax,ax       ; AX = 0 => no ATI adapter was detected, or
	jnz SetEF      ; video memory <512Kb
	call ck_Paradise ;Check for Paradise Professional chipset
	or ax,ax
	jnz SetEF
	call ck_Trident	;Check for Trident CS chipset
	or ax,ax
	jnz SetEF
	call ck_Tseng	;Check for Tseng based VGA card (e.g., Orchid card)
	or ax,ax
	jnz SetEF
	call ck_Vega    ;Check for Vega V7VGA chipset
	or ax,ax
	jz NoVGA
SetEF:	mov byte ptr es:[di].EVGAflag,al ;Set/clear EVGAflag
;Look for EGA via int 10h, fctn 12h.  If EGA is present, on return BL != 10h.
NoVGA:	mov bl,10h	;BL=10h - ret EGA info
	mov ah,12h	;Get info from EGA BIOS 
	int 10h		;If present, BL != 10h, CL=switch setting
	cmp bl,10h	;BL: 0=64Kb, 1=128Kb, 2=192Kb, 3=256Kb RAM
	je NoEGA
;	cmp bl,3	;Don't check for memory
;	jb NoEGA	;Not enough EGA memory (<256Kb)!!
;Check for EGA compatible monitor
	xor bx,bx
	shr cl,1
	mov bl,cl	;BX = switch settings/2
	mov al,[bx+offset EGADsps]
	or al,al
	jz NoEGA	;Display not compatible
	mov byte ptr es:[di].EGAflag,1 ;Set EGA flag
;Look for MCGA:	if(VGAflag==1 and EGAflag==0)	MCGAflag=1
NoEGA:	cmp byte ptr es:[di].VGAflag,0	 ;VGAflag==0 => MCGA not present
	jz NoMCGA
	cmp byte ptr es:[di].EGAflag,1	 ;EGAflag==1 => MCGA not present
	jz NoMCGA
	mov byte ptr es:[di].MCGAflag,1	 ;Set MCGA flag
;Look for MDA by searching for 6845 at I/O port 3b4h
NoMCGA:	mov dx,03b4h
	call Find6845
	jc NoMDA	;jmp if not present
	mov byte ptr es:[di].MDAflag,1	;Set flag for MDA
	call FindHGC
	jc NoMDA	;jmp if not present
	mov byte ptr es:[di].HGCflag,1  ;Set flag for HGC
;Look for CGA by searching for 6845 at I/O port 3d4h
NoMDA:	mov dx,CRTC_RG
	call Find6845
	jc VIXit			;Jump if not present
	mov byte ptr es:[di].CGAflag,1	;Set CGA flag
VIXit:	pop di
	pop si
 	pop bp
	ret
_pcvideoinfo  endp

;Tseng specific
GDCSS	EQU  03cdh	;GDC Segment Select reg
	.DATA
TseName  DB  'seng'
TSELENG  equ  $-TseName
	.CODE
;Search video BIOS area for "Tseng" and "STB" to identify Tseng based VGA
; card and check for 512Kb video memory. Return AX = TSENG or TSENG4 
; if found, else return 0. Uses AX, BX, CX, and DX.
Tflag  equ  word ptr [bp-2]  ;Flag value
ck_Tseng  proc  near
	push bp
	mov bp,sp
	sub sp,2	;Reserve space for local variables
	push es
	push di		;Save address of display struct
	mov ax,0c000h
	mov es,ax
    	mov Tflag,0	;Assume no Tseng adapter
	mov cx,4096	;Check up to 4096 bytes
	xor di,di	;Start check at 0c000:0000 (video BIOS seg)
	mov al,'T'
CTCkT:	repne scasb	;Scan for 'T'
	jne Ck4STB	;Jump if no 'T' and CX = 0
	push cx
	lea si,TseName	;DS:SI = expected name
	mov cx,TSELENG	;Length of expected name
	repz cmpsb	;Compare string to TseName
	pop cx
	jne CTckT	;jump if no match
;Found Tseng, determine if chipset is the 3000 or 4000
CTFdTs:	push cs	 	;Push CS to allow near call
	call near ptr _TsengType_
	cmp ax,3000
	je HardWay
;Tseng 4000 chipset, determine memory the easy way
	push cs	 	;Push CS to allow near call
	call near ptr _Tseng4Memory_
	cmp al,2
	jb NoTsng	 ;Only 256Kb RAM, exit
	mov Tflag,TSENG4 ;>256Kb, set EVGAflag to TSENG
	jmp short NoTsng
;Check for an STB display adapter by searching for 'STB'
; (STB uses the Tseng chipset)
Ck4STB:	mov cx,1024	;Check up to 1024 bytes
	mov al,'S'
	xor di,di	;Start check at 0c000:0000 (video BIOS seg)
CSTCkT:	repne scasb	;Scan for 'S'
	jne NoTsng	;Jump if no 'T'
	cmp es:[di],'BT' ;Looking for 'STB'
	je CTFdTs
	jmp short CSTCkT	;Continue if no match
;Tseng 3000 chipset, determine memory thru brute force
; by writing/reading memory in bank 4
HardWay: mov ah,0fh	;Get current video mode
	int 10h
	push ax		;Save it
	mov ax,EGASEG	;Have to set video mode to 13h
	mov es,ax	; for this to work
	mov ax,0013h	;Select mode 13h
	int 10h		;Change the mode
	mov dx,GDCSS	;GDC Segment Select reg
	in al,dx
	push ax		 ;Save reg value
	mov al,01100100b ;Set GDC Seg Sel reg for bank 4: 01rrrwww
	out dx,al	 ;Select to read/write bank 4
	mov bx,1234h
	mov es:[bx],bx	;Write 1234 to a000:1234, bank 4
	jmp $+2		;Delay a bit to allow time for device to
	jmp $+2		;  respond to successive memory accesses
	cmp bx,es:[bx]	;Read a000:1234. Was it written?
	jne NTseng	; No, don't set EVGAflag
	mov Tflag,TSENG	; Yes, set EVGAflag to TSENG
NTseng:	pop ax		;Restore GDC Seg Sel reg
	out dx,al
	pop ax		;Get video mode back
	xor ah,ah	;Restore video mode
	int 10h
NoTsng:	mov ax,Tflag	;Return Tsengflag value in AX
	pop di
	pop es
	mov sp,bp
	pop bp
	ret
ck_Tseng  endp

;Determine if chipset is the Tseng 3000 or 4000. Return 3000 or 4000.
; Usage: int TsengType_(void); Uses AX, DX
	public _TsengType_
_TsengType_  proc  far
	mov dx,CRTC_RG
	mov ax,33h
	out dx,al
	inc dx
	in al,dx	;Read the reg
	push ax		;Save the value
	not al		;Change it
	and al,0fh	;Only bottom 4 bits are valid
	mov ah,al	;Save the value in AH
	out dx,al	;Write the new value
	jmp $+2		;Delay a bit to allow time for device
	jmp $+2		;  to respond to successive I/O accesses
	in al,dx	;Read it back
	and al,0fh	;Only bottom 4 bits are valid
	cmp ah,al	;If AL = AH, reg can be written and read, 
	pop ax		; so the chipset is a 4000
	out dx,al	;Restore the reg's original value
	mov ax,3000	;Return 3000 if the chipset's a Tseng 3000 
	jne TT40
	mov ax,4000	;Return 4000 if it's a Tseng 4000
TT40:	ret
_TsengType_  endp

	.DATA
;Number of banks of 256Kb video RAM
TsenMemBanks  DB  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4

	.CODE
;Determine memory available on a Tseng 4000 based VGA. Return banks of 
; 256K RAM installed: 1, 2, or 4. Usage: int Tseng4Memory_(void);
; Only works for Tseng 4000 chipset! Uses AX, BX, DX.
	public _Tseng4Memory_
_Tseng4Memory_  proc  far
;Set "key" to access CRTC reg 37
	call SetTsenKey	;Unlock Tseng 4000 special regs
	mov dx,CRTC_RG
	mov ax,37h	;Access video system cfg 2 reg
	out dx,al
	inc dx
	in al,dx	;Read the reg
;Isolate the meaningful bits: 3 (bus width) and 0-1 (data depth)
	and ax,000bh
;Use the result as an index into TsenMemBanks table
	mov bx,offset TsenMemBanks
	add bx,ax
	mov ax,[bx]
	ret
_Tseng4Memory_  endp

;Unlock special regs. See p. 102, Tseng ET4000 data book.
; For Tseng 4000 chipset only. Uses AX and DX.
SetTsenKey  proc  near
	mov dx,3bfh	;Herc comp reg
	mov al,3
	out dx,al
;Determine if mode control reg is at 3d8 or 3b8
	mov dl,0cch	;Misc output reg, 3cch
	in al,dx	;Read the reg
	add dl,0ch	;Assume color mode (reg at 3d8)
	and al,1	;Bit 0 set if CRTC at 3dxh
	jnz KSetRg
	sub dl,20h	;Mono mode reg address (3b8)
KSetRg:	mov al,0a0h
	out dx,al	;DX = mode control reg
	ret
SetTsenKey  endp

;ATI specific equates
PLANE_MASK  equ  0e1h
PLANE_SEL   equ  0b2h
DATA_I      equ  0bbh
ATI_REG_ADR equ  010h
	.DATA
ATIName  DB  ' 761295520'
ATILENG  equ  $-ATIName
	.CODE
;Identify an ATI VGA Wonder chipset with 512Kb video memory.
; Return AX = ATI if found, else 0. Uses AX, CX, and SI.
ck_ATI  proc  near
	push es
	push di
	mov ax,0c000h
	mov es,ax
	mov di,30h	;Look at 0c000:0030 for " 761295520"
	mov si,offset ATIName	;DS:SI = expected name
	mov cx,ATILENG	;Length of expected name
	repz cmpsb	;Compare string to ATIName
	jne NoATI
;Found ATI product, now check for VGA Wonder
	mov di,40h
	cmp es:[di],'13' ;VGA Wonder has "31" at 0c000:40
	jne NoATI
;Found ATI VGA Wonder. Check for 512K RAM
	mov di,ATI_REG_ADR
	mov dx,es:[di]	;Get ATI_REG, store it in DX
	mov al,DATA_I
	cli
	out dx,al
	inc dx
	in al,dx
	sti
	and al,020h	;AL = 0 => 256K, AL = 020h => 512K
	or al,al
	jz NoATI
	mov ax,ATI	;Set AX for ATI VGA
CABy:	pop di
	pop es
	ret
NoATI:	xor ax,ax	;Not found
	jmp short CABy
ck_ATI  endp

;Paradise specific
GDC_INDEX   equ	   3ceh	;GDC index reg
GDC_DATA    equ	   3cfh	;GDC data reg
KEY	    equ   0500h	;Value to unlock PR regs PR0 - PR4
PR1	    equ   0bh	;PR reg for memory size
PR5	    equ   0fh	;PR reg for unlocking PR0 - PR4
	.DATA
ParName  DB  'VGA='
PARLENG  equ  $-ParName
	.CODE
;Identify a Paradise VGA Professional chipset with 512Kb video memory.
; Return AX = PARADISE if found, else 0. Uses AX, BX, CX, DX, and SI.
ck_Paradise  proc  near
	push es
	push di
	xor bx,bx	;Assume VGA is not Paradise or has RAM < 512Kb
	mov ax,0c000h
	mov es,ax
	mov di,7dh	;Look at 0c000:007d for "VGA="
	mov si,offset ParName	;DS:SI = expected name
	mov cx,PARLENG	;Length of expected name
	repz cmpsb	;Compare string to ParName
	jne NoPara
;It's a Paradise board, now check for 512K RAM
;Unlock PR0 - PR4
	mov dx,GDC_INDEX
	mov ax,KEY OR PR5
	out dx,ax	;Unlock PR0 - PR4
	mov al,PR1
	out dx,al	;Select to access PR1
	in ax,dx	;Get memory size (bits 6 and 7) from data reg
	and ah,080h	;00 or 40 => 256Kb, 80 => 512Kb, c0 => 1MB
	jz PaBy		;Bit 7 must be set if RAM >= 512Kb
	mov bx,PARADISE	;BX holds the flag value
;Relock PR0 - PR4
PaBy:	mov ax,0000h OR PR5
	out dx,ax
NoPara:	mov ax,bx	;Return value in AX
	pop di
	pop es
	ret
ck_Paradise  endp

;Trident specific
GDC_INDEX  equ	3ceh	;GDC index reg
GDC_MISC   equ  6	;Bits 2-3 of reg select a 64K or 128K video buffer
TS_INDEX   equ	3c4h	;TS index register
TRI_VERS   equ  0bh	;Chip version reg, bits 0-3
TRI_MODE1  equ  0eh	;Segment select reg, bits 0-3
TRI_MEM    equ  01fh	;Memory bank reg, bits 0-1

	.DATA
TriName  DB  'RIDENT'
TRILENG  equ  $-TriName
	.CODE
;Identify a Trident version CS chipset with 512Kb video memory.
; Return AX = TRIDENT if found, else 0. Uses AX, CX, DX, and SI.
ck_Trident  proc  near
	push es
	push di		;Save address of display struct
	mov cx,4096	;Check up to 4096 bytes
	mov ax,0c000h
	mov es,ax
	xor di,di	;Start at 0c000:0000 (video BIOS seg)
	mov al,'T'
CTrCk:	repne scasb	;Scan for 'T'
	jne NoTrid	;jump if no 'T'
	push cx
	mov si,offset TriName	;DS:SI = expected name
	mov cx,TRILENG	;Length of expected name
	repz cmpsb	;Compare string to TriName
	pop cx
	jne CTrCk	;Name not found
;Found Trident, check for 8900 or 8800CS version chip (also puts chip into
; 64K page mode)
	push cs	 	;Push CS to allow near call
	call near ptr _TridType_
	cmp al,2	;3 = 8900, 2 = 8800CS,
	jb NoTrid	; 1 = 8800BR (BR is not supported here)
;Check for > 256Kb VRAM
	push cs	 	;Push CS to allow near call
	call near ptr _TridMemory_	;For 8800CS and 8900 only
	cmp al,2
	jb NoTrid	 ;Only 256Kb RAM, exit
	mov ax,TRIDENT	;Set AX for Trident VGA
TrBy:	pop di
	pop es
	ret
NoTrid:	xor ax,ax	;Not found
	jmp short TrBy
ck_Trident  endp

;Determine if chipset is the Trident 8800BR, 8800CS, or 8900 (also puts chip
; into 64K page mode). Return 1 if 8800BR, 2 if 8800CS, or 3 if 8900.
; Usage: int TridType_(void); Uses AX, DX.
	public _TridType_
_TridType_  proc  far
	mov dx,TS_INDEX
	mov al,TRI_VERS
	out dx,al
	inc dx
	in al,dx
	and ax,3	;Return 0 - 3
	ret
_TridType_  endp

;Determine memory available on a Trident 8800CS and 8900 based VGA. Return
; banks of 256K RAM installed: 1, 2, 3, or 4. Usage: int TridMemory_(void);
	public _TridMemory_
_TridMemory_  proc  far
	mov dx,CRTC_RG
	mov al,TRI_MEM
	out dx,al
	inc dx
	in al,dx	;Bits 0 - 1 indicate installed memory:
	and ax,3	; 0 => 256Kb, 1 => 512Kb, 2 => 768Kb, 3 => 1024Kb
	inc ax		;Convert to number of 256Kb banks
	ret
_TridMemory_  endp

;Identify a Video Seven (Vega) V7VGA chipset with 512Kb video memory.
; Return AX = VEGA if found, else 0. Uses AX and BX.
ck_Vega  proc  near
	push di
	mov ax,6f00h
	int 10h
	cmp bx,'V7'	;'V7' returned in BX => Vega card
	jne NoVega
	mov ax,6f07h	;Now check for enough VRAM
	int 10h		;BX = rev no., AH: bit 7 = 1 => VRAM, else DRAM
	and ah,07fh	;AH = banks of 256Kb memory installed (1-4)
	cmp ah,2	;May also want to check for rev no. in BL
	jb NoVega	;If not 512Kb, don't set flag
	mov ax,VEGA	;Set AX for Vega VGA
VgBy:	pop di
	ret
NoVega:	xor ax,ax	;Not found
	jmp short VgBy
ck_Vega  endp

;Find6845 - Detect presence of a 6845 on the MDA, CGA, or HGC by writting
; and reading reg 0fh (cursor location low).  If value written = value read,
; assume chip present. Caller: DX=port addr, Returns: CY set if not present.
Find6845  proc  near
	mov al,0fh
	out dx,al  	;select cursor low reg
	inc dx
	in al,dx	;read current value -> AL
	mov ah,al	;save it in AH
	mov al,66h	;output an arbitrary value
	out dx,al
	mov cx,100h
CCWait: loop CCWait	;wait for 6845 to respond
	in al,dx
	xchg ah,al	;AH=returned value, AL=original value
	out dx,al	;restore original value
	cmp ah,66h	;test if 6845 responded
	je CCXit	;jmp if it did (CY cleared)
	stc		; else set CY if no 6845
CCXit:	ret
Find6845  endp

;FindHGC - Look for 6845 at I/O port 3b4h.  If a 6845 is found, routine
; distinguishes between MDA and Herc by bit 7 of CRT status port.  Bit
; changes on a Herc, but not on an MDA.  The Herc adapters are ID'd by
; bits 4-6 of the CRT Status value:	000b = HGC
; Ret with CY set if not found	       	001b = HGC+
;					101b = InColor
;					
FindHGC  proc  near
	mov dx,03bah	;Access CRTC status port
	in al,dx
	and al,80h
	mov ah,al	;AH=bit 7 (vert sync on HGC)
	mov cx,8000h	;do this 32768 times
RdStat: in al,dx
	and al,80h		;isolate bit 7
	cmp ah,al
	loope RdStat	;wait for bit 7 to change
	je HCNotH	;If bit 7 doesn't change, it's not a Herc.
	clc		;If it does, adapter = HCG, HCGPlus, or HCG InColor
	ret
HCNotH:	stc
	ret
FindHGC  endp
	END
+ARCHIVE+ vwmin.asm     8056  3/04/1991 14:46:16
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;		Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
;   Contents:
;	calc_pbyt_	Minavgerage support routines
;	init_errindx_
;	calc_dbyt_
;	erase1row_	Erase a row

	.MODEL	LARGE
@ab		equ  6	;Equate for large model

GDC_INDEX	equ  3ceh	;GDC index reg
GDC_ROTATE	equ  3		;GDC data rotate/logical function reg index
GDC_SET_RESET	equ  0		;GC set/reset reg
GDC_BIT_MASK	equ  8		;GDC bit mask register index
REPLACE		equ  0
ORVAL		equ  10h

	.DATA
	EVEN
;Used to store positions of ints to fetch
errindx	DW     15 dup (0)
;Alpha array:  Multiplied by big error array
alpha	DB	1, 3, 5, 3, 1
	DB	3, 5, 7, 5, 3
	DB	5, 7, 0, 0, 0
SUMALPHA  equ  1+3+5+3+1+3+5+7+5+3+5+7+0+0+0	;48

	.CODE
;Calculate the byte to display
; Usage: int calc_pbyt_(int *bigE, int pos, UCHAR *src);
bigE	equ dword ptr @ab[bp]	;Address of big error array
pos	equ  word ptr @ab[bp+4]	;Current position in big error array
srcofs	equ  word ptr @ab[bp+6]	;Where the data will come from
srcseg	equ  word ptr @ab[bp+8]

kk	equ  byte ptr [bp-2]
serrseg	equ  word ptr [bp-4]
serrofs	equ  word ptr [bp-6]
	public _calc_pbyt_
_calc_pbyt_  proc  far
	push bp        
	mov bp,sp     
	sub sp,8
	push di
	push si
	cld
	les di,bigE	;ES:DI -> big error array
	mov si,srcofs	;SI -> src
	mov kk,8
;   while(kk--) {
CPTop:	dec kk
	jl CPdone
;sumerr = 0;
	xor ax,ax
	mov serrseg,ax
	mov serrofs,ax
;pbyt <<= 1;
	shl ch,1	;CH = pbyt
;for(j=0; j<12; j++)	/* Calc sumerr */
	xor cl,cl	;CL = j
NxtJ:	mov bl,cl	;BX = j
	xor bh,bh	;BX is index into char array alpha
	mov dl,alpha[bx]	;DL = alpha[j];
	xor dh,dh
	shl bx,1	;Make BX an index into an int array
	mov ax,errindx[bx]
	add ax,pos		;AX = Errindx[j] + pos;
	shl ax,1
	mov bx,ax
	mov ax,es:[di+bx]	;AX = bigE[Errindx[j] + pos];
	imul dx		;DX:AX =  bigE[Errindx[j] + pos]  * (int)alpha[j];
;sumerr +=  bigE[Errindx[j] + pos]  * (int)alpha[j];
	add serrofs,ax
	adc serrseg,dx
	inc cl
	cmp cl,12
	jb NxtJ
;iprime = (int)(*src) + (int)(sumerr/SUMALPHA);
	mov dx,serrseg	;DX:AX = sumerr
	mov ax,serrofs
	mov bx,SUMALPHA
	or dx,dx		;Is dividend msw == 0?
	jz ItsZro		; Yes, overflow is not possible
;Overflow is possible, call ldiv with dividend in DX:AX and divisor in BX
	call ldiv		;Divide signed long by unsigned int
	jmp short Cnt
ItsZro:	idiv bx
Cnt:	mov dx,ax	;DX = (int)(sumerr/SUMALPHA);
	push ds
	mov ds,srcseg
	lodsb		;*src++
	pop ds
	xor ah,ah
	add ax,dx	;AX = iprime = (int)(*src) + (int)(sumerr/SUMALPHA);
;if(iprime > 128) {
	cmp ax,128
	jle CPipr
	sub ax,255	;iprime -= 255;
	or ch,1		;pbyt |= 1;	/* Set least significant bit */
;bigE[pos] = iprime; /* Update BigE array */
CPipr:	mov bx,pos
	shl bx,1	;Make BX an index into bigE array
	mov es:[di+bx],ax
	inc pos		;Move over one pixel, pos++
	jmp short CPTop
;return(pbyt);
CPdone:	mov al,ch	;CH = pbyt
	xor ah,ah
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_calc_pbyt_  endp

;int calc_pbyt_(int *bigE, int pos, UCHAR *src) {
;   int j, pbyt, iprime, kk=8;
;   long sumerr;	/* sumerr can probably be an int */
;   static char alpha[] = {	/* Multiplied by error array */
;      1, 3, 5, 3, 1, 3, 5, 7, 5, 3, 5, 7, 0, 0, 0};
;#define  SUMALPHA  (1+3+5+3+1+3+5+7+5+3+5+7+0+0+0) /* 48 */
;   while(kk--) {
;      sumerr = 0;
;      pbyt <<= 1;
;      for(j=0; j<12; j++)	/* Calc sumerr */
;         sumerr +=  bigE[Errindx[j] + pos]  * (int)alpha[j];
;      iprime = (int)(*src) + (int)(sumerr/SUMALPHA);
;      if(iprime > 128) {
;         iprime -= 255;
;         pbyt |= 1;
;         }
;      bigE[pos] = iprime; /* Update BigE array */
;      src++;
;      pos++;		  /* Move over one pixel */
;      }
;   return(pbyt);
;}

;Divide signed long int by an unsigned int. On entry dividend is in DX:AX 
; and divisor is in BX. Return with quotient in DX:AX. Uses AX, BX, DX.
; Used to ensure there will be no division overflow.
ldiv  proc  near
	push cx
;Check sign of dividend
	or dx,dx
	pushf			;Save sign on stack
	jns DivPos		;If no sign in dividend, jump to Lab1
;Dividend is negative
	neg dx			;Calc 2s complement of dividend
	neg ax
	sbb dx,0		;Dividend = DX:AX
DivPos:	push ax			;Save dividend offset
	mov ax,dx
	xor dx,dx
	div bx			;Dividend seg / divisor
	mov cx,ax		;Quotient -> CX
	pop ax			;AX = dividend offset
	div bx			;Rem:dividend ofs / divisor
	mov dx,cx		;Quotient = DX:AX
	popf			;Was dividend negative?
	jns NotNeg		;No, jump
	neg dx			;Calc 2s complement of quotient
	neg ax
	sbb dx,0
NotNeg:	pop cx
	ret
ldiv  endp

;Calc 15 indices used to fetch values from BigE array:
;| -2*cols-2 -2*cols-1 -2*cols -2*cols+1 -2*cols+2 |
;| -1*cols-2 -1*cols-1 -1*cols -1*cols+1 -1*cols+2 |
;|        -2        -1       0  not used  not used |
; Usage: void init_errindx_(int cols);
imwidth	equ word ptr @ab[bp]	;Image width
	public _init_errindx_
_init_errindx_  proc  far
	push bp        
	mov bp,sp     
	push di
	cld
	mov ax,ds
	mov es,ax
	mov di,offset errindx	;ES:DI -> errindx
	mov cx,-2		;CL = yy = -2
IEtop:	mov bx,-2		;BL = xx = -2
NxtCol:	mov ax,imwidth
	imul cx
	add ax,bx		;AX = yy*iwidth + xx;
	stosw
	inc bx
	cmp bx,2
	jle NxtCol
	inc cx
	jle IEtop
	pop di
	pop bp
	ret
_init_errindx_  endp

;void init_errindx_(int iwidth) {
;   int j=0, xx, yy;
;   for(yy=-2; yy<=0; yy++) {
;      for(xx=-2; xx<=2; xx++)
;         Errindx[j++] = yy*iwidth + xx;
;      }
;}

;Calculate the byte to display
; Usage: int calc_dbyt_(UCHAR *matrix, int row, UCHAR *src)
matrix	equ dword ptr @ab[bp]	;Matrix to use
drow	equ  word ptr @ab[bp+4]	;Starting point in matrix
dsrc	equ dword ptr @ab[bp+6]	;Where to get the data
	public _calc_dbyt_
_calc_dbyt_  proc  far
	push bp        
	mov bp,sp     
	push di
	push si
	cld
	push ds
;rowptr = &matrix[(rows & 7) * 8]; /* Assumes 8 elements per row */
	lds si,matrix
	mov ax,drow		;AX = image row
	and ax,7
	mov cl,3
	shl ax,cl		;AX = (rows & 7) * 8
	add si,ax		;DS:SI -> classic[row]
	les di,dsrc		;ES:DI -> src
;for(k=0; k<8; k++) {
	mov cx,8
;pbyt <<= 1;
CDTop:	shl dl,1		;Pbyt = DL
	lodsb			;AL = *rptr, rptr++
	cmp al,es:[di]		;Compare to *src
;Jump if(*rptr >= *src)
	jae CDge
	or dl,1			;*rptr < *src, so or pbyt with 1
CDge:	inc di			;src++
	loop CDTop
;return(pbyt)
	mov al,dl
	xor ah,ah
	pop ds
	pop si
	pop di
	pop bp
	ret
_calc_dbyt_  endp

;int calc_dbyt_(UCHAR *matrix, int row, UCHAR *src) {
;   int pbyt, k;
;   /* Point at row of matrix to use */
;   UCHAR *rowptr=&matrix[(rows & 7)<<3];
;   for(k=0; k<8; k++) {
;      pbyt <<= 1;
;      if(*rptr++ < *src++)
;         pbyt |= 1;
;      }
;   return(pbyt);
;}

;Erase 1 screen row (EGA mode)
; Usage: void erase1row_(UCHAR far *scrpos, int cols, int bckcol)
escrpos    equ dword ptr @ab[bp]
ecols      equ  word ptr @ab[bp+4]
bckcol     equ  byte ptr @ab[bp+6]	;Background color
	public _erase_1row_
_erase_1row_  proc  far
	push bp
	mov bp,sp
	push di
;outp(0x3ce, 3); outp(0x3cf, REPLACE);	/* Data Rot/Fct Select reg */
	mov dx,GDC_INDEX
	mov al,GDC_ROTATE
	out dx,al
	inc dl
	mov al,REPLACE
	out dx,al
;outp(0x3ce, 0); outp(0x3cf, bckcol);	/* Set/reset reg gets bckcol */
	dec dl
 	mov al,GDC_SET_RESET
	out dx,al
	inc dl
	mov al,bckcol
	out dx,al
;outp(0x3ce, 8); outp(0x3cf, 0xff);	/* Enable all bits */
	dec dl
 	mov al,GDC_BIT_MASK
	out dx,al
	inc dl
	mov al,0ffh
	out dx,al
;ES:DI -> starting address
	les di,escrpos
	mov cx,ecols
	shr cx,1
;Zero the row
	repz stosw	;Zero the bytes
	adc cx,cx	;Pick up carry
	repz stosb
	pop di
	pop bp
	ret
_erase_1row_  endp

;void erase1rows(UCHAR far *scrpos, int cols)
;{
;   outp(0x3ce, 3); outp(0x3cf, REPLACE); /* Data Rot/Fct Select reg */
;   outp(0x3ce, 0); outp(0x3cf, bckcol);  /* Set/reset reg gets bckcol */
;   outp(0x3ce, 8); outp(0x3cf, 0xff);	/* Enable all bits */
;   while(cols--)
;      *scrpos++ &= 1;
;}

	END

+ARCHIVE+ writegif.asm 40216  1/26/1991  7:25:02
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;		Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314) 962-7833
;   Contents:
;	ReadGif_
;	  DecompressGIF, ReadGCode, WriteGChar, PutRow, InitPassx,
;         CalcLaceLine, InitLZWvars, InitGVars, rindex, PutCode
;	WriteGif_
;	  CompressGIF, GetGChar, GetNextRow, CheckDTA, WriteGCode, 
;	  InitGTable, lookup_code, windex, AddCode

	.MODEL	LARGE
@ab	  equ	  6	;Equate for large model

MAXBUFSIZ equ 8192	;Maximum buffer size
NO_ERROR  equ	 0
BAD_DSK	  equ	-3
;DOS fctns
READSQ	  equ	3fh
WRTSQ	  equ	40h
;For the GIF format, we must add the byte count (0-255) to the front of
; a data packet. A packet is dispatched when bytes = GIFSIZE
GIFSIZE	  equ	254
GCODSIZ   equ    8	;For WriteGif_ only

	extrn _Hbuff_:BYTE	;General purpose buffer (see viccore.c)
	extrn _emgetrow:far
	extrn _xmgetrow:far
	extrn _cmgetrow_:far
	extrn _emputrow:far
	extrn _xmputrow:far
	extrn _cmputrow_:far
	extrn _cmopen_:far
	extrn _cmclose_:far

;Image descriptor
imgdes_  struc
ibuff	  DD  ?	;Image buffer address in conventional memory
ehandle	  DW  ?	;Expanded memory handle
xhandle	  DW  ?	;Extended memory handle
stx	  DW  ?	;Image area to be processed
sty	  DW  ?
endx	  DW  ?
endy	  DW  ?			
iwidth	  DW  ?	;Image width
ilength	  DW  ?	;Image length
palette   DD  ?	;Address of palette associated with the image
palsize   DW  ? ;Palette size in bytes
reserved  DW  ? ;Reserved
imgdes_  ends

;GIF file format info structure definition (used by gifinfo)
gifdes_  struc
gfwidth	    DW  ?	;File header image width
gflength    DW  ?	;Image length
BitsColRes  DW  ?	;Bits of color resolution
BitsPPixel  DW  ?	;Bits per pixel
BckCol	    DW  ?	;Background color
gflaceflag  DW  ?	;Interlace flag
gfcodsiz    DW  ?	;Code size
gifdes_  ends

;Hash table entry for ReadGif_ (so string table = 4096*3 bytes long)
hash_read  struc
nxt	DW	?    ;Next entry along chain
chr	DB	?    ;Suffix char
hash_read  ends

	.CODE
;Load GIF image from disk file to image buffer. Returns NO_ERROR, EMM_ERR,
; XMM_ERR, or CM_ERR. Usage: errcode = ReadGif_(imgdes *desimg, int handle,
; UCHAR *exbuff, UCHAR *strtab, int cols, int rows, struct GIfData gdat);
desimg      equ dword ptr @ab[bp]	;Destination image structure
rhandle	    equ  word ptr @ab[bp+4]	;File handle
xbuffofs    equ		  @ab[bp+6]	;Destination for data
xbuffseg    equ		  @ab[bp+8]
RdStrTbofs  equ  word ptr @ab[bp+10]	;4096*3 = 12288 bytes of memory 
RdStrTbseg  equ  word ptr @ab[bp+12]	; to use for LZW cmpd file
rcols	    equ  word ptr @ab[bp+14]	;Columns of image buffer to fill
rrows	    equ  word ptr @ab[bp+16]	;Rows of image buffer to fill
gifst       equ dword ptr @ab[bp+18]	;GIF image structure

free_code   equ  word ptr [bp-2]	;Next code to use
hash_seg    equ  word ptr [bp-4]	;Hash table segment address
cur_code    equ  word ptr [bp-6]
max_code    equ  word ptr [bp-8]
nbits	    equ  word ptr [bp-10]	;Code size (9-12)
bit_offset  equ  word ptr [bp-12]
BPRow	    equ  word ptr [bp-14]	;Bytes in a row
stack_count equ  word ptr [bp-16]
k	    equ  byte ptr [bp-18]
old_code    equ  word ptr [bp-20]
fin_char    equ  byte ptr [bp-22]
ercode	    equ  word ptr [bp-24]
buffseg     equ           [bp-26]	;DTA address
buffofs     equ           [bp-28]
codesize    equ  byte ptr [bp-30]	;code size = 4 - 8
clear_code  equ  word ptr [bp-32]	; = 1 << codesize = reinit the string table
eoi_code    equ  word ptr [bp-34]	; = clear_code + 1 = end of file marker
first_free  equ  word ptr [bp-36]	; = clear_code + 2 = First free code
codemax     equ  word ptr [bp-38]	; = 1 << (codesize+1)
desofs      equ  word ptr [bp-40]	;Position in exbuff
DtaSiz0     equ  word ptr [bp-42]	;Size of file buffer to use
DtaSiz2     equ  word ptr [bp-44]	;DtaSize - 2 (used by ReadCode)
byts_rd     equ  word ptr [bp-46]	;Bytes actually read from file
xs_byts     equ  word ptr [bp-48]	;Unprocessed valid bytes left in DTA
rowno       equ  word ptr [bp-50]	;Row no. of image data to read
maxyval     equ  word ptr [bp-52]	;Maximum no. of rows to write
gwidth      equ  word ptr [bp-54]	;Image width based on header info
in_code	    equ  word ptr [bp-56]	; shouldn't be written
laceflg     equ  byte ptr [bp-58]	;1 => interlace mode is used
pass2       equ  word ptr [bp-60]	;For interlaced GIF images,
pass3       equ  word ptr [bp-62]	; first rowno for corresponding
pass4       equ  word ptr [bp-64]	; interlace pass
putroutseg  equ           [bp-66]	;putrow() routine to use
putroutofs  equ           [bp-68]
puthandle   equ  word ptr [bp-70]	;Handle to use for putrow()

	public _ReadGif_
_ReadGif_  proc  far
	push bp        
	mov bp,sp     
	sub sp,70
	push di
	push si
	cld
;Init some variables
	mov ercode,NO_ERROR	;Assume no errors
	mov rowno,0
	mov ax,rrows
	mov maxyval,ax		;Init maximum no. of rows to write
;Read image data using LZW decompression. On entry DS -> DTA buffer seg.
	mov bx,RdStrTbofs  ;Set up hash table. We need 4096*3 (12288 bytes),
	mov cl,4	   ; so we allocated 4096*3+16 bytes in loadgif prog
	shr bx,cl	   ;BX = ofs/16
	add bx,RdStrTbseg  ;BX = Seg = Seg + ofs/16 = Seg:0
	inc bx		   ;Make sure BX is within border
	mov hash_seg,bx    ;Save hash segment address
;Store Hbuff_ addr so it's accesible thru SS
	mov si,offset _Hbuff_
	mov buffofs,si
	mov ax,seg _Hbuff_
	mov buffseg,ax
	push ds	      	;Save our DS
;Initialize putroutine() and puthandle
	lds di,desimg	;DS:DI -> desimg structure
	mov ax,[di]
	or ax,[di+2]	;Is desimg->ibuff 0?
	jz RGTryEM	;If yes, jump to test for buffer in EM
;desimg->ibuff != NULL, open cmhandle
;if((rcode=cmopen_(handle_addr, image->ibuff)) == NO_ERROR)
	push [di+2]	;Push ibuff seg, offset
	push [di]
	push ss		;Push handle seg, offset
	lea ax,puthandle
	push ax
	call _cmopen_	;Get CM handle
	add sp,8
	cmp ax,NO_ERROR
	jne RGCMBd
	mov cx,puthandle ;CX gets handle
	mov ax,offset _cmputrow_
	mov dx,seg _cmputrow_
	jmp short RGStPR
RGCMBd:	mov ercode,ax
	jmp short RGPBy2
;Test for buffer in EM
RGTryEM: mov cx,[di].ehandle
	or cx,cx	;Is desimg->ehandle == 0?
	jz RGinXM	;If yes, jump. Buffer must be in XM
	mov ax,offset _emputrow
	mov dx,seg _emputrow
	jmp short RGStPR
;ibuff and ehandle = 0, des must be in XM
RGinXM:	mov cx,[di].xhandle
	mov ax,offset _xmputrow
	mov dx,seg _xmputrow
RGStPR:	mov puthandle,cx
	mov putroutofs,ax
	mov putroutseg,dx
;Initialize laceflg, codsiz, gwidth, glength
	lds di,gifst	   ;DS:DI -> GIF image structure
;Initialize gwidth and bytes per row
	mov ax,[di].gfwidth
	mov gwidth,ax
	mov BPRow,ax	;Initialize bytes per row
;Initialize interlace flag
	mov al,byte ptr [di].gflaceflag
	mov laceflg,al
	or al,al	;Check for interlace mode
	jz NoIace
;Calc pass2, pass3, pass4 for interlaced GIF pics
	mov dx,[di].gflength
	call InitPassx	;Initialize local variables for interlaced GIF pics
NoIace:	mov cl,byte ptr [di].gfcodsiz ;CL gets code size to use
;Point DS at DTA buffer seg
	mov ds,buffseg		;DS -> Hbuff_ (DTA)
	call InitLZWvars   ;Initialize LZW constants
	les di,dword ptr xbuffofs ;ES:DI -> exbuff
	mov desofs,di	   ;Initialize desofs
	call DecompressGIF ;Decompress the data
;Close CM handle, if issued: void cmclose_(imgdes *image, int cmhandle);
RGPBye:	push puthandle
	lds di,desimg	;DS:DI -> dest image structure
	push ds		;Push desimg seg, offset
	push di
	call _cmclose_	;Close CM handle
	add sp,6
RGPBy2:	mov ax,ercode
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_ReadGif_  endp

	EVEN
OurSP	    DW  ?	;Save our stack pointer here

;Decompress a GIF file. On entry DS = DTA seg and ES = exbuff seg
DecompressGIF  proc  near
	mov cs:OurSP,sp		;Save SP for a possible unscheduled exit
	call InitGVars		;Initialize variables
	xor ax,ax
	mov bit_offset,ax
	mov stack_count,ax
	mov DtaSiz2,ax		;This is to force a file read
	mov DtaSiz0,ax
	mov xs_byts,ax
DGTop:	call ReadGCode		;Get a code
	cmp ax,eoi_code		;End of file?
	je DGEOI		;yes
	cmp ax,clear_code	;Clear code?
	jne DGNoClr		;no
	call InitGVars		;Initialize table
	call ReadGCode		;Read next code
	mov cur_code,ax		;Initialize variables
	mov old_code,ax
	mov k,al
	mov fin_char,al
	call WriteGChar		;Write a char in the image buffer
	jc DGNoMoData		;CF = 1 => Data all written
	jmp short DGTop		;Get next code
DGNoClr:
	mov cur_code,ax		;Save new code
	mov in_code,ax
	mov es,hash_seg		;ES -> hash table
	cmp ax,free_code	;Code in table? (k<w>k<w>k)
	jl DGCodeInTable	;Yes
	mov ax,old_code		;Get previous code
	mov cur_code,ax		;Make current
	mov al,fin_char		;Get old last char
	push ax			;Save it
	inc stack_count
DGCodeInTable:
	mov bx,clear_code
	cmp cur_code,bx		;Code or character?
	jl DGChar		;Char
	mov bx,cur_code		;Convert code to address
	call rindex		;Convert code in BX to an address in BX
	mov al,es:[bx+2]	;Get suffix char
	push ax			;Save it
	inc stack_count
	mov ax,es:[bx]		;Get prefix code
	mov cur_code,ax		;Save it
	jmp short DGCodeInTable	;Translate again
DGNoMoData:			;No more room for data
	mov sp,cs:OurSP		;Restore our SP for exit
DGEOI:	ret
DGChar:	mov es,xbuffseg		;ES -> exbuff seg
	mov ax,cur_code		;Get code
	mov fin_char,al		;Save as final char and k
	mov k,al
	push ax			;Push it
	inc stack_count
	mov cx,stack_count	;Pop stack
	jcxz DGStkMt		;If anything's there
DGWrt:	pop ax
	call WriteGChar
	jc DGNoMoData		;CF = 1 => Data all written
	loop DGWrt
DGStkMt:
	mov stack_count,cx	;Clear count on stack
	call PutCode		;Add new code to table
	mov ax,in_code		;Save input code
	mov old_code,ax
	mov bx,free_code	;At table limit?
	cmp bx,max_code
	jl DGNotMax		;Less means no
	cmp nbits,12		;Still within twelve bits?
	je DGNotMax		;No (next code should be clear)
	inc nbits		;Yes, increase code size
	shl max_code,1		;Double max code
DGNotMax:
	jmp DGTop		;Get the next code
DecompressGIF  endp

BASECODESIZE  equ  4

	EVEN
;Used by ReadGCode, for code size 4 - 11
gmasks	dw    0000000000011111b, 0000000000111111b, 0000000001111111b
	dw    0000000011111111b, 0000000111111111b, 0000001111111111b
	dw    0000011111111111b, 0000111111111111b

;Return a code (word) from the file in AX. Uses AX, BX, CX, DX, DI, and SI.
; On entry DS = DTA seg, ES = exbuff seg.
ReadGCode  proc  near
	mov ax,bit_offset	;Get bit offset
	add ax,nbits		;Adjust by code size
	xchg bit_offset,ax	;bit_offset += nbits (for next time)
;Calculate byte offset and offset in byte
	mov cx,ax
	and cx,7		;CX = offset in byte
	shr ax,1
	shr ax,1
	shr ax,1		;AX = byte ofset
	mov si,buffofs		;Point SI to beginning of DTA
	cmp ax,DtaSiz2	 	;DtaSize-2, Approaching end of buffer?
	jge RGBufMT		;Jump if we're approaching the end of it
;Still bytes to read in the input buffer
RGBufOK: add si,ax		;Add in byte offset. SI -> bytes to read in
	lodsw			;Get first 2 bytes
	mov bx,ax		;Save them in BX
	lodsb			;Get 3rd byte, CX = offset in byte (0-7)
	jcxz RGSkp		;If zero, skip shifts
RGRot:	shr al,1		;Else put code in low (code size) bits of BX
	rcr bx,1
	loop RGRot
RGSkp:	mov ax,bx		;Put code to return in ax
	mov bx,nbits		;Mask off unwanted bits
	sub bx,BASECODESIZE+1	;Subtract base code size
	shl bx,1
	and ax,cs:gmasks[bx]	;AX = masks[(nbits-5)*2]
	ret
;Near the end of the input buffer, get some more bytes
; On entry, AX = byte offset, CX = offset in byte, SI -> start of DTA
RGBufMT: push cx 	;Save offset in byte
	push si
	push es
	mov dx,ds
	mov es,dx	;ES = DS = DTA segment
	add cx,nbits	;Calculate a new bit offset and save it
	mov bit_offset,cx
;Calc byts_left (valid bytes not processed between DtaSiz2 and DtaSiz0)
	mov dx,DtaSiz0
	sub dx,ax	;DX = byts_left = DtaSize - byte_ofs
;Move valid bytes still left in the DTA to the front of the DTA
	mov cx,dx	;CX = bytes to move = byts_left + xs_byts
	add cx,xs_byts
	mov bx,cx	;BX = byts2mov = byts_left + xs_byts
	mov di,si	;SI = DI -> Start of DTA = dest addr
	add si,ax	;AX = byte offset, SI -> 1st byte to move
	rep movsb	;Move the bytes
;Fill rest of DTA: read(handle, dta+byts2mov, MAXBUFSIZ-byts2mov);
	mov cx,MAXBUFSIZ
	sub cx,bx	;CX = bytes to load = MAXBUFSIZ - byts2mov
	push bx		;Save byts2mov
	push dx		;Save byts_left
	mov dx,di	;DS:DX -> Position in DTA to store new data
	mov bx,rhandle
	mov ah,READSQ	;Read in the bytes. May not use all of them
	int 21h
	pop dx		;Restore byts_left
	pop bx		;BX = byts2mov
	mov byts_rd,ax	;AX = bytes successfully read
	add ax,bx	;AX = byts_rd, BX = byts2mov
	or ax,ax	;AX = valid_byts = byts_rd + byts2mov
	jz RGEOI	;If no valid bytes are left, we're done
;Go thru DTA and strip out the byte counts (i.e, pack data)
	mov di,buffofs	;Point DI to beginning of DTA
	add di,dx	;DI -> first count byte in DTA (DX = byts_left)
	mov si,di	;DI = SI -> Position in DTA to store packed data
;Calc last byte of valid data in the DTA, store offset in DX
; ofs = valid_byts + buffofs
	add ax,buffofs	;AX = valid_byts
	mov dx,ax	;DX = valid_byts + buffofs
	xor ch,ch
RGTop:	mov cl,[si]	;CX gets byte count
	inc si		;SI = source addr -> 1st byte of data in packet
	mov ax,cx
	add ax,si	;AX -> offset of next count byte
	cmp ax,dx	;Still reading new data? (DX = end of valid data in DTA)
	jge RGNoRm	;Jump if next count byte is outside valid DTA data
	or cx,cx	;If byte count = 0, we're done
	jz RGSrPak
;Count byte is valid DTA data, pack the data to remove byte counts
	rep movsb	;SI -> source addr, DI -> dest addr
	jmp short RGTop
;Count byte is beyond valid DTA data
; Is the DTA buffer full? (the normal condition)
RGNoRm:	mov ax,MAXBUFSIZ
	sub ax,bx	;BX = byts2mov
	cmp ax,byts_rd	;Did we read the bytes requested?
	jg RGSrPak	;Jump if no
;As many bytes as requested were read => more data on disk to read.
; Set xs_byts for next time the buffer is empty and move any
; valid data left in buffer to immediately follow packed data
	mov cx,dx	;CX = DX = valid_byts + buffofs
	sub cx,si	;SI -> byte beyond count byte
	inc cx		;xs_byts = valid_byts - (SI - buffofs) + 1
	mov xs_byts,cx	;xs_byts = valid_byts + buffofs + 1 - SI
	mov ax,di	;DI = 1 byte beyond valid, packed data
	sub ax,buffofs	;AX = no. of valid bytes in DTA
	mov DtaSiz0,ax
	cmp ax,2	;DtaSiz2 = (DtaSize>=2) ? (DtaSize-2) : DtaSize;
	jl RGLt2	;Guard against a negative DtaSiz2
	dec ax
	dec ax
RGLt2:	mov DtaSiz2,ax
;Move any valid data bytes left in the DTA to immediately follow packed data
	dec si		;Point SI at a count byte
	rep movsb	;CX = xs_byts
RGByy:	xor ax,ax	;Set byte offset = 0
	pop es
	pop si
	pop cx		;Restore offset in byte
	jmp RGBufOK
;Fewer bytes read than requested => there's no data left on disk to process
RGSrPak: mov xs_byts,0	;Invalidate bytes remaining in DTA
	mov ax,di	;DI = 1 byte beyond valid, packed data
	sub ax,buffofs	;AX = no. of valid bytes in DTA
	sub ax,2	;DtaSize = k-2;
	jl RGEOI	;We're done if DI -> start of DTA
	mov DtaSiz0,ax
	mov DtaSiz2,ax
	jmp short RGByy
RGEOI:	mov ax,eoi_code
	pop es
	add sp,4	;Fix SP
	ret
ReadGCode  endp

;int ReadGCode(void)
;{
;   int byts_rd;	/* Bytes read from file */
;   int byts_left;	/* = DtaSiz0 - byte_offset */
;   int valid_bytes;	/* = byts_rd + byts_left + xs_byts */
;   UCHAR *dta;
;
;   dta = _Hbuff;
;   byte_ofs = bit_ofs/8	/* Calc byte offset */
;   ofs_inbyte = bit_ofs & 7;	/* Calc offset in byte */
;   bit_ofs += nbits;		/* Calc new bit offset */
;   if(byte_ofs < DtaSiz2) {	/* Still bytes to get from DTA */
;rdem: dta += byte_ofs;
;      ch12 = *(int *)dta;
;      ch3  = *(dta+2);
;      while(ofs_inbyte--) {
;         ch3  shr 1;
;         ch12 rcr 1;
;         }
;      ch1 &= masks[(nbits-5)*2]; /* Mask off unused bits */
;      return(ch1);
;      }
;   else {	/* Nearing end of DTA */
;      bit_ofs = ofs_inbyte + nbits;
;      byts_left = DtaSize - byte_ofs;	/* 0-2 bytes */
;      byts2mov = byts_left + Xs_byts;
;      /* Move valid bytes remaining in the DTA to the front of the DTA */
;      memcpy(dta, dta+byte_ofs, byts2mov);
;      /* Fill the rest of the DTA */
;      byts_rd = read(handle, dta+byts2mov, MAXBUFSIZ-byts2mov);
;      /* Calc no. of bytes of valid data in the DTA */
;      valid_bytes = byts2mov + byts_rd;
;      if(valid_bytes==0)
;         return(EOI);
;      /* Go thru DTA and strip out the byte counts (i.e, pack data) */
;      j = bytes_left;	/* j is source index in DTA */
;      k = bytes_left;	/* k is dest index in DTA */
;      for(;;) {
;         /* Remove byte counts from DTA */
;         bytct = dta[j++];
;         if(j+bytct >= valid_bytes) {
;            /* Count byte is beyond valid DTA data */
;            if(byts_rd < MAXBUFSIZ-byts2mov)
;   /* Fewer bytes read than requested => no data left on disk to process */
;               goto bct0;
;            /* (Normal condition) -- As many bytes requested were read =>
;		more data on disk. Set Xs_byts for next time the buffer is
;		empty and move any valid data left in buffer to immediately
;		follow packed data */
;            Xs_byts = MAXBUFSIZ - j + 1;
;            memcpy(&dta[k], &dta[j-1], Xs_byts);  /* Move the bytes */
;            DtaSize = k;	/* Set DTA size */
;            DtaSiz2 = (k>=2) ? (k-2) : k;
;            break;
;            }
;         if(bytct==0) {
;            /* Count byte=0 => all data's in the DTA, so set Xs_byts to
;               ignore extra data and DtaSizX to process all the data. */
;bct0:	     Xs_byts = 0;	/* Invalidate bytes remaining in DTA */
;            if(k<=2)
;               return(EOI);
;            DtaSize = k-2;	/* Set DTA size to process all the data */
;            DtaSiz2 = DtaSize;
;            break;
;            }
;         /* Count byte is within DTA, pack data to remove byte count */
;         memcpy(&dta[k], &dta[j], bytct);
;         j += bytct;	/* Point at next count byte */
;         k += bytct;	/* Point at next dest byte */
;         }
;     byte_ofs = 0;
;     goto rdem:
;     }
;}

;Write a byte to the image buffer. If out of room, ret with CF = 1. On entry,
; AL = byte to write, DS = DTA buffer seg, ES = exbuff seg.
WriteGChar  proc  near
;Write the character first
	mov di,desofs	;DI = where to store cahr in exbuff
	inc desofs	;Inc desofs for next access
	stosb
;Check to see if we're done with a row
	dec BPRow	;--BPRow
	jle WNewRw  	;Jump if yes
WGCby:	clc	    	;Success
	ret
;Done with a row -- store the row in CM or EM
WNewRw:	mov dx,rowno	;DX = rowno
	cmp laceflg,0	;Check for interlace mode
	jz NoILac
;Calc line no. of next line to fill in image buffer (returned in DX)
	call CalcLaceLine
NoILac:	cmp dx,maxyval	;DX = yval = next row in buffer to fill (-starty)
	jge NoWrRw	;Compare row in buffer (yval) with last row to fill
;Copy the row in exbuff into CM or EM
	call PutRow
	cmp ax,NO_ERROR	;Only an EMM error is possible here
	jne WGEmEr
	dec rrows	;Dec rows only if yval < maxyval
	jle WGODat	;Exit if no more rows to do
;Inc rowno, reset ES:desofs to exbuff, and BPRows to gwidth
NoWrRw:	inc rowno	;rowno++
	les di,dword ptr xbuffofs
	mov desofs,di
	mov ax,gwidth	;Reset BPRows
	mov BPRow,ax
	jmp short WGCby
WGEmEr:	mov ercode,ax	;Store return error code and exit as if we're done
WGODat:	stc		;Out of space to store data
	ret
WriteGChar  endp

;WriteGChar(int ch) {
;   *des++ = ch;
;   if(--BPRow <= 0) {	/* Done with a row? */
;      /* Yes, calc where to store the row */
;      yval = (!laceflg) ? rowno : CalcLaceLine(rowno);
;      /* If within the image area, move the row to CM or EM */
;      if(yval<maxyval) {
;         if(errcode=PutRow(yval)) != NO_ERROR)
;            return(errcode);
;         /* Are we done with the image area? */
;         if(--rrow <= 0)
;            return(-1);	/* Yes, return -1 */
;         }
;      rowno++;		/* Inc row ctr */
;      des = exbuff;    /* Reset des */
;      BPRow = giwidth;	/* Reset bytes per row */
;      }
;   return(0);
;   }

;Store the row of data just processed into CM, EM, or XM. On entry, DX = yval 
; (next row in buffer to fill). Returns NO_ERROR, EMM_ERR, XMM_ERR, or CM_ERR.
; Uses ES, DI, AX, BX, DX. Preserves DS, SI, CX.
PutRow  proc  near
	push cx		;Save stack_count
	push ds		;Save DTA seg
	lds di,desimg	;DS:DI -> desimg.ibuff
;DX = where to put the row = yval + desimg->sty
	add dx,[di].sty ;DX = yval + desimg->sty
;Write row: putrow(exbuff, dhandle, desimg->stx, yval, cols, desimg->iwidth);
	push [di].iwidth
	push rcols
	push dx		;DX = yval + desimg->sty
	push [di].stx
	push puthandle
	push xbuffseg	;Push source seg, offset
	push xbuffofs
	call dword ptr putroutofs	;Any error code is returned in AX
	add sp,14
	pop ds		;Restore DTA seg
	pop cx		;Restore stack_count
	ret
PutRow  endp

;int PutRow(int yval)
;{
;   int rcode;
;   rcode = putrow(exbuff, dhandle, desimg->stx,
;      (yval+desimg.sty), cols, desimg->iwidth);
;   return(rcode);
;}

;Initialize local variables pass2, pass3, pass4 for interlaced GIF pics.
; On enrty, DX = gflength. Uses AX, BX.
InitPassx  proc  near
	mov ax,1	;AX = rowno = 1;
	mov bx,8	;BX = yval = 8;
IP1:	cmp bx,dx
	jae IPNt1	;   while(yval < glength) {
	inc ax		;      rowno++;
	add bx,8	;      yval += 8;
	jmp short IP1	;      }
IPNt1:	mov pass2,ax	;   pass2 = rowno;
	mov bx,4
IP2:	cmp bx,dx	;BX = yval = 4;
	jae IPNt2	;   while(yval < glength) {
	inc ax		;      rowno++;
	add bx,8	;      yval += 8;
	jmp short IP2	;      }
IPNt2:	mov pass3,ax	;   pass3 = rowno;
	mov bx,2
IP3:	cmp bx,dx	;BX = yval = 4;
	jae IPNt3	;   while(yval < rows) {
	inc ax		;      rowno++;
	add bx,4	;      yval += 8;
	jmp short IP3	;      }
IPNt3:	mov pass4,ax	;   pass3 = rowno;
	ret
InitPassx  endp

;InitPassx(int rows)
;{
;   rowno = 1;
;   yval = 8;
;   while(yval < glength) {
;      rowno++;
;      yval += 8;
;      }
;   pass2 = rowno;
;   yval = 4;
;   while(yval < glength) {
;      rowno++;
;      yval += 8;
;      }
;   pass3 = rowno;
;   yval = 2;
;   while(yval < glength) {
;      rowno++;
;      yval += 4;
;      }
;   pass4 = rowno;
;}

;Calculate line number of next line to fill in image buffer. For interlaced
; GIF pic. On entry, DX = line no. we're processing. Return with DX = line
; no. in image buffer to fill. Uses DX.
CalcLaceLine  proc  near
	cmp dx,pass2
	jae CLNt1	;if(rowno < pass2)
;Pass 1: yval = rowno*8;
	shl dx,1
	shl dx,1
	shl dx,1
	jmp short CLby
CLNt1:	cmp dx,pass3	;else if(rowno < pass3)
	jae CLNt2
;Pass 2: yval = (rowno-pass2)*8 + 4;
	sub dx,pass2
	shl dx,1
	shl dx,1
	shl dx,1
	add dx,4
	jmp short CLby
CLNt2:	cmp dx,pass4	;else if(rowno < pass4)
	jae CLNt3
;Pass 3: yval = (rowno-pass3)*4 + 2;
	sub dx,pass3
	shl dx,1
	shl dx,1
	inc dx
	inc dx
	jmp short CLby
;Must be pass 4: yval = (rowno-pass4)*2 + 1;
CLNt3:	sub dx,pass4
	shl dx,1
	inc dx
CLby:	ret
CalcLaceLine  endp

;/* Calc line no. of next line to fill in image buf */
;CalcLaceLine(int rowno)
;{
;   if(rowno < pass2)		/* Pass 1 */
;      yval = rowno*8;
;   else if(rowno < pass3)	/* Pass 2 */
;      yval = (rowno-pass2)*8 + 4;
;   else if(rowno < pass4)	/* Pass 3 */
;      yval = (rowno-pass3)*4 + 2;
;   else			/* rowno > pass3 => Pass 4 */
;      yval = (rowno-pass4)*2 + 1;
;}

;Initialize LZW constants. Enter with code size in CL. Uses CL, AX.
InitLZWvars  proc  near
	mov codesize,cl
	mov ax,1
	shl ax,cl
	mov bx,ax
	shl bx,1
	mov codemax,bx	   ;codemax = 1 << (codesize+1)
	mov clear_code,ax  ;clear_code = 1 << codesize
	inc ax
	mov eoi_code,ax	   ;eoi_code = clear_code + 1
	inc ax
	mov first_free,ax  ;first_free = clear_code + 2
	ret
InitLZWvars  endp

;Initialize some LZW variables. Uses AX.
InitGVars  proc  near
	xor ah,ah
	mov al,codesize
	inc ax
	mov nbits,ax
	mov ax,codemax
	mov max_code,ax
	mov ax,first_free
	mov free_code,ax
	ret
InitGVars  endp

;Convert code in BX to address in BX. Uses BX, CX
rindex  proc  near
	mov cx,bx		;bx = bx * 3 (3 byte entries)
	shl bx,1
	add bx,cx
	ret
rindex  endp

;Add new code to table
PutCode  proc  near
	mov bx,free_code	;Get new code
	call rindex		;Convert code in BX to address in BX
	push es			;ES -> hash table
	mov es,hash_seg
	mov al,k		;Get suffix char
	mov es:[bx].chr,al	;Save it
	mov ax,old_code	   	;Get prefix code
	mov es:[bx].nxt,ax	;Save it
	pop es
	inc free_code		;Set next code
	ret
PutCode  endp

;**** Write GIF routines ****

;Hash table entry for WriteGif_ (so string table = 4096*5 bytes long)
hash_write  struc
first	DW	?	;First code with equivalent prefix
next	DW	?	;Next entry along chain
char	DB	?	;Suffix char
hash_write  ends

;Save image data to disk as a GIF file. Returns NO_ERROR, BAD_DSK,
; EMM_ERR, XMM_ERR, or CM_ERR. Usage: errcode = WriteGif_(imgdes *srcimg,
; int handle, UCHAR *exbuff, UCHAR *WtStrTb); Uses Hbuff_ as the output
; buffer. GIF files are different from LZW TIF files in that GIF files 
; aren't broken up into strips and image data is compressed into "packets"
; of data that are preceeded by a byte count (0-255).
srcimg      equ dword ptr @ab[bp]	;Source image structure
whandle	    equ  word ptr @ab[bp+4]	;File handle
xbuffofs    equ		  @ab[bp+6]	;Source of data to save
xbuffseg    equ		  @ab[bp+8]
WtStrTbofs  equ  word ptr @ab[bp+10]	;4096*5 = 20480 bytes of memory 
WtStrTbseg  equ  word ptr @ab[bp+12]	; to use for LZW cmpd file

free_code   equ  word ptr [bp-2]	;Next code to use
hash_seg    equ  word ptr [bp-4]	;Hash table segment address
prefix_code equ  word ptr [bp-6]
max_code    equ  word ptr [bp-8]
nbits	    equ  word ptr [bp-10]	;Code size (9-12)
bit_offset  equ  word ptr [bp-12]
BPRow	    equ  word ptr [bp-14]	;Bytes in a row
stack_count equ  word ptr [bp-16]
k	    equ  byte ptr [bp-18]
old_code    equ  word ptr [bp-20]
fin_char    equ  byte ptr [bp-22]
ercode	    equ  word ptr [bp-24]
buffseg     equ           [bp-26]	;DTA address
buffofs     equ           [bp-28]
codesize    equ  byte ptr [bp-30]	;code size = 4 - 8
clear_code  equ  word ptr [bp-32]	; = 1 << codesize = reinit the string table
eoi_code    equ  word ptr [bp-34]	; = clear_code + 1 = end of file marker
first_free  equ  word ptr [bp-36]	; = clear_code + 2 = First free code
codemax     equ  word ptr [bp-38]	; = 1 << (codesize+1)
srcofs      equ  word ptr [bp-40]	;Position in exbuff
buff_end    equ  word ptr [bp-42]	;Address of the end of the DTA buffer
buff_st     equ  word ptr [bp-44]	;Points at 1st byte of data in packet
wcols	    equ  word ptr [bp-46]	;Columns of image buffer to write
wrows	    equ  word ptr [bp-48]	;Rows of image buffer to write
yctr        equ  word ptr [bp-50]	;Y counter for emgetrow()
getroutseg  equ           [bp-52]	;getrow() routine to use
getroutofs  equ           [bp-54]
gethandle   equ  word ptr [bp-56]	;Handle to use for getrow()

	public _WriteGif_
_WriteGif_  proc  far
	push bp
	mov bp,sp
	sub sp,56
	push di
	push si
	cld
	mov ercode,NO_ERROR	;Assume no errors
;Store Hbuff_ addr so it's accesible thru SS and point ES at buffer seg
	mov ax,seg _Hbuff_
	mov buffseg,ax
	mov es,ax		;ES = DTA segment
	mov bx,offset _Hbuff_
	mov buffofs,bx		;Store buffer's starting address
;Set buff_st and buff_end
	inc bx			;Leave room for count byte at front
	mov buff_st,bx		;Store address of where to store data
	add bx,MAXBUFSIZ-1
	mov buff_end,bx		;Store end of buffer address
	push ds
;rows = srcimg->endy - srcimg->sty + 1
	lds bx,srcimg		;DS:BX -> srcimg
	mov ax,[bx].endy
	mov dx,[bx].sty
	sub ax,dx
	inc ax
	mov wrows,ax
;yctr = srcimg->sty;
	mov yctr,dx
;cols = srcimg->endy - srcimg->sty + 1
	mov ax,[bx].endx
	sub ax,[bx].stx
	inc ax
	mov wcols,ax
;Initialize getroutine() and gethandle
	mov ax,[bx]
	or ax,[bx+2]	;Is srcimg->ibuff 0?
	jz WGTryEM	;If yes, jump to test for buffer in EM
;srcimg->ibuff != NULL, open cmhandle
;if((rcode=cmopen_(handle_addr, image->ibuff)) == NO_ERROR)
	push [bx+2]	;Push ibuff seg, offset
	push [bx]
	push ss		;Push handle seg, offset
	lea ax,gethandle
	push ax
	call _cmopen_	 ;Get CM handle
	add sp,8
	cmp ax,NO_ERROR
	jne WGCMBd
	mov cx,gethandle ;CX gets handle
	mov ax,offset _cmgetrow_
	mov dx,seg _cmgetrow_
	jmp short WGStPR
WGCMBd:	mov ercode,ax
	jmp short WTPBy2
;Test for buffer in EM
WGTryEM: mov cx,[bx].ehandle
	or cx,cx	;Is desimg->ehandle == 0?
	jz WGinXM	;If yes, jump. Buffer must be in XM
	mov ax,offset _emgetrow
	mov dx,seg _emgetrow
	jmp short WGStPR
;ibuff and ehandle = 0, src must be in XM
WGinXM:	mov cx,[bx].xhandle
	mov ax,offset _xmgetrow
	mov dx,seg _xmgetrow
WGStPR:	mov gethandle,cx
	mov getroutofs,ax
	mov getroutseg,dx
;Get a row of data to process from CM or EM
WGUsCM:	call GetNextRow		;Also initailizes BPRow and srcofs
	cmp ax,NO_ERROR
	jne NorXit
;Set DS to exbuff segment
	mov ds,xbuffseg
	mov cs:OurSP,sp		;Save SP for exit from CheckDTA()
;Set up our LZW string table
	mov bx,WtStrTbofs  ;Set up hash table. We need 4096*5 (20480 bytes),
	mov cl,4	   ; so we allocated 4096*5+16 bytes in savegif prog
	shr bx,cl	   ;BX = ofs/16
	add bx,WtStrTbseg  ;BX = Seg = Seg + ofs/16 = Seg:0
	inc bx		   ;Make sure BX is within border
	mov hash_seg,bx    ;Save hash segment address
	mov cl,GCODSIZ	   ;CL gets code size to use
	call InitLZWvars   ;Initialize LZW constants
	call CompressGIF   ;Compress the image
;Close CM handle, if issued: void cmclose_(imgdes *image, int cmhandle);
NorXit:	push gethandle
	lds di,srcimg	;DS:DI -> source image structure
	push ds		;Push desimg seg, offset
	push di
	call _cmclose_	;Close CM handle
	add sp,6
WTPBy2:	mov ax,ercode
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
;Disk full error handler
WDskFl:	mov sp,cs:OurSP		;Restore our SP for exit
	mov ercode,BAD_DSK	;Exit if error
	jmp short NorXit
_WriteGif_  endp

;Compress an image ala LZW scheme.
; On entry ES -> DTA buffer seg, DS -> exbuff seg.
CompressGIF  proc  near
	call InitGTable		;Initialize the table and some vars
	mov bit_offset,0
	mov ax,clear_code	;Write a clear code
	call WriteGCode
	call GetGChar		;Read first char
ComTop:	xor ah,ah		;Turn char into code
NewP:	mov prefix_code,ax	;Set prefix code
	call GetGChar		;Read next char
	jc NoMoChars		;Carry means EOI
	mov k,al		;Save char in k
	mov bx,prefix_code	;Get prefix code
	call lookup_code	;See if this pair is in the table
	jnc NewP		;CF = 0 => yes, new code in ax
	call AddCode		;Add pair to table
	push bx			;Save new code
	mov ax,prefix_code	;Write old prefix code
	call WriteGCode
	pop bx
	mov al,k		;Get last char
	cmp bx,max_code		;Exceed code size?
	jl ComTop		;Less means no
	cmp nbits,12		;Currently less than 12 bits?
	jl LT12			;Yes, jump
	mov ax,clear_code	;No, write a clear code
	call WriteGCode
	call InitGTable		; and reinit table
	mov al,k		;Get last char
	jmp short ComTop	;Start over
LT12:	inc nbits		;Increase number of bits
	shl max_code,1		;Double max code size
	jmp short ComTop	;Get next char
NoMoChars:
	mov ax,prefix_code	;Write last code
	call WriteGCode
	mov ax,eoi_code		;Write EOI code
	call WriteGCode
	mov ax,bit_offset	;Make sure buffer is flushed to file
	or ax,ax
	jz CoXit		;It is, exit
	mov dx,ax		;Else convert bits to bytes
	and dx,7
	mov cl,3
	shr ax,cl
	or dx,dx		;Write any extra bits
	je NoXtraBits
	inc ax
NoXtraBits:
;Flush the buffer
	mov di,buff_st	;DI -> beginning of current packet in DTA
	mov buff_end,0	;Force writing the buffer
	call CheckDTA
CoXit:	ret
CompressGIF  endp

;Read a byte from the image buffer. Set CF = 1 if we're done with the 
; image (no more rows to read) or there's an error condition. On entry, 
; ES = DTA seg, DS = exbuff seg. Return with char in AL. Uses AX, DX, SI.
GetGChar  proc  near
;Are we done with a row?
GCSmRw:	dec BPRow
	jl GCNwRw  	;Jump if we're done with a row
	mov si,srcofs	;DS:SI -> exbuff
	inc srcofs	;Inc srcofs for next access
	lodsb		;Get the char
	clc	    	;Success
	ret
;Done reading a row -- get the next one from CM or EM
GCNwRw:	dec wrows
	jle EOImage	;Exit if no more rows to do
;Get a row of data to process from CM or EM
	call GetNextRow
	cmp ax,NO_ERROR
	je GCSmRw
	mov ercode,ax	;Exit if error
EOImage: stc		;End of image
	ret
GetGChar  endp

;GetGChar() {
;   if(--BPRow <= 0) {	/* Done with a row */
;      if(--row <= 0)	/* Done with image area? */
;         return(-1);	/* Yes, return -1 */
;      /* Get row also incs yctr, resets srcofs and BPRow */
;      if((errcode=GetNextRow) != NO_ERROR)
;         return(errcode);
;      }
;   return(ch=*src++); /* Return the char */
;   }

;Get the next row of data to process from CM or EM. Also, inc y counter,
; reset srcofs, and reset BPRow. Returns NO_ERROR, EMM_ERR, XMM_ERR, or CM_ERR.
; Uses AX, CX, DX, SI. Preserves DS, ES, DI, CX.
GetNextRow  proc  near
	push es		;Save seg regs since we call far routines
	push cx
	push ds
	lds si,srcimg	;SI -> srcimg
;Read row: getrow(exbuff, shndle, srcimg->stx, yctr, wcols, srcimg->iwidth);
	push [si].iwidth
	push wcols
	push yctr
	push [si].stx
	push gethandle
	push xbuffseg	;Push source seg, offset
	push xbuffofs
	call dword ptr getroutofs	;Any error code is returned in AX
	add sp,14
GNIcRw:	pop ds		;Restore exbuff seg
	pop cx		;Restore regs
	pop es		;AX has error code!
	inc yctr	;Inc row ctr
	mov si,wcols	;Reset bytes per row
	mov BPRow,si
	mov si,xbuffofs	;Reset srcofs to xbuffofs
	mov srcofs,si
	ret		;Return code is in AX!
GetNextRow  endp

;GetNextRow()
;{
;   int errcode = NO_ERROR;
;   if(srcimg->ibuff) {	/* Loading image into CM */
;      hptr = srcimg->ibuff[srcimg.stx + yctr*srcimg->iwidth];
;      memcpy_(exbuff, hptr, cols);
;      }
;   else {	/* Saving image in EM */
;      if((rcode=getrow(exbuff, shandle, srcimg->stx,
;         yctr, cols, srcimg->iwidth)) != NO_ERROR) {
;         errcode = EMM_ERR;
;         }
;      }
;   yctr++;	     /* Inc row ctr */
;   BPRow = Cols;    /* Reset bytes per row */
;   src = exbuff;    /* Reset src */
;   return(errcode);
;}

;Write output buffer to disk if DTA is full. Jump to error handler if disk
; is full. For GIF format, we must add the byte count to the front of
; the output record. On entry, AX = bytes in current packet, DI -> 1st 
; byte of data in latest packet, DS = spic seg, ES = DTA segment. On
; return, DI and buff_st -> 1st byte of data in next packet to be written.
; Uses AX, BX.
CheckDTA  proc  near
	push cx
	push dx
;Store count byte (in AX) in front of packet we just finished
	mov es:[di-1],al
;Is there room in the DTA for another packet
	mov dx,di
	add dx,512	;Since BX points at start of latest packet,
	cmp dx,buff_end	; we need room for 512 bytes
	jge CDWrtDta	;No room, write the DTA
;Room left in DTA for another packet, just reset buff_st
	add di,ax
CDXit:	inc di		;buff_st = buff_st + count byte + 1
	mov buff_st,di	;DI -> 1st byte of data in next packet
	pop dx
	pop cx
	ret
;Not enough room for another packet, write the DTA
CDWrtDta: push ds
	lds dx,dword ptr buffofs
;Bytes to write = buff_st + count byte - buffofs
	add di,ax	;AX = count byte
	sub di,dx	;DX = buffofs
	mov cx,di	;Bytes to write
	mov ah,WRTSQ
	mov bx,whandle	;BX = handle
	int 21h	      	;output record
	pop ds
	jc BadFls    	;Write error
	cmp ax,cx    	;Check if no. of bytes requested was read
	jb BadFls    	;Exit if not (disk full)
;Record written, reset buff_st to buffofs+1
	mov di,buffofs
	jmp short CDXit
BadFls:	jmp WDskFl	;If disk full or write error jmp to handler
CheckDTA  endp		; SP problems handled by exit routine

;CheckDTA(int count)
;{   /* Store count byte (in AX) in front of packet we just finnished */
;   dta[Pos-1] = count;
;   /* Is there room in the DTA for another packet? */
;   if(Pos+512 < MAXBUFSIZ)
;      /* Room left in DTA for another packet, calc Pos */
;      Pos += count + 1;
;   else { /* Not enough room for another packet, write the DTA */
;      byts_wrt = write(handle, dta, Pos);
;      if(byts_wrt!=Pos)  disk_full();
;      Pos = 1;
;      }
;}

;Write a code to the output buffer by writing nbits bits and write buffer
; to disk when full. On entry, AX = code to write, ES = DTA seg,
; DS = exbuff seg. Uses AX, CX, DX, and DI.
WriteGCode  proc  near
	push ax	 		;Save code
	mov ax,bit_offset	;Get bit offset
	mov cx,nbits 		;Adjust bit offset by code size
	add bit_offset,cx
	mov cx,ax		;Convert bit offset to byte offset
	and cx,7		;CX = offset within byte
	shr ax,1
	shr ax,1
	shr ax,1		;AX = bit_offset/8 = byte_offset
	mov di,buff_st		;DI -> beginning of current packet in DTA
	cmp ax,GIFSIZE		;Approaching end of packet?
	jge PakFull		;If yes, write it when DTA is full
	add di,ax		;DI -> where to store byte in DTA
PakNotFull:
	pop ax		;Restore code
	xor dx,dx	;DX will catch bits rotated out
	jcxz NoShft	;If offset in byte is zero (in CX), skip shift
WRot:	shl ax,1	;Rotate code
	rcl dx,1
	loop WRot
	or al,byte ptr es:[di]	;Grab bits currently in buffer
NoShft:	stosw			;Save data
	mov al,dl		;Grab extra bits
	stosb			;Save them
	ret
;Data packet is full, add a count byte to its front and output a record,
; if necessary.
PakFull: push bx
	mov bx,ax		;BX = AX = count byte (byte_offset)
	push es:[di+bx]		;Save the last byte
	call CheckDTA	 	;Write the DTA if full (AX = bytes to write)
	mov ax,cx	 	;CX = offset within byte
	add ax,nbits	 	;Adjust by code size
	mov bit_offset,ax	;New bit offset
	pop es:[di]		;Insert last byte in first position
	pop bx
	jmp short PakNotFull	;Byte offset = 0
WriteGCode  endp

;Initialize the hash table and some other variables
InitGTable  proc  near
	call InitGVars		;Initialize variables
	push es			;Save seg reg
	mov es,hash_seg		;Address hash table
	mov ax,-1		;Unused flag
	mov cx,640		;Clear first 256 entries (256*5=640)
	xor di,di		;Point to first entry
	rep stosw		;Clear it out
	pop es			;Restore seg reg
	ret
InitGTable  endp

;See if prefix_code+k is in string table. On entry, AL = k and BX =
; prefix_code. Return with SI -> hash table addr and DI = 0 if first use
; of prefix, and, if code is in the table, CF = 1. If code is NOT in the
; table, return CF = 0 and AX = new code. Uses AX, BX, DI, SI.
lookup_code  proc  near
	push ds		        ;Save seg reg
	mov ds,hash_seg		;point to hash table
	call windex		;Convert code in BX to address in SI
	xor di,di		;Zero flag
	cmp [si].first,-1	;Has this code been used?
	je NotUsed		;Equal means no
	inc di			;Set flag
	mov bx,[si].first	;Get first entry
CkChar:	call windex		;Convert code in BX to address in SI
	cmp [si].char,al	;Is char the same?
	jne DifChr		;Not equal means no
	mov ax,bx		;Put found code in ax
	clc  			;Success
	pop ds
	ret
DifChr:	cmp [si].next,-1	;More left with this prefix?
	je NotUsed		;Equal means no
	mov bx,[si].next	;Get next code
	jmp short CkChar	;Try again
NotUsed: stc  			;Not found (AL still contains char)
	pop ds
	ret
lookup_code  endp

;Add prefix_code+k to string table. On entry, AX = new code, DI = 0 if
; first use of prefix, and SI -> hash table addr. Uses AX and BX.
AddCode  proc  near
	mov bx,free_code	;Get code to use
	push ds			;point to hash table
	mov ds,hash_seg
	or di,di		;First use of this prefix?
	jz FirstUse		;Zero => yes
	mov [si].next,bx	;Point last use to new entry
	jmp short CkLimit
FirstUse:
	mov [si].first,bx	;Point first use to new entry
CkLimit:
	cmp bx,4096	;Have we reached code limit (Max code + 1)?
	je AtLimit		;Yes, just return
	call windex		;No, convert code in BX to address in SI
	mov [si].first,-1	;Initialize pointers
	mov [si].next,-1
	mov [si].char,al	;Save suffix char
	inc free_code		;Adjust next code
AtLimit:
	pop ds
	ret
AddCode  endp

;Convert code in BX to address in SI
windex   proc  near
	mov si,bx		;si = bx * 5 (5 byte hash entries)
	shl si,1		;si = bx * 2 * 2 + bx
	shl si,1
	add si,bx
	ret
windex   endp

	END
+ARCHIVE+ writepcx.asm  7059  4/05/1991  8:11:58
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
;   Contents:
;	unpackpcx_
;	packpcx_

	.MODEL	LARGE
@ab	  equ	6	;Equate for large model
;If BPL_EVEN != 0, write PCX files with even bytes per line only
; Equate in savepcx.c must match!
BPL_EVEN  equ	0

	.CODE
;Pack a line of data using the PCX scheme. Return the number of
; compressed bytes.
; Usage: int packpcx_(UCHAR *des, UCHAR *src, int byts2pack);
des	equ dword ptr @ab[bp]		;Where to put the packed data
src	equ dword ptr @ab[bp+4]		;Source of data to pack
byts2pc	equ  word ptr @ab[bp+8]		;Number of data bytes to pack
	public  _packpcx_
_packpcx_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	cld
	push ds
	lds si,src
	les di,des
	push di		;Save des offset to calc no. of compressed bytes
;Make sure byts2pack is even
IF BPL_EVEN NE 0
	test byts2pc,1
	jz PXodd
	inc byts2pc
ENDIF
;Init last_byte, count, and byts2pack
PXodd:	lodsb
	mov dl,al	;DX = last_byt = *src++;
	dec byts2pc	;byts2pack--;
	mov cx,1	;count = 1;
PXtop:	dec byts2pc	;byts2pack--;
	jl PXdone	;Exit when they're no more bytes to pack
	lodsb
	cmp al,dl	;If current byte == last byte, we have a run
	jne NoRun
;Bump run counter and test if run is done
	inc cx
	cmp cx,63
	jl PXtop	;Run not finished if count < 63
;Run is done, write count/byte, advance des, and reset count
	call encode	;Write count/byte, advance des
        xor cx,cx	;count = 0; /* Reset run counter */
	jmp short PXtop
;Current byte != last byte, write count/byte
NoRun:	push ax		;Save what will become last_byte (AL)
	call encode	;Write count/byte, advance des
	pop dx		;DX = last_byte gets current byte
        mov cx,1	;count = 1; /* Reset run counter */
	jmp short PXtop
;Write last run fragment
PXdone:	call encode
;Get des offset back to calc no. of compressed bytes = des - dptr;
	pop ax		;AX = original des
	sub ax,di
	neg ax		;Return number of compressed bytes
	pop ds
	pop si
	pop di
	pop bp
	ret
_packpcx_  endp

;int packpcx_(des, src, byts2pack)
;UCHAR *des;		/* Where to put the packed data */
;UCHAR huge *src;	/* Source of data to pack */
;int byts2pack;		/* Data bytes to pack */
;{
;   extern int enc_pcx_(int,int,UCHAR *);
;   UCHAR *dptr=des; /* Save des to calc no. of compressed bytes */
;   int count, last_byt, ch;
;   /* Make sure byts2pack is even */
;/* if(byts2pack & 1)  byts2pack++; */
;   /* Init last_byte, count, and counter */
;   last_byt = *src++;
;   byts2pack--;
;   count = 1;
;   while(byts2pack--) {
;      ch = *src++;
;      if(ch == last_byt) {	/* Current byte = last byte => run */
;         /* Bump run counter and test if run is done */
;         if(++count >= 63) {	/* If yes, write count/byte */
;            des += enc_pcx_(last_byt, count, des); /*  and advance des */
;            count = 0;		/* Reset run counter */
;            }
;         }
;      else {	/* Current byte != last byte, write count/byte */
;         des += enc_pcx_(last_byt, count, des);	/*  and advance des */
;         last_byt = ch;
;         count = 1;	/* Reset run counter */
;         }
;      }
;   /* Write last run fragment */
;   des += enc_pcx_(last_byt, count, des);
;   /* Return number of compressed bytes */
;   return(des - dptr);
;}

;Store the count/char pair at ES:DI using the PCX compression scheme.
; On entry, ES:DI -> where to store the bytes, DL = char to encode, 
; CL = chars in run length (0-63). Uses AX, advances DI as bytes are stored.
encode  proc  near
;Exit if no bytes to write
	mov ah,cl
	or ah,ah 	;CL = AH = number of repeated chars (0 - 63)
	jz EPBye
;ES:DI -> where to write bytes
	mov al,dl	;AL = DL = byte to encode
;If count > 1 or chr >= 192, write the count/char pair
	cmp ah,1	;AH = count
	ja EncTwo
	cmp al,0c0h	;AL = char to encode
	jae EncTwo
;If count = 1 or chr < 0xc0, write just the char
	stosb		;*des++ = chr;
EPBye:	ret
;Write count/char pair
EncTwo:	or ah,0c0h	;AH = count | 0xc0
	xchg al,ah	;AL = count | 0xc0, AH = char
	stosw
	jmp short EPBye
encode  endp

;int encode(int chr, int count, UCHAR *des)
;{
;   int rcode=0;
;   if(count) {
;   /* If count > 1 or chr >= 192, write the count/char pair */
;      if(count > 1 || chr >= 0xc0) {
;         *des++ = count | 0xc0;
;         rcode++;
;         }
;      /* If count = 1 or chr < 192, write just the char */
;      *des = chr;
;      rcode++;
;      }
;   return(rcode);	/* Ret no. of bytes written */
;}

;Unpack a line of data compressed with the PCX scheme.
; Return the number of compressed bytes read.
; Usage: int unpackpcx_(UCHAR *des, UCHAR *src, int desbyts)
des	equ dword ptr @ab[bp]		;Where to put the expanded data
src	equ dword ptr @ab[bp+4]		;Where to get the packed data
desbyts	equ  word ptr @ab[bp+8]		;Uncompressed bytes to write
	public  _unpackpcx_
_unpackpcx_  proc  far
	push bp
	mov bp,sp
	push di
	push si
	push ds
	lds si,src
	push si	;Save src offset to calc no. of compressed bytes read
	les di,des
;while(des_byts > 0) {
UPtop:	cmp desbyts,0
	jle UPdone
	mov cx,1 	;count = 1;
	lodsb
	cmp al,0c0h
	jbe UPltc0
;ch >= 0xc0:
	and al,3fh
	xor ah,ah
	mov cx,ax	;count = ch & 0x3f;
	lodsb		;ch = *src++;
;Store count bytes if there's room
UPltc0:	dec cx
	jl UPtop
;Exit if we've written as many bytes as requested
	dec desbyts	;if(des_byts-- == 0)
 	jl UPckct	;  goto ckct;
	stosb		;*des++ = ch; /* Update dest */
	jmp short UPltc0
;Get src offset back to calc no. of compressed bytes read = src - sptr;
UPdone:	pop ax		;AX = original src
	sub ax,si
	neg ax		;Return number of compressed bytes read
	pop ds
	pop si
	pop di
	pop bp
	ret
;Compensate for files that use multi-scan line compression (DFI does!)
UPckct:	or cx,cx	;Count <= 0?
 	jl UPdone	;Yes, exit
	dec si		;No, reset src for next time
	dec si
	add cl,0c1h	;*src = 0xc1 + count
	mov [si],cl
 	jmp short UPdone
_unpackpcx_  endp

;static int unpackpcx(des, src, des_byts)
;UCHAR *des;		/* Where to put the unpacked data */
;UCHAR *src;		/* Source of data to unpack */
;int des_byts;		/* Uncompressed bytes to write */
;{
;   UCHAR *sptr=src; /* Save src to calc no. of compressed bytes read */
;   int count, ch;
;   while(des_byts > 0) {
;      count = 1;
;      /* If ch = 192 -> 255, write the following byte (63 & ch) times */
;      if((ch = *src++) >= 0xc0) {
;         count = ch & 0x3f;
;         ch = *src++;
;         }	/* Store count bytes if there's room */
;      while(count--) {
;         if(des_byts-- <= 0) {
;            if(count > 0) { /* Compensate for multi-line compression */
;               src -= 2;
;               *src = 0xc1 + count
;               }
;            goto xit; /* Exit if we've written as many bytes as requested */
;         *des++ = ch; /* Update dest */
;         }
;      }	/* Return the number of compressed bytes we read */
;xit: 
;   return(src - sptr);
;}
	END
+ARCHIVE+ writepiw.asm  2216  3/28/1991  8:37:44
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
; Contents:
;	enc_piw_	Encode a character run using PIW scheme

	.MODEL	LARGE
@ab	   equ   6	;Equate for large model

;PIW file format special codes
SOP	equ	40h	;Start of picture code
SOL	equ	41h	;Start of line code
EOP	equ	42h	;End of picture code
REP1	equ	80h	;Repeat previous byte 1-16 times
REP16	equ	90h	;Repeat previous byte 16-240 times

	.CODE
;Encode a character run using the PIW scheme. Return the number of
; bytes written. If called with count = 0, just stores ebyt.
; Usage: int enc_piw_(int ebyt, int count, UCHAR *des)
ebyt	equ  byte ptr @ab[bp]	;Byte to store
count	equ  word ptr @ab[bp+2]	;Run counter value
desofs	equ dword ptr @ab[bp+4]	;Where to store the data
	public _enc_piw_
_enc_piw_  proc  far
	push bp
	mov bp,sp
	push di
	cld
	les di,desofs
	push di		;Save DI starting position
	mov al,ebyt
	shr al,1
	shr al,1	;*des++ = byt >> 2
	stosb		;Store the char
;Check for repeated bytes
	mov bx,count
	mov al,bl
	test bx,00f0h	;Check for 16 -> 255 repeated chars
	jz No16to255
	mov cl,4
	shr al,cl
	or al,REP16	;*des++ = REP16 | (count >> 4);
	stosb		;Store the char
;	and bx,000fh	;count &= 0x0f, update count
No16to255:
	and bx,000fh	;Check for 1 -> 15 repeated chars
	jz No1to15
	mov al,bl
	or al,REP1
	stosb		;Store the char
;Calc number of bytes that were written
No1to15: pop ax		;Restore DI starting position
	sub ax,di
	neg ax		;Return the number of bytes written
	pop di
	mov sp,bp
	pop bp
	ret
_enc_piw_  endp

;/* Encode a character run using PIW scheme. Return the number of bytes
;   written.
;*/
;int enc_piw_(int byt, int count, UCHAR *des)
;{
;   int bctr=1;
;   *des++ = byt >> 2;   /* Store the char */
;   /* Check for repeated bytes */
;   if(count & 0xf0) {	/* Check for 16 -> 255 repeated chars */
;      *des++ = REP16 | (count >> 4);
;      bctr++;
;      count &= 0x0f;		/* Update count */
;      }
;   if(count &= 0x0f) {	/* Check for 1 -> 15 repeated chars */
;      *des++ = REP1 | count;
;      bctr++;
;      }
;   return(bctr);
;}
	END
+ARCHIVE+ writetif.asm 37234  1/26/1991  7:26:08
; Victor Library, Copyright (c) 1989-1991, ALL RIGHTS RESERVED
;	     	Catenary Systems
;		470 Belleview
;		St Louis, MO 63119
;		(314)962-7833
;   Contents:
;	ReadTif_
;	  DecompressLZW, ReadCode, WriteChar, InitVars, rindex,
;	  PutCode, SetFilePtr
;	WriteTif_
;	  CompressLZW, FlushBuff, WriteCode, GetChar, WrtFilePos,
;	  InitTable, lookup_code, windex, AddCode
;	expand_
;	swapwords_	Swap words for count bytes
;	swapbytes_	Swap bytes for count bytes

	.MODEL	LARGE
@ab	  equ	  6	;Equate for large model

MAXBUFSIZ equ	8192	;Maximum buffer size
NO_ERROR  equ     0
BAD_DSK	  equ	 -3
BAD_TIFF  equ	 -6
;DOS fctns
READSQ	  equ	 3fh
WRTSQ	  equ	 40h
;LZW constants
TCODSIZ   equ	8
BUFSIZE	  equ	MAXBUFSIZ	;Buffer size

	extrn _Hbuff_:BYTE	;General purpose buffer (see viccore.c)
	extrn _emgetrow:far
	extrn _xmgetrow:far
	extrn _cmgetrow_:far
	extrn _emputrow:far
	extrn _xmputrow:far
	extrn _cmputrow_:far
	extrn _cmopen_:far
	extrn _cmclose_:far

;Image descriptor
imgdes_  struc
ibuff	  DD  ?	;Image buffer address in conventional memory
ehandle	  DW  ?	;Expanded memory handle
xhandle	  DW  ?	;Extended memory handle
stx	  DW  ?	;Image area to be processed
sty	  DW  ?
endx	  DW  ?
endy	  DW  ?			
iwidth	  DW  ?	;Image width
ilength	  DW  ?	;Image length
palette   DD  ?	;Address of palette associated with the image
palsize   DW  ? ;Palette size in bytes
reserved  DW  ? ;Reserved
imgdes_  ends

;TIF file format info structure definition (used by gifinfo)
tifdes_  struc
tfByteOrder DW  ?	;Byte order -- II or MM
tfwidth	    DW  ?	;File header image width	
tflength    DW  ?	;Image length
tfBitsPSam  DW  ?	;Bits per sample
tfcomp	    DW  ?	;Compression scheme code
tfSamPPix   DW  ?	;Samples per pixel
tfPhotoInt  DW  ?	;Photo interpretation
tifdes_  ends

;Hash table entry for ReadTif_ (so string table = 4096*3 bytes long)
hash_read  struc
nxt	DW	?    ;Next entry along chain
chr	DB	?    ;Suffix char
hash_read  ends

	.CODE
;Load LZW compressed TIF image from disk file to image buffer.
; Returns NO_ERROR, EMM_ERR, XMM_ERR, or CM_ERR.
; Usage: errcode = ReadTif_(imgdes *desimg, int handle, UCHAR *exbuff,
; UCHAR *strtab, int cols, int rows, long *StrStarts, long *StrCounts,
; int Strips, Tiffdata *tdat);
desimg      equ dword ptr @ab[bp]	;Destination image structure
rhandle	    equ  word ptr @ab[bp+4]	;File handle
xbuffofs    equ		  @ab[bp+6]	;Source of data to save (cols bytes)
xbuffseg    equ		  @ab[bp+8]
RdStrTbofs  equ  word ptr @ab[bp+10]	;4096*5 = 20480 bytes of memory 
RdStrTbseg  equ  word ptr @ab[bp+12]	; to use for LZW cmpd file
rcols	    equ  word ptr @ab[bp+14]	;Rows, cols of image area to load
rrows	    equ  word ptr @ab[bp+16]
StrStarts   equ dword ptr @ab[bp+18]	;Addr of StripStarts array in C prog
StrCounts   equ dword ptr @ab[bp+22]	;Addr of StripCounts array
Strips      equ  word ptr @ab[bp+26]	;Number of strips in the image
tifst       equ dword ptr @ab[bp+28]	;TIF image structure

free_code   equ  word ptr [bp-2]	;Next code to use
hash_seg    equ  word ptr [bp-4]	;Hash table segment address
cur_code    equ  word ptr [bp-6]
max_code    equ  word ptr [bp-8]
nbits	    equ  word ptr [bp-10]	;Code size (9-12)
bit_offset  equ  word ptr [bp-12]
BPRow	    equ  word ptr [bp-14]	;Uncompressed bytes in a row
stack_count equ  word ptr [bp-16]
k	    equ  byte ptr [bp-18]
old_code    equ  word ptr [bp-20]
fin_char    equ  byte ptr [bp-22]
ercode	    equ  word ptr [bp-24]
buffseg     equ 	  [bp-26]	;DTA address
buffofs     equ 	  [bp-28]
codesize    equ  byte ptr [bp-30]	;code size = 8 - 12
clear_code  equ  word ptr [bp-32]	; = 1 << codesize = reinit the string table
eoi_code    equ  word ptr [bp-34]	; = clear_code + 1 = end of file marker
first_free  equ  word ptr [bp-36]	; = clear_code + 2 = First free code
codemax     equ  word ptr [bp-38]	; = 1 << (codesize+1)
desofs      equ  word ptr [bp-40]	;Position in exbuff
buff_end    equ  word ptr [bp-42]	;Address of the end of the DTA buffer
StripCtr    equ  word ptr [bp-44]	;Strip counter
DtaSize     equ  word ptr [bp-46]	;Size of file buffer to use
DtaSiz4     equ  word ptr [bp-48]	;DtaSize - 4 (used by ReadCode)
yctr        equ  word ptr [bp-50]	;Y counter for emputrow()
BPStriphi   equ  word ptr [bp-52]	;Bytes per strip, hi word
BPStrip     equ  word ptr [bp-54]	;Bytes per strip, low word
twidth	    equ  word ptr [bp-56]	;Adjusted image width based on header
in_code	    equ  word ptr [bp-58]
putroutseg  equ           [bp-60]	;putrow() routine to use
putroutofs  equ           [bp-62]
puthandle   equ  word ptr [bp-64]	;Handle to use for putrow()

	public _ReadTif_
_ReadTif_  proc  far
	push bp        
	mov bp,sp     
	sub sp,64
	push di
	push si
	cld
;Init some variables
	mov ercode,NO_ERROR	;Assume no errors
	xor ax,ax
	mov StripCtr,ax
	mov BPStriphi,ax
	mov BPStrip,ax
	mov buff_end,ax		;This is to force a read
;Set DTA size to maximum
	mov ax,BUFSIZE
	mov DtaSize,ax	 	;DtaSiz4 = DtaSize-4 (used in ReadCode)
	sub ax,4
	mov DtaSiz4,ax
;Read image data using LZW decompression. On entry DS -> DTA buffer seg.
	mov bx,RdStrTbofs  ;Set up hash table. We need 4096*3 (12288 bytes),
	mov cl,4	   ; so we allocated 4096*3+16 bytes in loadtif prog
	shr bx,cl	   ;BX = ofs/16
	add bx,RdStrTbseg  ;BX = Seg = Seg + ofs/16 = Seg:0
	inc bx		   ;Make sure BX is within border
	mov hash_seg,bx	   ;Save hash segment address
;Store Hbuff_ addr so it's accesible thru SS
	mov ax,offset _Hbuff_
	mov buffofs,ax
	mov ax,seg _Hbuff_
	mov buffseg,ax
	push ds	      	;Save our DS
;yctr = desimg->sty;
	lds di,desimg	;DS:DI -> dest image structure
	mov ax,[di].sty
	mov yctr,ax
;Initialize putroutine() and puthandle
	mov ax,[di]
	or ax,[di+2]	;Is desimg->ibuff 0?
	jz RTTryEM	;If yes, jump to test for buffer in EM
;desimg->ibuff != NULL, open cmhandle
;if((rcode=cmopen_(handle_addr, image->ibuff)) == NO_ERROR)
	push [di+2]	;Push ibuff seg, offset
	push [di]
	push ss		;Push handle seg, offset
	lea ax,puthandle
	push ax
	call _cmopen_	;Get CM handle
	add sp,8
	cmp ax,NO_ERROR
	jne RTCMBd
	mov cx,puthandle ;CX gets handle
	mov ax,offset _cmputrow_
	mov dx,seg _cmputrow_
	jmp short RTStPR
RTCMBd:	mov ercode,ax
	jmp short RTPBy2
;Test for buffer in EM
RTTryEM: mov cx,[di].ehandle
	or cx,cx	;Is desimg->ehandle == 0?
	jz RTinXM	;If yes, jump. Buffer must be in XM
	mov ax,offset _emputrow
	mov dx,seg _emputrow
	jmp short RTStPR
;ibuff and ehandle = 0, des must be in XM
RTinXM:	mov cx,[di].xhandle
	mov ax,offset _xmputrow
	mov dx,seg _xmputrow
RTStPR:	mov puthandle,cx
	mov putroutofs,ax
	mov putroutseg,dx
;Initialize codsiz and twidth
RTUsCM:	lds di,tifst	   ;DS:DI -> GIF image structure
;Initialize twidth and bytes per row
;NOTE: twidth should really be adjusted twidth (see savetif.c),
; but since bits per sample = 8, twidth = adj_twidth
	mov ax,[di].tfwidth
	mov twidth,ax
	mov BPRow,ax		;Initialize bytes per row
	mov cl,TCODSIZ		;CL gets code size to use
;Set DS to the DTA seg
	mov ds,buffseg		;DS -> Hbuff_ (DTA)
	call InitLZWvars	;Initialize LZW constants
	les di,dword ptr xbuffofs ;ES:DI -> exbuff
	mov desofs,di	   ;Initialize desofs
RTStripTop:
	cmp rrows,0	;More rows to fill?
	jle RTPBye	;Exit if no
	call SetFilePtr	;Move file ptr to next strip, inc StrCtr, set BPStrip
	jc RTPBye	;Exit if no more strips to read
	call DecompressLZW	;Decompress a strip
	cmp ercode,NO_ERROR	;Exit if error
	je RTStripTop
;Close CM handle, if issued: void cmclose_(imgdes *image, int cmhandle);
RTPBye:	push puthandle
	lds di,desimg	;DS:DI -> dest image structure
	push ds		;Push desimg seg, offset
	push di
	call _cmclose_	;Close CM handle
	add sp,6
RTPBy2:	mov ax,ercode
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_ReadTif_  endp

;Decompress a strip. On entry DS -> DTA buffer seg, ES -> dpic seg.
DecompressLZW  proc  near
	mov cs:OurSP,sp		;Save SP for exit
	call InitVars		;Initialize variables
	mov bit_offset,0
	mov stack_count,0
;Read in the first record
	mov bx,rhandle
	mov dx,buffofs		;DS:DX -> DTA buffer
	mov cx,DtaSize
	mov ah,READSQ
	int 21h			;Ignore errors for now
;To exclude reading a file of the wrong format,
; exit if the first code of a strip isn't a clear code
	call ReadCode		;Get a code
	cmp ax,clear_code	;Clear code?
	je Clr1			;Yes, continue
	mov ercode,BAD_TIFF	;No, exit
	jmp short DCEOI
DCTop:	call ReadCode		;Get a code
	cmp ax,eoi_code		;End of file?
	je DCEOI		;yes
	cmp ax,clear_code	;Clear code?
	jne DCNoClr		;no
	call InitVars		;Initialize table
Clr1:	call ReadCode		;Read next code
	mov cur_code,ax		;Initialize variables
	mov old_code,ax
	mov k,al
	mov fin_char,al
	call WriteChar		;Write a char in the image buffer
	jc DCNoMoData		;CF = 1 => Data all written or error
	jmp short DCTop		;Get next code
DCNoClr:
	mov cur_code,ax		;Save new code
	mov in_code,ax
	mov es,hash_seg		;ES -> hash table
	cmp ax,free_code	;Code in table? (k<w>k<w>k)
	jl DCCodeInTable	;yes
	mov ax,old_code		;Get previous code
	mov cur_code,ax		;Make current
	mov al,fin_char		;Get old last char
	push ax			;Save it
	inc stack_count
DCCodeInTable:
	mov bx,clear_code
	cmp cur_code,bx		;Code or character?
	jl DCChar		;Char
	mov bx,cur_code		;Convert code to address
	call rindex		;Convert code in BX to an address in BX
	mov al,es:[bx+2]	;Get suffix char
	push ax			;Save it
	inc stack_count
	mov ax,es:[bx]		;Get prefix code
	mov cur_code,ax		;Save it
	jmp short DCCodeInTable	;Translate again
DCNoMoData:			;No more room for data
	mov sp,cs:OurSP		;Restore our SP for exit
DCEOI:	ret			;Done with a strip
DCChar:	mov es,xbuffseg		;ES -> exbuff seg
	mov ax,cur_code		;Get code
	mov fin_char,al		;Save as final char and k
	mov k,al
	push ax			;Push it
	inc stack_count
	mov cx,stack_count	;Pop stack
	jcxz DCStkMt		;If anything's there
DCWrt:	pop ax
	call WriteChar
	jc DCNoMoData		;CF = 1 => Data all written or error
	loop DCWrt
DCStkMt:
	mov stack_count,cx	;Clear count on stack
	call PutCode		;Add new code to table
	mov ax,in_code		;Save input code
	mov old_code,ax
	mov bx,free_code	;At table limit?
	cmp bx,max_code
	jl DCNotMax		;Less means no
	cmp nbits,12		;Still within twelve bits?
	je DCNotMax		;No (next code should be clear)
	inc nbits		;Yes, increase code size
	shl max_code,1		;Double max code
	inc max_code
DCNotMax:
	jmp DCTop		;Get the next code
DecompressLZW  endp

;Return a code (word) from the file in AX. Uses AX, BX, CX, DX, DI, and SI.
; On entry DS = DTA seg, ES = exbuff seg.
ReadCode  proc  near
	mov ax,bit_offset	;Get bit offset
	add ax,nbits		;Adjust by code size
	xchg bit_offset,ax	;bit_offset += nbits (for next time)
;Calculate byte offset and offset in byte
	mov cx,ax
	and cx,7		;CX = offset in byte
	shr ax,1
	shr ax,1
	shr ax,1		;AX = byte ofset
	mov si,buffofs		;Point SI to beginning of DTA
	cmp ax,DtaSiz4	 	;DtaSize-4, Approaching end of buffer?
	jge RDBufMT		;Jump if we're approaching the end of it
;Still bytes to read in the input buffer
RDBufOK: add si,ax		;Add in byte offset. SI -> bytes to read in
	lodsw			;Get first 2 bytes
	xchg ah,al
	mov bx,ax		;Save them in BX
	jcxz RDSkp		;If zero, skip loading 3rd byte and shifts
	lodsb			;Get 3rd byte, CX = offset in byte (0-7)
RDRot:	shl al,1		;Put code in high (code size) bits of BX
	rcl bx,1
	loop RDRot
RDSkp:	mov cx,16
	sub cx,nbits		;Keep only the valid bits
	mov ax,bx		;Put code to return in ax
	shr ax,cl		;return(ch >>= 16 - nbits);
	ret
;Near the end of the input buffer, get some more bytes
; On entry, AX = byte offset, CX = offset in byte, SI -> start of DTA
RDBufMT: push cx 	;Save offset in byte
	add cx,nbits	;Calculate a new bit offset
	mov bit_offset,cx
	mov bx,ax	;Save the byte offset in BX
	mov cx,DtaSize
	sub cx,ax	;Calculate the bytes left in the buffer to move
;Move last chars to front of buffer
	mov di,si	;Both DI and SI point to start of DTA
BckFil:	mov al,[di+bx]	;Move 0-4 bytes (DI+BX -> DTA[0 + byte_ofs]
	mov [di],al
	inc di
	loop BckFil
	mov dx,di	;Fill the rest of the input buffer with data
	mov cx,bx	;CX = byte offset (DtaSize-4 to DtaSize bytes)
	mov bx,rhandle
	mov ah,READSQ	;Read in DtaSize bytes. May not use all of them
	int 21h
	xor ax,ax	;Set byte offset = 0
	pop cx		;Restore offset in byte
	jmp short RDBufOK
ReadCode  endp

;ReadCode() {
;  byte_ofs = bit_ofs/8		/* Calc byte offset */
;  ofs_inbyte = bit_ofs%8;	/* Calc offset in byte */
;  bit_ofs += nbits;		/* Calc new bit offset */
;  if(byte_ofs >= DtaSize-4) {
;     /* Near end of DTA */
;     bit_ofs = ofs_inbyte + nbits;
;     bytes_left = DtaSize - byte_ofs;
;     while(bytes_left--) { /* Move bytes left to front of buffer */
;        *dta = *(dta+byte_ofs);
;        dta++;
;        }
;     read(handle, dta+bytes_left, byte_ofs); /* Fill rest of buffer */
;     byte_ofs = 0;
;     }
;  dta += byte_ofs;
;  ch12 = *(int *)dta;
;  swab(ch12, 2);	/* exchange bytes */
;  if(ofs_inbyte) {	/* Skip if ofs_inbyte == 0 */
;     ch3  = *(dta+2);
;     while(ofs_inbyte--) {
;        ch3 shl 1;
;        ch12 rcl 1;
;        }
;     }
;  return(ch12 >> (16-nbits));
;}

;Write a byte to the image buffer. If out of room, ret with CF = 1. On entry,
; AL = byte to write, DS -> DTA buffer seg, ES -> dpic seg. Uses AX, DX, DI.
WriteChar  proc  near
;Write the character first
	mov di,desofs	;DI = where to store cahr in exbuff
	inc desofs	;Inc desofs for next access
	stosb
;Check to see if we're done with a row
	dec BPRow	;--BPRow
	jle WNwRow  	;Jump if yes
WTCby:	clc	    	;Success
	ret
;Done with a row -- store the row in CM or EM
;Copy the row in exbuff into CM or EM, inc yctr
WNwRow:	call TPutRow
	cmp ax,NO_ERROR	;Only an EMM/XMM error is possible here
	jne WTEmEr
	dec rrows
	jle WTODat	;Exit if no more rows to do
;Reset ES:desofs to exbuff, and BPRows to twidth
NoWrRw:	les di,dword ptr xbuffofs
	mov desofs,di
	mov ax,twidth	;Reset BPRows
	mov BPRow,ax
	jmp short WTCby
WTEmEr:	mov ercode,ax	;Store return error code and exit as if we're done
WTODat:	stc		;Out of space to store data
	ret
WriteChar  endp

;*******************************

;WriteChar(int ch) {
;   *des++ = ch;
;   if(--BPRow <= 0) {	/* Done with a row? */
;      /* Yes, store the row (also advances yctr) */
;         if(errcode=TPutRow()) != NO_ERROR)
;            return(errcode);
;         /* Are we done with the image area? */
;         if(--rrow <= 0)
;            return(-1);	/* Yes, return -1 */
;         }
;      des = exbuff;    /* Reset des */
;      BPRow = twidth;	/* Reset bytes per row */
;      }
;   return(0);
;   }

;Store the row of data just processed into CM/EM/XM and inc yctr.
; Returns NO_ERROR, EMM_ERR, XMM_ERR, or CM_ERR. Uses AX, BX, DX, ES, DI.
TPutRow  proc  near
	push cx		;Save stack_count
	push ds		;Save DTA seg
	lds di,desimg	;DS:DI -> desimg.ibuff
;Write row: putrow(exbuff, dhandle, desimg->stx, yval, cols, desimg->iwidth);
	push [di].iwidth
	push rcols
	push yctr
	push [di].stx
	push puthandle
	push xbuffseg	;Push source seg, offset
	push xbuffofs
	call dword ptr putroutofs	;Any error code is returned in AX
	add sp,14
	inc yctr	;rowno++
	pop ds		;Restore DTA seg
	pop cx		;Restore stack_count
	ret
TPutRow  endp

;int TPutRow(void)
;{
;   int rcode;
;   rcode = putrow(exbuff, dhandle, desimg->stx, yctr++, cols, desimg->iwidth);
;   return(rcode);
;}

;Initialize LZW constants. Enter with code size in CL. Uses CL, AX.
InitLZWvars  proc  near
	mov codesize,cl
	mov ax,1
	shl ax,cl
	mov bx,ax
	shl bx,1
	mov codemax,bx	   ;codemax = 1 << (codesize+1)
	mov clear_code,ax  ;clear_code = 1 << codesize
	inc ax
	mov eoi_code,ax	   ;eoi_code = clear_code + 1
	inc ax
	mov first_free,ax  ;first_free = clear_code + 2
	ret
InitLZWvars  endp

;Initialize some LZW variables. Uses AX.
InitVars  proc  near
	xor ah,ah
	mov al,codesize
	inc ax
	mov nbits,ax
	mov ax,codemax
	dec ax
	mov max_code,ax
	mov ax,first_free
	mov free_code,ax
	ret
InitVars  endp

;Convert code in BX to address in BX
rindex  proc  near
	mov cx,bx		;bx = bx * 3 (3 byte entries)
	shl bx,1
	add bx,cx
	ret
rindex  endp

;Add new code to table
PutCode  proc  near
	mov bx,free_code	;Get new code
	call rindex		;Convert code in BX to address in BX
	push es			;ES -> hash table
	mov es,hash_seg
	mov al,k		;Get suffix char
	mov es:[bx].chr,al	;Save it
	mov ax,old_code	   	;Get prefix code
	mov es:[bx].nxt,ax	;Save it
	pop es
	inc free_code		;Set next code
	ret
PutCode  endp

;Gets BPStrip from StrCounts[], positions file pointer to start of next strip,
; increments StripCtr. Ret with CF = 1 if we run out of strips to read.
; On entry DS -> DTA buffer seg.
SetFilePtr  proc  near
	mov cx,StripCtr
;More Strips to read?
	cmp cx,Strips
	jae SFNoSp	;If no more Strips, we're done
	shl cx,1
	shl cx,1	;addr = StartStarts[StripCtr*4]
	push ds
	lds bx,StrCounts
	add bx,cx 
	mov ax,[bx]	;get lsw
	mov BPStrip,ax
	mov ax,[bx+2]	;get msw
	mov BPStripHi,ax
	lds bx,StrStarts
	add bx,cx
	mov cx,[bx+2]	;get msw -> cx
	mov dx,[bx]	;get lsw -> dx
	pop ds
	mov ax,4200h	;Move file ptr fctn
	mov bx,rhandle
	int 21h		; position of file pointer returned in dx:ax
	inc StripCtr
	clc
	ret
SFNoSp:	stc
	ret
SetFilePtr  endp

	EVEN
OurSP	    DW  ?	;Save our stack pointer here

;**** Write TIFF routines ****

;Hash table entry for WriteTif_ (so string table = 4096*5 bytes long)
hash_write  struc
first	DW	?	;First code with equivalent prefix
next	DW	?	;Next entry along chain
char	DB	?	;Suffix char
hash_write  ends

;Save image data to disk as an LZW compressed TIF file.
; Returns NO_ERROR, BAD_DSK, EMM_ERR, XMM_ERR, or CM_ERR.
; Usage: errcode = WriteTif_(imgdes *srcimg, int handle, UCHAR *exbuff,
; UCHAR *strtab, long *StrStarts, int StrSize);
srcimg      equ dword ptr @ab[bp]	;Source image structure
whandle	    equ  word ptr @ab[bp+4]	;File handle
xbuffofs    equ		  @ab[bp+6]	;Source of data to save
xbuffseg    equ		  @ab[bp+8]
WtStrTbofs  equ  word ptr @ab[bp+10]	;4096*5 = 20480 bytes of memory 
WtStrTbseg  equ  word ptr @ab[bp+12]	; to use for LZW cmpd file
StrStarts   equ dword ptr @ab[bp+14]	;Addr of StripStarts array in C prog
StrSize	    equ  word ptr @ab[bp+18]	;Strip size to write

free_code   equ  word ptr [bp-2]	;Next code to use
hash_seg    equ  word ptr [bp-4]	;Hash table segment address
prefix_code equ  word ptr [bp-6]
max_code    equ  word ptr [bp-8]
nbits	    equ  word ptr [bp-10]	;Code size (9-12)
bit_offset  equ  word ptr [bp-12]
BPRow	    equ  word ptr [bp-14]	;Bytes in a row
stack_count equ  word ptr [bp-16]
k	    equ  byte ptr [bp-18]
old_code    equ  word ptr [bp-20]
fin_char    equ  byte ptr [bp-22]
ercode	    equ  word ptr [bp-24]
buffseg     equ 	  [bp-26]	;DTA address
buffofs	    equ	          [bp-28]
codesize    equ  byte ptr [bp-30]	;code size = 4 - 8
clear_code  equ  word ptr [bp-32]	; = 1 << codesize = reinit the string table
eoi_code    equ  word ptr [bp-34]	; = clear_code + 1 = end of file marker
first_free  equ  word ptr [bp-36]	; = clear_code + 2 = First free code
codemax     equ  word ptr [bp-38]	; = 1 << (codesize+1)
srcofs      equ  word ptr [bp-40]	;Position in exbuff
buff_end    equ  word ptr [bp-42]	;Address of the end of the DTA buffer
StripCtr    equ  word ptr [bp-44]	;Strip counter
wcols	    equ  word ptr [bp-46]	;Columns of image buffer to write
wrows	    equ  word ptr [bp-48]	;Rows of image buffer to write
yctr        equ  word ptr [bp-50]	;Y counter for emgetrow()
BPStriphi   equ  word ptr [bp-52]	;Bytes per strip, hi word
BPStrip     equ  word ptr [bp-54]	;Bytes per strip, low word
getroutseg  equ           [bp-56]	;getrow() routine to use
getroutofs  equ           [bp-58]
gethandle   equ  word ptr [bp-60]	;Handle to use for getrow()

	public _WriteTif_
_WriteTif_  proc  far
	push bp
	mov bp,sp
	sub sp,60
	push di
	push si
	cld
;Initialize many variables
	mov ercode,NO_ERROR	;Assume no errors
	mov StripCtr,0		;Init strip counter
	call WrtFilePos		;Write file ptr position as StrStart[0]
;rows = srcimg->endy - srcimg->sty + 1
;Store Hbuff_ addr so it's accesible thru SS and point ES at buffer seg
	mov ax,seg _Hbuff_
	mov buffseg,ax
	mov es,ax		;ES = DTA segment
	mov ax,offset _Hbuff_
	mov buffofs,ax		;Store buffer's starting address
;Set buff_end
	add ax,StrSize		;When we get a strip, write it
	mov buff_end,ax		; (Strip size must be < MAXBUFSIZ)
	push ds
	lds di,srcimg		;DS:DI -> srcimg
	mov ax,[di].endy
	mov dx,[di].sty
	sub ax,dx
	inc ax
	mov wrows,ax
;yctr = srcimg->sty;
	mov yctr,dx
;cols = srcimg->endy - srcimg->sty + 1
	mov ax,[di].endx
	sub ax,[di].stx
	inc ax
	mov wcols,ax
;Initialize getroutine() and gethandle
	mov ax,[di]
	or ax,[di+2]	;Is srcimg->ibuff 0?
	jz WTTryEM	;If yes, jump to test for buffer in EM
;srcimg->ibuff != NULL, open cmhandle
;if((rcode=cmopen_(handle_addr, image->ibuff)) == NO_ERROR)
	push [di+2]	;Push ibuff seg, offset
	push [di]
	push ss		;Push handle seg, offset
	lea ax,gethandle
	push ax
	call _cmopen_	 ;Get CM handle
	add sp,8
	cmp ax,NO_ERROR
	jne WTCMBd
	mov cx,gethandle ;CX gets handle
	mov ax,offset _cmgetrow_
	mov dx,seg _cmgetrow_
	jmp short WTStPR
WTCMBd:	mov ercode,ax
	jmp short WTPBy2
;Test for buffer in EM
WTTryEM: mov cx,[di].ehandle
	or cx,cx	;Is desimg->ehandle == 0?
	jz WTinXM	;If yes, jump. Buffer must be in XM
	mov ax,offset _emgetrow
	mov dx,seg _emgetrow
	jmp short WTStPR
;ibuff and ehandle = 0, src must be in XM
WTinXM:	mov cx,[di].xhandle
	mov ax,offset _xmgetrow
	mov dx,seg _xmgetrow
WTStPR:	mov gethandle,cx
	mov getroutofs,ax
	mov getroutseg,dx
;Get a row of data to process from CM or EM
WTUsCM:	call GetNextRow		;Also initailizes BPRow and srcofs
	cmp ax,NO_ERROR
	jne NorXit
;Set DS to exbuff segment
	mov ds,xbuffseg
	mov cs:OurSP,sp		;Save SP for exit from Flush()
;Set up our LZW string table
	mov bx,WtStrTbofs  ;Set up hash table. We need 4096*5 (20480 bytes),
	mov cl,4	   ; so we allocated 4096*5+16 bytes in savetif prog
	shr bx,cl	   ;BX = ofs/16
	add bx,WtStrTbseg  ;BX = Seg = Seg + ofs/16 = Seg:0
	inc bx		   ;Make sure BX is within border
	mov hash_seg,bx    ;Save hash segment address
	mov cl,TCODSIZ	   ;CL gets code size to use
	call InitLZWvars   ;Initialize LZW constants
WTStripTop:
	cmp wrows,0		;More rows to process?
	jle NorXit		;Exit if no
	mov ax,StrSize		;Initialize bytes per strip
	mov BPStrip,ax
	call CompressLZW	;Compress a strip
	call WrtFilePos		;Write file ptr position as StrStart[StrCtr]
	cmp ercode,NO_ERROR	;Exit if error
	je WTStripTop
;Close CM handle, if issued: void cmclose_(imgdes *image, int cmhandle);
NorXit:	push gethandle
	lds di,srcimg	;DS:DI -> source image structure
	push ds		;Push desimg seg, offset
	push di
	call _cmclose_	;Close CM handle
	add sp,6
WTPBy2:	mov ax,ercode
	pop ds			;Return without closing file
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
;Disk full error handler
WDskFl:	mov sp,cs:OurSP		;Restore our SP for exit
	mov ercode,BAD_DSK	;Exit if error
	jmp short NorXit
_WriteTif_  endp

;Get the next row of data to process from CM or EM. Also, inc y counter, reset
; srcofs, and reset BPRow. Returns NO_ERROR, EMM_ERR, XMM_ERR, or CM_ERR.
; Uses AX, CX, DX, SI. Preserves DS, ES, DI, CX.
GetNextRow  proc  near
	push es		;Save seg regs since we call far routines
	push cx
	push ds		;Save exbuff seg
	lds si,srcimg	;SI -> srcimg
;Read row: getrow(exbuff, shndle, srcimg->stx, yctr, wcols, srcimg->iwidth);
	push [si].iwidth	;Move iwidth into AX 
	push wcols
	push yctr
	push [si].stx
	push gethandle
	push xbuffseg	;Push source seg, offset
	push xbuffofs
	call dword ptr getroutofs	;Any error code is returned in AX
	add sp,14
GNIcRw:	pop ds		;Restore exbuff seg
	pop cx		;Restore regs
	pop es		;AX has error code!
	inc yctr	;Inc row ctr
	mov si,wcols	;Reset bytes per row
	mov BPRow,si
	mov si,xbuffofs	;Reset srcofs to xbuffofs
	mov srcofs,si
	ret		;Return code is in AX!
GetNextRow  endp

;GetNextRow()
;{
;   int rcode;
;   rcode = getrow(exbuff, shandle, srcimg->stx, yctr, cols, srcimg->iwidth);
;   yctr++;	     /* Inc row ctr */
;   BPRow = Cols;    /* Reset bytes per row */
;   src = exbuff;    /* Reset src */
;   return(errcode);
;}

;Compress a strip ala LZW scheme.
; On entry ES -> DTA buffer seg, DS -> spic seg.
CompressLZW  proc  near
	call InitTable		;Initialize the table and some vars
	mov bit_offset,0
	mov ax,clear_code	;Write a clear code
	call WriteCode
	call GetChar		;Read first char
ComTop:	xor ah,ah		;Turn char into code
NewP:	mov prefix_code,ax	;Set prefix code
	call GetChar		;Read next char
	jc NoMoChars		;Carry means EOI
	mov k,al		;Save char in k
	mov bx,prefix_code	;Get prefix code
	call lookup_code	;See if this pair in table
	jnc NewP		;nc means yes, new code in ax
	call AddCode		;Add pair to table
	push bx			;Save new code
	mov ax,prefix_code	;Write old prefix code
	call WriteCode
	pop bx
	mov al,k		;Get last char
	cmp bx,max_code		;Exceed code size?
	jl ComTop		;Less means no
	cmp nbits,12		;Currently less than 12 bits?
	jl LT12			;Yes, jump
	mov ax,clear_code	;No, write a clear code
	call WriteCode
	call InitTable		; and reinit table
	mov al,k		;Get last char
	jmp short ComTop	;Start over
LT12:	inc nbits		;Increase number of bits
	shl max_code,1		;Double max code size
	inc max_code
	jmp short ComTop	;Get next char
NoMoChars:
	mov ax,prefix_code	;Write last code
	call WriteCode
	mov ax,eoi_code		;Write EOI code
	call WriteCode
	mov ax,bit_offset	;Make sure buffer is flushed to file
	or ax,ax
	jz CoXit		;It is, exit
	mov dx,ax		;Else convert bits to bytes
	and dx,7
	mov cl,3
	shr ax,cl
	or dx,dx		;Write any extra bits
	je NoXtraBits
	inc ax
NoXtraBits:
	call FlushBuff
CoXit:	ret
CompressLZW  endp

;Read a byte from the image buffer. Set CF = 1 if we're at the end of a
; strip, there's no more data to write, or there's an error condition. 
; On entry, ES = DTA seg, DS = exbuff seg. Return with char in AL.
; Uses AX, DX, SI.
GetChar  proc  near
;Are we done with a strip?
	dec BPStrip
	jl EOStrip  	;Jump if we're done with a strip
;Are we done with a row?
GTSmRw:	dec BPRow
	jl GTNwRw  	;Jump if we're done with a row
	mov si,srcofs	;DS:SI -> exbuff
	inc srcofs	;Inc srcofs for next access
	lodsb		;Get the char
	clc	    	;Success
	ret
;Done reading a row -- get the next one from CM or EM
GTNwRw:	dec wrows
	jle EOStrip	;Exit if no more rows to do
;Get a row of data to process from CM or EM
	call GetNextRow
	cmp ax,NO_ERROR
	je GTSmRw
	mov ercode,ax	;Exit if error
EOStrip: stc		;End of a strip, image, or error
	ret
GetChar  endp

;GetChar() {
;   if(--BPStrip >= 0) { 	/* Bytes in strip left to read */
;      if(--BPRow <= 0) {	/* Done with a row */
;         if(--row <= 0)	/* Done with image area? */
;            return(-1);	/* If yes, it's the end of a strip */
;      /* Get row also incs yctr, resets srcofs and BPRow */
;      if((errcode=GetNextRow) != NO_ERROR)
;         return(errcode);
;      return(ch = *src++);	/* Return the char */
;      }
;   else  return(-1);}	/* BPStrip <= 0 */

;Write a code to the output buffer by writing nbits bits and write buffer
; to disk when full. On entry, AX = code to write, ES = DTA seg,
; DS = exbuff seg. Uses AX, CX, DX, and DI.
WriteCode  proc  near
	push ax	 		;Save code
	mov ax,bit_offset	;Get bit offset
	mov cx,nbits 		;Adjust bit offset by code size
	add bit_offset,cx
	mov cx,ax		;Convert bit offset to byte offset
	and cx,7		;CX = offset within byte
	shr ax,1
	shr ax,1
	shr ax,1		;AX = bit_offset/8 = byte_offset
	mov di,buffofs		;DI -> beginning of DTA
	cmp ax,(BUFSIZE-4) 	;Approaching end of buffer?
	jge BufFull		;If yes, write it
	add di,ax		;Destination for byte
BufNotFull:
	pop ax			;Restore code
	mov dx,cx		;Save offset within a byte
	mov cx,16
	sub cx,nbits		;Keep only the valid bits
	shl ax,cl		;Left align code
	mov cx,dx	;Restore offset within a byte
	xor dx,dx	;DX will catch bits rotated out
	jcxz NoShft	;If offset in byte is zero (in CX), skip shift
WRot:	shr ax,1	;Rotate code
	rcr dl,1
	loop WRot
	or byte ptr es:[di],ah	;Grab bits currently in buffer
	inc di
	mov ah,dl		;Grab extra bits
	stosw			;Save data
	ret
NoShft: xchg ah,al		;Just store the values
	stosw
	ret
;File buffer is about full, write it
BufFull: push bx
	mov bx,ax		;BX = byte offset
	push es:[di+bx]		;Save the last byte
	call FlushBuff	 	;Write the buffer (AX = bytes to write)
	mov ax,cx	 	;CX = offset within byte
	add ax,nbits	 	;Adjust by code size
	mov bit_offset,ax	;New bit offset
	pop es:[di]		;Insert last byte in first position
	pop bx
	jmp short BufNotFull	;Byte offset = 0
WriteCode  endp

;Write output buffer to disk. Jump to error handler if disk is full or
; there's a write error. On entry, AX contains number of bytes to write.
; Uses AX, BX.
FlushBuff  proc  near
	push bx
	push cx
	push dx
	push ds
	lds dx,dword ptr buffofs
	mov cx,ax
	mov ah,WRTSQ
	mov bx,whandle		;BX = handle
	int 21h		 	;output record
	jc FlsBad		;Write error
	cmp ax,cx		;Check if no. of bytes requested was read
	jb FlsBad		;Exit if not
FlshOk: pop ds
	pop dx
	pop cx
	pop bx
	ret
FlsBad:	jmp WDskFl	;If disk full or write error jmp to handler
FlushBuff  endp		; SP problems handled by exit routine

;Set StripStarts[StripCtr] = file ptr position
; Uses AX.
WrtFilePos  proc  near
	push bx
	push cx
	push dx
	push ds
	mov ax,4201h		;Move file ptr fctn
	xor cx,cx		; move it 0:0 bytes
	xor dx,dx
	mov bx,whandle
	int 21h			; position of file pointer returned in dx:ax
	mov cx,StripCtr
	shl cx,1
	shl cx,1		;addr = _StartStarts[StripCtr*4]
	lds bx,StrStarts
	add bx,cx
	mov [bx+2],dx		;store as long int, dx=msw
	mov [bx],ax		;ax=lsw
	inc StripCtr		;Advance Strip counter
	pop ds
	pop dx
	pop cx
	pop bx
	ret
WrtFilePos  endp

;Initialize the hash table and some other variables
InitTable  proc  near
	call InitVars		;Initialize variables
	push es			;Save seg reg
	mov es,hash_seg		;Address hash table
	mov ax,-1		;Unused flag
	mov cx,640		;Clear first 256 entries (256*5=640)
	xor di,di		;Point to first entry
	rep stosw		;Clear it out
	pop es			;Restore seg reg
	ret
InitTable  endp

;See if prefix_code+k is in string table. On entry, AL = k and BX =
; prefix_code. Return with SI -> hash table addr and DI = 0 if first use
; of prefix, and, if code is in the table, CF = 1. If code is NOT in the
; table, return CF = 0 and AX = new code. Uses AX, BX, DI, SI.
lookup_code  proc  near
	push ds		        ;Save seg reg
	mov ds,hash_seg		;point to hash table
	call windex		;Convert code in BX to address in SI
	xor di,di		;Zero flag
	cmp [si].first,-1	;Has this code been used?
	je NotUsed		;Equal means no
	inc di			;Set flag
	mov bx,[si].first	;Get first entry
CkChar:	call windex		;Convert code in BX to address in SI
	cmp [si].char,al	;Is char the same?
	jne DifChr		;Not equal means no
	mov ax,bx		;Put found code in ax
	clc  			;Success
	pop ds
	ret
DifChr:	cmp [si].next,-1	;More left with this prefix?
	je NotUsed		;Equal means no
	mov bx,[si].next	;Get next code
	jmp short CkChar	;Try again
NotUsed: stc  			;Not found
	pop ds
	ret
lookup_code  endp

;Add prefix_code+k to string table. On entry, AX = new code, DI = 0 if
; first use of prefix, and SI -> hash table addr. Uses AX and BX.
AddCode  proc  near
	mov bx,free_code	;Get code to use
	push ds			;point to hash table
	mov ds,hash_seg
	or di,di		;First use of this prefix?
	jz FirstUse		;Zero => yes
	mov [si].next,bx	;Point last use to new entry
	jmp short CkLimit
FirstUse:
	mov [si].first,bx	;Point first use to new entry
CkLimit:
	cmp bx,4096	;Have we reached code limit (Max code + 1)?
	je AtLimit		;Yes, just return
	call windex		;No, convert code in BX to address in SI
	mov [si].first,-1	;Initialize pointers
	mov [si].next,-1
	mov [si].char,al	;Save suffix char
	inc free_code		;Adjust next code
AtLimit:
	pop ds
	ret
AddCode  endp

;Convert code in BX to address in SI
windex   proc  near
	mov si,bx		;si = bx * 5 (5 byte hash entries)
	shl si,1		;si = bx * 2 * 2 + bx
	shl si,1
	add si,bx
	ret
windex   endp

	.DATA
;Mask values for bits per sample = 1 to 8
exmasks	DB  10000000b, 11000000b, 11100000b, 11110000b
	DB  11111000b, 11111100b, 11111110b, 11111111b

	.CODE
;Expand a line of 1 to 8-bit packed data into 8-bit data. Return the number
; of packed bytes read. For 4-bit data: abcdefgh -> abcd0000 efgh0000, for
; 6-bit: abcdefgh ijklmnop qrstuvwx -> abcdef00 ghijkl00 mnopqr00 stuvwx00
; Usage: byts_rd = expand_(UCHAR *des, UCHAR *src, int des_byts, int bps);
exdes   equ dword ptr @ab[bp]	;Where to put the expanded data
exsrc   equ dword ptr @ab[bp+4]	;Source of data to expand
desbyts	equ  word ptr @ab[bp+8]	;Uncompressed bytes to write
btspsam equ  word ptr @ab[bp+10];Bits per sample used
maskval equ  byte ptr [bp-2]
	public _expand_
_expand_  proc  far
	push bp
	mov bp,sp
	dec sp
	dec sp
	push di
	push si
	cld
;Set mask value
	mov bx,btspsam
	dec bx		;Assume bits per sample = 1 to 8
	mov al,exmasks[bx]
	mov maskval,al	;mask_val = masks[bits_psam-1];
	xor dx,dx	;bit_ofs = 0;
	les di,exdes	;ES:DI -> des
	push ds
	lds si,exsrc	;DS:SI -> src
exptop:	dec desbyts
	jl expbye
;Calc byte offset
	mov bx,dx
	shr bx,1
	shr bx,1
	shr bx,1	;BX = byte_ofs = bit_ofs / 8;
;Get a word of data
	mov ax,[si+bx]	;ch = *(unsigned *)&src[byte_ofs];
	xchg al,ah	;Exchange bytes so this works
;Calc offset in byte
	mov cl,8
	mov ch,dl	;CL = bit_ofs
	and ch,7	;CH = offset in byte
	sub cl,ch	;CL = 8 - ofs in byte
;Shift word into position
	shr ax,cl	;val >>= (8 - (bit_ofs & 7));
;Mask off unused bits
	and al,maskval
	stosb
;Calc new bit offset
	add dx,btspsam
	jmp short exptop
expbye:	mov ax,bx
	inc ax		;Return bytes read (byte_ofs+1)
	pop ds
	pop si
	pop di
	mov sp,bp
	pop bp
	ret
_expand_  endp

;int expand_(des, src, des_byts, bits_psam)
;UCHAR *des;	/* Where to put the expanded data */
;UCHAR *src;	/* Source of data to expand */
;int des_byts;	/* Uncompressed bytes to write */
;int bits_psam;	/* Bits per sample, 4 - 8 */
;{  /* For BPS of 3 - 8 */
;   static UCHAR masks[] = {0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
;   unsigned ch;
;   int byte_ofs, mask_val, int bit_ofs=0;
;   mask_val = masks[bits_psam-4];
;   while(des_byts--) {
;      byte_ofs = bit_ofs / 8;		/* Calc byte offset */
;      ch = src[byte_ofs] | (src[byte_ofs+1] << 8);
;      ch >>= (8 - (bit_ofs & 7)); /* Calc offset in byte, shift to position */
;      /* Mask off unused bits */
;      *des++ = ch & mask_val;
;      bit_ofs += bits_psam;	/* Calc new bit offset */
;      }
;   return(byte_ofs+1);		/* Return bytes read */
;}

;Swap bytes for count bytes. Used to read a Motorola TIF image header.
; Usage: void swapbytes_(void *bptr, int count); A B -> B A
swbptr  equ dword ptr @ab[bp]	;Source/dest of bytes to swap
swcount equ  word ptr @ab[bp+4]	;Bytes to swap
	public _swapbytes_
_swapbytes_  proc  far
	push bp
	mov bp,sp
	push ds
	mov cx,swcount
	shr cx,1	;count /= 2;
	jcxz SWSxit	;Exit if no bytes to swap
	lds bx,swbptr	;DS:BX -> Source/dest of bytes to swap
SWBTop:	mov ax,[bx]	;Get an int
	xchg ah,al	;Swap the bytes
	mov [bx],ax	;Put the int
	inc bx
	inc bx		
	loop SWBTop
SWSxit:	pop ds
	pop bp
	ret
_swapbytes_  endp

;void swapbytes_(void *bptr, int count)
;{
;   union {char chr[2]; unsigned wrd; } dint;
;   count /= 2;
;   while(count--) {
;      dint.wrd = *(unsigned *)bptr;
;      *bptr++   = *(char *)(dint.chr + 1);
;      *bptr++   = *(char *)(dint.chr);
;      }
;}

;Swap words for count bytes. Used to read a Motorola TIF image header.
; Usage: void swapwords_(void *bptr, int count); A B C D -> D C B A
swwptr  equ dword ptr @ab[bp]	;Source/dest of words to swap
swcount equ  word ptr @ab[bp+4]	;Bytes to swap
	public _swapwords_
_swapwords_  proc  far
	push bp
	mov bp,sp
	push ds
	mov cx,swcount
	shr cx,1
	shr cx,1	;count /= 4;
	jcxz SWWxit	;Exit if no words to swap
	lds bx,swwptr	;DS:BX -> Source/dest of words to swap
SWWTop:	mov ax,[bx]	;Get first int
	mov dx,[bx+2]	;Get second int
	xchg ah,al	;Swap the bytes
	xchg dh,dl	;Swap the bytes
	mov [bx],dx	;Put the second int first
	mov [bx+2],ax	;Put the first int second
	add bx,4
	loop SWWTop
SWWxit:	pop ds
	pop bp
	ret
_swapwords_  endp

;void swapwords_(void *bptr, int count)
;{
;   union {char chr[4]; long lng; } dwrd;
;   count /= 4;
;   while(count--) {
;      dwrd.lng = *(long *)bptr;
;      *bptr++   = *(char *)(dwrd.chr + 3);
;      *bptr++   = *(char *)(dwrd.chr + 2);
;      *bptr++   = *(char *)(dwrd.chr + 1);
;      *bptr++   = *(char *)(dwrd.chr);
;      }
;}
	END
+ARCHIVE+ xmsasm.asm   25335  2/03/1991  7:18:26
; Victor Library, Copyright (c) 1990-1991, ALL RIGHTS RESERVED
;	Catenary Systems
;	470 Belleview
;	St Louis, MO 63119
;	(314) 962-7833
; Contents:
;	xmallocate	Allocate pages of extended memory
;	xmavailable	Return kilobytes of XM available
;	xmdetect	Check that the XMM is installed and functional
;	xmerror		Report latest XMM error code
;	xmfree		Free extended memory owned by handle
;	xmgetrow	Copy a row in extended memory to conventional memory
;	xmkbytesowned	Return number of kilobytes owned by handle
;	xmputrow	Copy a row in conventional memory to extended memory
;	xmversion	Get XMM version number
;	xmsetbyte_	Write byte to image buffer in XM
;	xmgetbyte_	Read byte in image buffer in XM

	.MODEL	LARGE
@ab	equ	6	;Equate for large model

;Error codes:
NO_ERROR   EQU	  0	;No error
NO_XMM	   EQU	-23	;Extended memory manager not found
XMM_ERR	   EQU	-24	;Extended memory manager error

;	Native XMS error codes:
;a0h All extd mem already allocated	;80h Function not implemented
;a1h Extended mem handles exhausted	;81h VDISK device driver detected
;a2h Invalid handle		   	;82h A20 error occurred
;a3h Invalid source handle		;8eh General driver error
;a4h Invalid source offset		;8fh Uncoverable driver error
;a5h Invalid dest handle		;90h High mem area does not exist
;a6h Invalid dest offset		;91h High mem area already in use
;a7h Invalid length			;92h DX < /HMAMIN=value
;a8h Invalid overlap in move request	;93h High memory area not allocated
;a9h Parity error detected		;94h A20 line still enabled
;aah Block not locked
;abh Block is locked
;ach Lock count overflowed
;adh Lock failed
;b0h Smaller unlocked memory blk available
;b1h No unlocked memory blocks available
;b2h Invalid unlocked memory block segment number

;Move extended memory block parameter block
xm_movepb  struc
byts2move DD  ?	;Number of bytes to move. NOTE: Length must be even!
shndle    DW  ?	;Source handle, 0 => source is in conventional memory
soffset   DD  ?	;Source offset, either a normal far pointer if CM or 
dhndle    DW  ?	; a 32-bit offset if XM
doffset   DD  ?	;Destination offset: For CM a far ptr, for XM a 32-bit no.
xm_movepb  ends

	.DATA
	EVEN
xmmaddr      dd  0	;XMM entry point address
;Our move memory parameter block
xmmove  xm_movepb  <0, 0, 0, 0, 0>
xdummy       dw  0	;Dummy variable
atbeg        db  0	;If 1, at the beginning of an allocated blk
xmm_errcode  db  0	;XMM native error codes are stored here
xmm_flag     db  0	;Nonzero if XMM entry point address is installed

	.CODE
;Report latest XMM error code. Usage: errcode = xmerror_(void);
	public _xmerror
_xmerror  proc  far
	push bp
	push ds
	mov ax,@Data
	mov ds,ax
	mov al,xmm_errcode
	xor ah,ah
	pop ds
	pop bp
	ret
_xmerror  endp

;Allocate extended memory. Return NO_ERROR, NO_XMM, or XMM_ERR. Must 
; call xmdetect() prior to xmallocate() to set up xmmaddr. If XMM_ERR is 
; returned, additional error information is available from xmerror().
; Usage: int xmallocate(int *handle, int kbyts);
handle_addr  equ dword ptr @ab[bp]   	;XMM handle value
xmm_kbyts    equ  word ptr @ab[bp+4]	;Kilobytes to allocate
	public _xmallocate
_xmallocate  proc  far
	push bp
	mov bp,sp
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz EAMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne EAXit		;XMM not installed
EAMan:	mov ah,9		;Function 9
	mov dx,xmm_kbyts	;DX = Kb's requested
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz EAErr		;XMM error
	les bx,handle_addr
	mov es:[bx],dx		;DX = XMM handle value
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in xmm_errcode
EAXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop bp
	ret
EAErr:	mov ax,XMM_ERR		;XMM error
	jmp short EAXit
_xmallocate  endp

;Return the number of Kb available in the largest free block, NO_XMM, or 
; XMM_ERR. Additional error information is available from xmerror() if 
; XMM_ERR is returned. Usage: Kbs = xmavailable(void);
	public _xmavailable
_xmavailable  proc  far
	push bp
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz XAMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne XAXit		;XMM not installed
;Get available Kbs
XAMan:	mov ah,8
	call dword ptr xmmaddr	;DX = total available, AX = largest free block
	or ax,ax		;AX != 0 if success
	jz XAErr		;Jump if error
	xor bl,bl		;Store 0 in xmm_errcode
XAXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds			;Return largest free block or error code
	pop bp
	ret
XAErr:	mov ax,XMM_ERR		;XMM error
	jmp short XAXit
_xmavailable  endp

;Check that the XMM is installed and return the entry point address.
; Return NO_ERROR or NO_XMM in AX, native XMM errcode in BL.
; Usage: int xmdetect(void);
	public _xmdetect
_xmdetect  proc  far
	push bp
	push ds
	mov ax,@Data
	mov ds,ax
;Xmm_flag is nonzero if XMM entry point address is already installed
	cmp xmm_flag,0
	jnz EMDxt1	;No need to continue
;Check for XMM driver using the multiplex interrupt
	mov ax,4300h
	int 2fh
	cmp al,80h		;80h if present
	jne NoXMM		;Not present, exit
;Save entry point to XMM driver
	mov ax,4310h
	int 2fh
	mov word ptr xmmaddr,bx
	mov word ptr xmmaddr+2,es
	mov xmm_flag,1	;Nonzero if XMM entry point address is installed
EMDxt1:	mov ax,NO_ERROR	;Success, return NO_ERROR
EMDxt2:	xor bl,bl	;Store 0 in xmm_errcode
	pop ds
	pop bp
	ret
NoXMM:	mov ax,NO_XMM	;No XMM, set XMM errcode = 0
	jmp short EMDxt2
_xmdetect  endp

;Free extended memory owned by handle. Return NO_ERROR, NO_XMM, or XMM_ERR.
; Additional error information is available from xmerror().
; Usage: int xmfree(int handle);
xmm_handle equ	word ptr @ab[bp]	;XMM handle
trynum     equ  word ptr [bp-2]		;Counter
	public _xmfree
_xmfree  proc  far
	push bp
	mov bp,sp
	sub sp,2
	push ds
	mov ax,@Data
	mov ds,ax
	mov trynum,255		;Try releasing memory 255 times
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz XFNxt		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne XFXit		;XMM not installed
;Get available Kbs
XFNxt:	mov ah,0ah
	mov dx,xmm_handle	;XMM handle
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz XFErr		;If XMM error, try again - up to 255 times
	mov ax,NO_ERROR
	xor bl,bl		;Store 0 in xmm_errcode
XFXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	mov sp,bp
	pop bp
	ret
XFErr:	dec trynum		;Try releasing memory 255 times
	jnz XFNxt		;If not zero, try again
	mov ax,XMM_ERR		;XMM error
	jmp short XFXit
_xmfree  endp

;Return number of kilobytes of XM owned by handle, NO_XMM, or XMM_ERR.
; Additional error information is available from xmerror().
; Usage: int xmkbytesowned(int handle);
xmm_handle  equ  word ptr @ab[bp]   	;XMM handle value
	public _xmkbytesowned
_xmkbytesowned  proc  far
	push bp
	mov bp,sp
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz POMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne POXit		;XMM not installed
;Get available Kbs
POMan:	mov ah,0eh		;Function 0x0e
	mov dx,xmm_handle
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz POErr		;XMM error
	mov ax,dx		;DX = kilobytes owned by handle
	xor bl,bl
POXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds			;Return kilobytes value (0 -> 2048)
	pop bp
	ret
POErr:	mov ax,XMM_ERR		;XMM error
	jmp short POXit
_xmkbytesowned  endp

;Get XMM version number. Return the version number as a BCD number (i.e., 
; 0x0200 = 2.0), NO_XMM, or XMM_ERR. Additional error information is 
; available from xmerror(). Note: call xmdetect() first to ensure that an 
; XMM is installed and save it's entry point.
; Usage: version = xmversion(void);
	public _xmversion
_xmversion  proc  far
	push bp
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz EVMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne EVxit		;XMM not installed
;Get available Kbs
EVMan:	xor ah,ah		;Use function 0
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz EVErr		;XMM error, exit
	xor bl,bl
EVxit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop bp
	ret
EVErr:	mov ax,XMM_ERR		;XMM error
	jmp short EVxit
_xmversion  endp

;Get the value of a byte in XM at (stx,yctr). Does not check that XM
; exists or that xhandle owns any XM. Call xmdetect() before calling this 
; function to save the XMM driver's entry address. Returns the byte's 
; value (0-255), NO_XMM, or XMM_ERR.
; Usage: int xmgetbyte_(int xhandle, int stx, int yctr, int xwidth);
gxhandle  equ  word ptr   @ab[bp]	;XM handle
gxstx     equ  word ptr @ab[bp+2]
gxyctr    equ  word ptr @ab[bp+4]
gxwidth   equ  word ptr @ab[bp+6]	;XM buffer width

	public _xmgetbyte_
_xmgetbyte_  proc  far
	push bp        
	mov bp,sp     
	push si
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz XbMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne XbXit		;XMM not installed
;Get the value of 2 bytes in XM at (stx,yctr). Store results in xdummy.
XbMan:	call xmgettwo
	jc XbErr	;CF = 1 if error
;Byte to return is in xdummy
	mov ax,xdummy
;If atbeg = 1, saddr = 0, so return left byte,
; else saddr != 0, so return right byte
	cmp atbeg,1
	je GPXbg
	mov al,ah  ;Not at beginning (saddr != 0), byte we want is in AH
GPXbg:	xor ah,ah  		;Return color value in AX
XbXt:	xor bl,bl		;BL = XMM error code = 0
XbXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop si
	pop bp
	ret
;Error occured in xmgettwo()
XbErr:	mov ax,XMM_ERR		;XMM error
	jmp short XbXit
_xmgetbyte_  endp

;Set the value of a byte in XM at (stx,yctr). Does not check that XM exists
; or that xhandle owns any XM. Call xmdetect() before calling this function to
; save the XMM driver's entry address. Returns NO_ERROR, NO_XMM, or XMM_ERR.
; Usage: int xmsetbyte_(int xhndle, int stx, int yctr, int xwidth, int color);
gxhandle  equ  word ptr   @ab[bp]	;XM handle
gxstx     equ  word ptr @ab[bp+2]
gxyctr    equ  word ptr @ab[bp+4]
gxwidth   equ  word ptr @ab[bp+6]	;XM buffer width
gxcolor   equ  byte ptr @ab[bp+8]

	public _xmsetbyte_
_xmsetbyte_  proc  far
	push bp        
	mov bp,sp     
	push si
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz SpMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne SpXit		;XMM not installed
;The strategy is to read two bytes, change the byte at (stx,yctr) and
; then write two bytes. If stx,yctr = 0,0, read bytes 0 and 1, replace
; byte 0 with color, and write bytes 0 and 1. Else read bytes at stx-1,
; yctr and stx,yctr, replace byte stx,yctr with color, and write bytes
; stx-1,yctr and stx,yctr.
;Get the value of 2 bytes in XM at (stx,yctr). Store result in xdummy.
SpMan:	call xmgettwo
	jc SpErr		;CF = 1 if error
	mov si,offset xmmove	;Be safe
	mov bl,gxcolor		;BL = color to set
;Byte read is in xdummy
	mov ax,xdummy
;If atbeg == 1, replace left byte with color
	cmp atbeg,1
	je SPbg
	mov ah,bl	;Not at beginning, AH gets color
      	jmp short SPCnt
;If atbeg != 1, replace right byte with color
SPbg:	mov al,bl	;At beginning, AL gets color
SPCnt:	mov xdummy,ax	;Store 2 bytes to write in xdummy
;Write 2 bytes - reverse source and dest fields
; Swap handles
	mov word ptr [si].shndle,0	;Source is in CM
	mov ax,gxhandle
	mov [si].dhndle,ax
;Swap source and dest offsets
	mov ax,word ptr [si].soffset
	mov word ptr [si].doffset,ax
	mov dx,word ptr [si+2].soffset
	mov word ptr [si+2].doffset,dx
	mov bx,offset xdummy
	mov word ptr [si].soffset,bx
	mov word ptr [si+2].soffset,ds
;Finally, write the 2 bytes
	mov ah,0bh	;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz SpErr		;Jump if error
	mov ax,NO_ERROR
	xor bl,bl		;BL = XMM error code = 0
SpXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop si
	pop bp
	ret
SpErr:	mov ax,XMM_ERR		;XMM error
	jmp short SpXit
_xmsetbyte_  endp

;Get the value of 2 bytes in XM at (stx,yctr). Used by xmsetbyte() and
; xmgetbyte(). Puts 2 bytes values in xdummy. Returns with CF = 1 if XMM
; error. Uses AX, BX, CX, DX, SI.
xmgettwo  proc  near
	mov atbeg,1	;Set "at beginning" flag to true
;Point SI at our move memory parameter block
	mov si,offset xmmove
;We read 2 bytes, since only an even no. of bytes can be read
	mov ax,2
	cwd
	mov word ptr [si].byts2move,ax
	mov word ptr [si+2].byts2move,dx	;DX = 0
;Set the source and destination handles
	mov ax,gxhandle
	mov [si].shndle,ax
	mov word ptr [si].dhndle,dx	;des handle = 0, since des is in CM
;Store des offset - use xdummy variable in CM
	mov bx,offset xdummy
	mov word ptr [si].doffset,bx
	mov word ptr [si+2].doffset,ds
;Calculate source offset in XM: DX:AX = saddr = stx + sty * (long)width;
	mov ax,gxyctr
	mul gxwidth
	add ax,gxstx
	adc dx,0		;DX:AX = saddr
;If saddr == 0, read bytes 0 and 1
	mov cx,ax
	or cx,dx
	jz Gp2Zr
;If saddr != 0, read bytes saddr-1 and saddr
	sub ax,1	;saddr--;
	sbb dx,0	;Check for borrow
	mov atbeg,0	;Clear "at beginning" flag
Gp2Zr:	mov word ptr [si].soffset,ax
	mov word ptr [si+2].soffset,dx
;Call move extended memory block function
	mov ah,0bh	;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax	;AX != 0 if success
	jz GP2_Er	;Jump if error
;Byte to return is in xdummy
	clc		;Success!
	ret
GP2_Er:	stc		;XMM or NO_XMM error
	ret
xmgettwo  endp

;Copy 'byts2mov' bytes from a row in extended memory (XM) to conventional
; memory (CM). Buffer to receive the row must be large enough to hold
; 'byts2mov' bytes. Does not check that XM exists or that handle owns any
; XM. Call xmdetect() or xmdetect() prior to calling this function to 
; confirm that XM is present and save the XMM driver's entry address.
; Returns NO_ERROR, NO_XMM, or XMM_ERR if XM error.
; Usage: int xmgetrow(void huge *des, int xhandle, int stx, int yctr,
; int byts2mov, int width);
desbuff	  equ	          @ab[bp]	;Destination for data
xhandle   equ  word ptr @ab[bp+4]
gstartx   equ  word ptr @ab[bp+6]	;XM buffer dimensions:
gyctr     equ  word ptr @ab[bp+8]
gbyts2mov equ  word ptr @ab[bp+10]
gwidth    equ  word ptr @ab[bp+12]

	public _xmgetrow
_xmgetrow  proc  far
	push bp        
	mov bp,sp     
	push si
	push ds
	mov ax,@Data
	mov ds,ax
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz XgMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne XgXit		;XMM not installed
;Point SI at our move memory parameter block
XgMan:	mov si,offset xmmove
	xor cx,cx		;CX = 0
;Set the bytes to move
	mov ax,gbyts2mov
	and ax,0fffeh		;Only even lengths are allowed
	mov word ptr [si].byts2move,ax
	mov word ptr [si+2].byts2move,cx	;CX = 0
;Set the source and destination handles
	mov ax,xhandle
	mov [si].shndle,ax
	mov word ptr [si].dhndle,cx	;des handle = 0, since des is in CM
;Calculate source offset in XM: saddr = stx + sty * (long)width;
	mov ax,gyctr
	mul gwidth
	add ax,gstartx
	adc dx,0		;DX:AX = saddr
;Store source offset as the 32-bit value
	mov word ptr [si].soffset,ax
	mov word ptr [si+2].soffset,dx
;Store des offset -- since it's in CM, store the far ptr
	les bx,dword ptr desbuff
	mov word ptr [si].doffset,bx
	mov word ptr [si+2].doffset,es
;Call move extended memory block function
	mov ah,0bh	;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax	;AX != 0 if success
	jz XG_Err	;Jump if error
;If byts2mov is even, we're done
	test byte ptr gbyts2mov,1 ;Test for an odd length
	jnz B2mOdd
XgGgX:	mov ax,NO_ERROR
	xor bl,bl		;BL = XMM error code = 0
XgXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop si
	pop bp
	ret
;Byts2mov is odd, we need to read one more byte
B2mOdd:	mov si,offset xmmove	;SI -> move memory parameter block
	mov cx,word ptr [si].byts2move	;CX = bytes just moved
;If byts moved == 0, use _xmgetbyte_(handle, stx, sty, xwidth) to
; read the first byte
	or cx,cx
	jz GIsZr
;Otherwise, update src and dest and write 2 bytes
; Update src: src = src + byts2mov - 1;
	dec cx				;CX = bytes just moved - 1
	add word ptr [si].soffset,cx	;CX = bytes just moved - 1
	adc word ptr [si+2].soffset,0	;Adjust for any overflow
;Update dest: des = des + byts2mov - 1;
	add word ptr [si].doffset,cx	;src = src + byts2mov - 1;
	sbb ax,ax	 		;Pick up carry (AX = 0 or 0ffffh)
	and ax,1000h			;AX = 0 or 1000h
	add word ptr [si+2].doffset,ax	;Add 0 or 1000h to seg
;Move 2 bytes
	mov ax,2
	mov word ptr [si].byts2move,ax	;MSW should still be 0
;Call move extended memory block function
	mov ah,0bh		;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jnz XgGgX		;Jump if no error
XG_Err:	mov ax,XMM_ERR		;XMM error
	jmp short XgXit
;Bytes moved == 0, use _xmgetbyte_(handle, stx, sty, xwidth)
; to read the last byte
GIsZr:	push gwidth
	push gyctr
	push gstartx
	push xhandle
	push cs	      	;Push CS so we can use a near call
	call near ptr _xmgetbyte_
	add sp,8
	cmp ax,XMM_ERR
	je XG_Err
	les bx,dword ptr desbuff	;ES:BX -> des
;*src = _xmgetbyte_(handle, stx, sty, xwidth);
	mov byte ptr es:[bx],al
	jmp short XgGgX		;Exit
_xmgetrow  endp

;int xmgetrow(void huge *des,int xhandle, int stx,int sty,int cols,int width)
;{
;   xm_movepb xmblk;	/* Allocate space for parameter block */
;   long saddr;
;   int atbeg=0;	/* Clear flag to keep lsb */
;   /* Set the bytes to move (only even lengths are allowed) */
;   xmblk.byts2move = (ULONG)(byts2mov & 0xfffe);
;   /* Set the source and dest handles */
;   xmblk.shndle = xhandle;
;   xmblk.dhndle = 0;	/* Dest handle = 0, since des is in CM */
;   /* Calc source offset in XM */
;   saddr = stx + sty * (long)iwidth;
;   xmblk.soffset = saddr;	/* Store the 32-bit number */
;   /* Store des offset -- since it's in CM, store the far pointer */
;   xmblk.doffset = des;	/* Store the far pointer */
;   /* Call move extended memory block function */
;   if((rcode=sendit(&xmblk) != NO_ERROR)
;      goto xm_ferr;
;   /* If byts2mov is even, we're done */
;   if((byts2mov & 1) == 0)
;      return(rcode);
;   /* Byts2mov is odd, read 1 more byte */
;   /* If bytes moved = 0, use xmgetbyte() */
;   if(xmblk.byts2move == 0) {
;      if(*des = xmgetbyte(xhandle, stx, sty, width)) == XMM_ERR)
;         goto xm_ferr;
;      }
;   else { /* Else, update src and dest and read 2 bytes */
;      xmblk.byts2move = 2;
;      saddr += xmblk.byts2move - 1;
;      xmblk.soffset = saddr;		/* Store the 32-bit number */
;      des += xmblk.byts2move - 1;
;      xmblk.doffset = des;		/* Store the 32-bit number */
;      if((rcode=sendit(&xmblk) != NO_ERROR)
;         goto xm_ferr;
;      }
;   return(NO_ERROR);
;xm_ferr:
;   return(XMM_ERR);
;}

;Copy 'byts2mov' bytes from a row in conventional memory (CM) to extended
; memory (XM). Does not check that XM exists or that 'handle' owns any XM. 
; Call xmdetect() prior to calling this function to confirm that XM is 
; present and save the XMM driver's entry address. Returns NO_ERROR, NO_XMM,
; or XMM_ERR. Usage:int xmputrow(void huge *src, int handle, int stx, 
; int sty, int cols, int width);
srcbuff	  equ		  @ab[bp]	;Source of for data
xhandle   equ  word ptr @ab[bp+4]
pstartx   equ  word ptr @ab[bp+6]	;XM buffer dimensions:
pyctr     equ  word ptr @ab[bp+8]
pbyts2mov equ           @ab[bp+10]
pwidth    equ  word ptr @ab[bp+12]

	public _xmputrow
_xmputrow  proc  far
	push bp        
	mov bp,sp     
	push si
	push ds
	mov si,@Data
	mov ds,si
;Check that XMM entry point was saved
	cmp xmm_flag,0
	jnz XpMan		;XMM installed, continue
	push cs			;Push CS so we can use a near call
	call near ptr _xmdetect	;Try to save entry point
	cmp ax,NO_ERROR
	jne XpXit		;XMM not installed
;Point DS:SI at our move memory parameter block
XpMan:	mov si,offset xmmove
	xor cx,cx		;CX = 0
;Set the bytes to move
	mov ax,pbyts2mov
	and ax,0fffeh		;Only even lengths are allowed
	mov word ptr [si].byts2move,ax
	mov word ptr [si].byts2move[2],cx	;CX = 0
;Set the source and destination handles
	mov ax,xhandle
	mov [si].dhndle,ax
	mov word ptr [si].shndle,cx	;src handle = 0, since src is in CM
;Store source offset -- since it's in CM, store the far ptr
	les bx,dword ptr srcbuff
	mov word ptr [si].soffset,bx
	mov word ptr [si].soffset[2],es
;Calculate dest offset in XM: daddr = stx + sty * (long)width;
	mov ax,pyctr
	mul pwidth
	add ax,pstartx
	adc dx,0		;DX:AX = saddr
;Store dest offset as the 32-bit value
	mov word ptr [si].doffset,ax
	mov word ptr [si].doffset[2],dx
;Call move extended memory block function
	mov ah,0bh		;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jz XP_Err		;Jump if error
;If byts2mov is even, we're done
	test byte ptr pbyts2mov,1 ;Test for an odd length
	jnz Pb2mOd
XpGgX:	mov ax,NO_ERROR
	xor bl,bl		;BL = XMM error code = 0
XpXit:	mov xmm_errcode,bl	;Store XMM error code
	pop ds
	pop si
	pop bp
	ret
;Byts2mov is odd, we need to write one more byte
Pb2mOd: mov si,offset xmmove		;SI -> move memory parameter block
	mov cx,word ptr [si].byts2move	;CX = bytes just moved
;If byts moved == 0, use _xmputbyte_(handle, stx, sty, xwidth, color) to
; write the last byte
	or cx,cx
	jz PIsZr
;Otherwise, update src and dest and write 2 bytes
; Update src: src = src + byts2mov - 1;
	dec cx				;CX = bytes just moved - 1
	add word ptr [si].soffset,cx	;src = src + byts2mov - 1;
	sbb ax,ax	 		;Pick up carry (AX = 0 or 0ffffh)
	and ax,1000h			;AX = 0 or 1000h
	add word ptr [si+2].soffset,ax	;Add 0 or 1000h to seg
;Update dest: des = des + byts2mov - 1;
	add word ptr [si].doffset,cx	;CX = bytes just moved - 1
	adc word ptr [si+2].doffset,0	;Adjust for any overflow
;Move 2 bytes
	mov ax,2
	mov word ptr [si].byts2move,ax	;MSW should still be 0
;Call move extended memory block function
	mov ah,0bh		;Call with AH = 0xb, DS:SI->parameter block
	call dword ptr xmmaddr	;Call XMM only if flag is set
	or ax,ax		;AX != 0 if success
	jnz XpGgX		;Jump if no error
XP_Err:	mov ax,XMM_ERR		;XMM error
	jmp short XpXit
;Bytes moved == 0, use _xmsetbyte_(handle, stx, sty, xwidth, color)
; to write the last byte
PIsZr:	les bx,dword ptr srcbuff	;ES:BX -> src
	push es:[bx]	;Push color, color = src[0] since byts moved = 0
	push pwidth
	push pyctr
	push pstartx
	push xhandle
	push cs	      	;Push CS so we can use a near call
	call near ptr _xmsetbyte_
	add sp,10
	cmp ax,NO_ERROR
	jne XP_Err
	jmp short XpGgX		;Exit
_xmputrow  endp

;int xmputrow(void huge *src,int xhandle, int stx,int sty,int cols,int width)
;{
;   xm_movepb xmblk;	/* Allocate space for parameter block */
;   long daddr;
;   /* Set the bytes to move (only even lengths are allowed) */
;   xmblk.byts2move = (ULONG)(byts2mov & 0xfffe);
;   /* Set source and des handles */
;   xmblk.shndle = 0;	/* 0 => source is in CM */
;   xmblk.dhndle = xhandle;
;   /* Store source offset - since it's in CM, store the far ptr */
;   xmblk.soffset = src;
;   /* Calc dest offset in XM and store the 32-bit number */
;   xmblk.doffset = daddr = stx + sty * (long)iwidth;
;   /* Call move extended memory block function */
;   if((rcode=sendit(&xmblk) != NO_ERROR)
;      goto xm_ferr;
;   /* If byts2mov is even, we're done */
;   if((byts2mov & 1) == 0)
;      return(NO_ERROR);
;   /* Byts2mov is odd, write 1 more byte */
;   /* If bytes moved = 0, use xmsetbyte() */
;   if(xmblk.byts2move == 0) {
;      if(xmsetbyte(xhandle, stx, sty, width, *src)) != NO_ERROR)
;         goto xm_ferr;
;      }
;   else { /* Else, update src and dest and write 2 bytes */
;      xmblk.byts2move = 2;
;      src += xmblk.byts2move - 1;
;      xmblk.soffset = src;		/* Store the 32-bit number */
;      daddr += xmblk.byts2move - 1;
;      xmblk.doffset = daddr;		/* Store the 32-bit number */
;      if((rcode=sendit(&xmblk) != NO_ERROR)
;         goto xm_ferr;
;      }
;   return(NO_ERROR);
;xm_ferr:
;   return(XMM_ERR);
;}

	END
