	page	74,132
        title   ZIPVIEW - Verbose ARC directory listing

; usage:
;
;       CALL ARCV (Workname$,"filename[.PAK]", RETCD%)


STDOUT  equ     1                       ;Standard Output
STDERR  equ     2                       ;Std Error (console)
FALSE	equ	0
TRUE	equ	NOT FALSE
DEBUG	equ	FALSE

Print	macro	name			; display a field
	mov	dx,offset name
	call	PrintS
	endm

header  struc				; archive header
aMbrflag        db      1AH             ;unique ARC/PAK flag
aCmpMeth	db	0		;  compression code
aMbrName	db	13 dup (0)	;  file name
aCmpSiz		dw	0,0		;  file size in archive
aModDate	dw	0		;  creation date
aModTime	dw	0		;  creation time
aCrc16		dw	0		;  cyclic redundancy check
aUncmpSiz	dw	0,0		;  true file size, bytes
header  ends

ARCHDRLEN       equ     29              ;size of ARC/PAK header.

;ZIP Local file header structure:

zLocalEntry	STRUC
  
zdig0	db	50H,4BH,03H,04H	;local file header signature	4 bytes
				;(0x04034b50)
zVerMade	dw	?	;version needed to extract	2 bytes
zBitflag	dw	?	;general purpose bit flag	2 bytes
zCmpMeth	dw	?	;compression method		2 bytes
zModTime	dw	?	;last mod file time 		2 bytes
zModDate	dw	?	;last mod file date		2 bytes
zCrc32		dw	?,?	;crc-32   			4 bytes
zCmpSiz		dw	?,?	;compressed size		4 bytes
zUncmpSiz	dw	?,?	;uncompressed size		4 bytes
zNameLen	dw	?	;filename length		2 bytes
zExtraLen	dw	?	;extra field length		2 bytes
zMbrName	db	?	;filename (variable size)
				;extra field (variable size)
ZLocalEntry	ENDS

ZIPHDRLEN       equ     30              ;length of initial ZIP hdr read

;LZH header structure

lzhlfh	STRUC			;Local file header
lUnk1		db	?,?	;char unknown1[2];	;?
lCmpMeth	db	5 dup(?) ;char method[5];	;compression method
lCmpSiz		dw	?,?	;long csize;	;compressed size
lUncmpSiz	dw	?,?	;long fsize;	;uncompressed size
lModTime	dw	?	;int ftime;	;last mod file time
lModDate	dw	?	;int fdate;	;last mod file date
lFAttr		db	?	;char fattr;	;file attributes
lUnk2		db	?	;char unknown2;	;?
lNameLen	db	?	;char namelen;	;filename length
lMbrName	db	?	;char *fname;	;filename
;lCrc16		dw	?		;int crc;	;crc-16
lzhlfh	ENDS

LZHHDRLEN	equ	22	;not including lMbrName or lCrc16


CSEG	segment public para 'CODE'
	assume	CS:CSEG,DS:CSEG,ES:CSEG

	public  ArcV

ArcV	proc	far
	push	bp			; save BASIC reg
	mov	bp,sp			; get parameter list pointer
	mov	CS:stkptr,sp		; save stack ptr
	mov	CS:saveds,DS		; save QB seg reg
	mov	CS:savees,ES		; save QB seg reg
        call    Start                   ; do our thing

;	set DOS error level and exit
;       We aren't relying on the CF flag anymore to indicate errors.
;	Instead, check AL.
;	0 = success
;	1 = command line parm error
;	2..6 are file-related (not found, etc.)
;	11 = Invalid format (probably didn't find a member header)
;	13 = invalid data (probably a bad file header structure)
;	18 = Unexpected EOF ('no further files to be found')

Exit:	mov	sp,stkptr		; restore entry stack value

        push    ax                      ;save error value

;Numerous errors could be returned

	or	al,al			;no errors?
	jz	Exit_NoErr		;yep, ok

	mov	bx,offset errtbl	;assume unknown error
	mov	di,bx			;various error values
	mov	cx,ERRTBLLEN		;table length
	repne	scasb			;find the offset
	jnz	Err_TblDone		;unknown, BX has table start

        dec    di                       ;back up to actual error
        sub    di,bx                    ;current psn - start = relative nr
        mov    bx,di                    ;into BX for msg offset

Err_TblDone:
	shl	bx,1			;*2 for words
Err_Unk:
	add	bx,offset errmsgtbl	;table of addresses
	mov	dx,[bx]			;ptr to string
	call	PrintS			;output error msg
		
Exit_NoErr:

	mov	bx,word ptr outhdl	; close listing file
        cmp     bl,STDOUT               ;never opened or STDERR?
        jna     Exit1                   ;not a real handle
        mov     ah,3eh                  ;close file handle
        int     21h
Exit1:
        mov     bx,word ptr archdl      ;close ARC/PAK/ZIP file
        or      bx,bx                   ; if it was opened
        jz      Exit2                   ; nope
        mov     ah,3EH                  ;close file handle
        int     21H
Exit2:

;Adding a test to insure we switched DTAs
;(so we don't blow away the caller's DTA with a vector 0:0!)

	lds	dx,dword ptr savedta	;get orig DTA vector
	or	dx,dx			;did we ever get it?
	jz	Exit_NoDTA		;nope
	mov	ax,DS			;check out seg
	or	ax,ax
	jz	Exit_NoDTA		;nope
        mov     ah,1ah                  ;set DTA
        int     21h
Exit_NoDTA:

        les     ax,dword ptr CS:saveds  ;recover calling seg regs
                                        ;(low word is orig DS)
        mov     ds,ax
	ASSUME	DS:NOTHING,ES:NOTHING	;a reminder

        pop     ax                      ;restore error level
        xor     ah,ah                   ;insure msb clear

	mov	bp,sp			; parm ptr from entry
        mov     6[bp],ax                ;return retcd variable
	pop	bp
        ret     6                       ; clear parms from stack

	subttl	'--- constants, equates and work areas'
	page

CR	equ	13
LF	equ	10
BEL	equ	7
TAB	equ	9

STOPPER equ	0		; end of display line indicator
ARCMARK equ	26		; special archive marker
ARCVER  equ	10		; highest compression code used

        even

stkptr  dw	0		; stack pointer upon entry

arctitl db      CR,LF,'Archive:  '      ;keep this even
saveds  dw	0		; QB seg reg
savees  dw	0		; QB seg reg

	subttl	'--- i/o control variables'
	page

INBUFSZ equ     128     ;512    ; size of input buffer

;Completely reordered these runtime variables
;so we can purge them with one fell swoop

PURGESTART      equ     $

totsf	dw	0,0		; average stowage factor
totlen  dw	0,0		; total of file lengths
totsize dw	0,0		; total of file sizes
totmbrs dw	0		; total number of files

archdl  dw	0		; file handle
fileptr dw	0		; ptr to filename part of arcname
arclen  dw      0               ;full archive filename length
;260 long filename length
arcname db      260 dup (0)

outhdl  dw      0               ; handle for output listing
templen dw      0               ;output filename length
;260 long filename length
temp    db      260 dup (0)     ; and temporary file name

filelen dw      0,0             ;absolute archive file length
curpsn  dw      0,0             ;remember current file pointer psn

savedta dw	0,0		; addr of QB dta
dta	db	48 dup (0)	; data transfer area

        even

PURGELEN        EQU     ($ - PURGESTART) SHR 1  ;amount to purge each run

;	display lines for verbose

vhdr db CR,LF,'Name          Length    Stowage    SF   Size now  Date       Time    CRC '
 db CR,LF,'============  ========  ========  ====  ========  =========  ======  ===='
 db CR,LF
 db STOPPER

vflnm db 'Contents of: ',0

;vline	db	CR,LF
vline   label   byte
vname	db	14 dup (' ')
vlength db      '          '    ; length in archive
vstyle  db	'          '	; compression method
vfactor db	' xx%  '	; compression factor
vsize	db	10 dup (' ')	; actual file bytes
vdate	db	'dd '		; creation date
 vmonth db	'mmm '
 vyear  db	'yy  '
 vtime  db	'hh:mm   '	; creation time
 vcrc	db	'xxxx'		; crc in hex
        db      CR,LF
	db	STOPPER

hundred dw	100		; for computing percentages

;	final totals line

vthdr   db '------    --- --------            ----  --------',CR,LF
        db      '*Total    '
 vtmbrs db	'    '
 vtlen  db	8 dup (' '),'  '
	db	10 dup (' ')
 vtsf	db	'   %  '
 vtsize db	8 dup (' ')
	db	CR,LF		; for tom
	db	STOPPER

 sign	db	' '

styles  db	'  ----- '	; 1 = old, no compression
	db	'  ----- '	; 2 = new, no compression
	db	' Packed '	; 3 = dle for repeat chars
	db	'Squeezed'	; 4 = huffman encoding
	db	'crunched'	; 5 = lz, no dle
	db	'crunched'	; 6 = lz with dle
	db	'Crunched'	; 7 = lz with readjust
	db	'Crunched'	; 8 = lz with readjust and dle
	db	'Squashed'	; 9 = 13-bit lz with no dle
	db	' Crushed'	;10 = Pak10 file ---------Bob Simoneau

;ZIP compression types:

zstyles	label	byte
	db	'  Stored'	;0 - The file is stored (no compression)
	db	'  Shrunk'	;1 - The file is Shrunk
	db	'Reduced1'	;2 - Reduced with compression factor 1
	db	'Reduced2'	;3 - Reduced with compression factor 2
	db	'Reduced3'	;4 - Reduced with compression factor 3
	db	'Reduced4'	;5 - Reduced with compression factor 4
        db      'Imploded'      ;6 - New don't know format
        db      'Deflate1'      ;7 - New format
        db      'Deflate2'      ;8 - New format

;LZH compression types are already coded as 5 chars of text
;in the compressed file.
;All we need to do is pad them out to the correct width.

months  db	'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec '

ARCPAK  =       0
ZIP     =       1
LZH     =       2
ftype   db      ZIP                     ;flag which type file

;4 types of archive file

ziptype	db	'ZIP'
arctype	db	'ARC'
paktype	db	'PAK'
lzhtype db      'LZH'
larctype db     'LZS'                   ;not enabled for now

;zfilesig db    50H,4BH,03H,04H         ;local file header signature
;zdirsig db     50H,4BH,01H,02H         ;central file header signature

ZSIG    equ     4B50H                   ;unique ZIP signature
ZFILESIG equ    0403H                   ;file member signature
ZDIRSIG equ     0201H                   ;central file header signature

;Centralizing errors at the exit point

;	1 = command line parm error
;	2..6 are file-related (not found, etc.)
;	11 = Invalid format (probably didn't find a member header)
;	12 = Invalid file type (not an ARC, PAK, ZIP)
;	13 = invalid data (probably a bad file header structure)
;	18 = Unexpected EOF ('no further files to be found')

errtbl db       0,1,2,3,4,5,6,11,12,13,18,25,27,29,30
ERRTBLLEN	equ	$ - errtbl

errmsgtbl dw    msg0,msg1,msg2,msg3
	dw	msg4,msg5,msg6,msg11
	dw	msg12,msg13,msg18,msg25
	dw	msg27,msg29,msg30

msg0	db	'Unknown error',0

msg1	db	'Invalid function number',0
msg2	db	'Archive file not found',0
msg3	db	'Path not found',0
msg4	db	'No handle available',0
msg5	db	'Access denied',0
msg6	db	'Invalid handle',0
msg11	db	'Archive header error',0
msg12	db	'Invalid file type',0
msg13	db	'Archive format error',0
msg18	db	'No further files to be found',0
msg25	db	'Disk seek error',0
msg27	db	'Disk sector not found',0
msg29	db	'Write error',0
msg30	db	'Read error',0


	subttl	'--- mainline processing'
	page
;
Start   proc    near

        mov     ax,CS                   ;just set ES for now
	mov	ES,ax
        ASSUME  DS:NOTHING,ES:CSEG      ;a reminder

;Insure all variables are cleared
	cld
	mov	di,offset PURGESTART
        xor     ax,ax                   ;clear all the variables
        mov     cx,PURGELEN             ;nr words to clear
        rep     stosw

;Move first parameter (output filename) into code space

        mov     si,word ptr 10[bp]      ; ptr to parameter vector
        lodsw                           ; get string length
        mov     cx,ax
        jcxz    Copy_Parm2              ;empty, forget it
        mov     di,offset templen       ;str length
        stosw                           ;save length
        mov     si,[si]                 ; get string offset
        rep     movsb                   ;copy in the string

Copy_Parm2:

;Now copy 2d parameter (target archive filename)

	mov	si,word ptr 8[bp]	; ptr to parameter vector
	lodsw				; get string length
        mov     cx,ax
        jcxz    Parm2_Done              ;forget it
        mov     di,offset arclen        ;archive name length
        stosw                           ;save length
        mov     si,[si]                 ; get string offset
        mov     ah,'a'                  ;constant for typecasing
Parm2_Upper:
        lodsb                           ;snarf char
        cmp     al,ah                   ;need uppercasing?
        jb      Parm2_NoU               ;nope
        sub     al,20H                  ;uppercase it
Parm2_NoU:
        stosb
        loop    Parm2_Upper

Parm2_Done:

;All done with DS

        mov     ax,CS
        mov     DS,ax
        ASSUME  DS:CSEG,ES:CSEG         ;a reminder

        mov     ax,STDOUT               ;assume no output filename
        cmp     temp,0                  ;any output filename?
        jz      Temp_Opened2            ;nope, use STDERR

;open for append
        mov     si,offset temp          ;open temporary file for output
        mov     bx,41h                  ;write only
        xor     cx,cx                   ;no special attributes
        mov     dx,11h                  ;create\append
        mov     ah,6ch                  ;open file
        mov     al,0
	int	21h
        jnb     Temp_Opened1            ;fine
        ret                             ;back to Exit, AL=error code
                                        ;CF set
Temp_Opened1:
         mov     outhdl,ax              ;save handle
;reset to eof
         mov     bx,outhdl              ;store file handle
         xor     cx,cx                  ;clear file offset
         xor     dx,dx                  ;clear file offset
         mov     ah,42h                 ;seek to file
         mov     al,2h                  ;seek to eof
         int     21h
         mov     ax,outhdl              ;restore file handle
Temp_Opened2:
         mov     outhdl,ax              ;store actual file handle

;Parse the target archive name
;Separate path from name
;Insure it's an ARC, PAK or ZIP type.

        mov     di,offset arclen        ;archive name length
        mov     ax,[di]                 ;snarf length
        inc     di                      ;bump to name proper
        inc     di
        mov     cx,ax                   ;into CX for scans to come
        jcxz    No_ArcName              ;no length, ergo no name

        mov     dx,ax                   ;save in DX for later
        xor     al,al                   ;will scan for AsciiZ terminator
        cmp     [di],al                 ;no name at all?
        jnz     Got_ArcName             ;yep

No_ArcName:
        mov     al,2                    ;'Archive file not found'
        ret                             ;back to Exit

Got_ArcName:

;We have some sort of target name.
;But is it a legal type?
;DX = filename length
;DI -> archive filename (arcname)

        add     di,dx                   ;+ length -> last char+1
        dec     di                      ;back up to last char
        mov     bx,di                   ;BX -> last char

        mov     al,'\'                  ;look for normal path delimiter
        mov     cx,dx                   ;length for scan
        std                             ;backwards scanning now
        repne   scasb
        jz      Got_Start               ;found one

	mov	di,bx			;back to end
	mov	cx,dx			;restore length
	mov	al,'/'			;funny path delimiter
	repne	scasb
	jz	Got_Start		;found one

	mov	di,bx			;back to end .. sigh ..
	mov	cx,dx			;restore length
	mov	al,':'			;ok, how about a drive?
	repne	scasb
	jnz	No_Paths		;nope, DI -> name start

Got_Start:
	inc	di			;bump up to the separator
No_Paths:
	inc	di			;bump to the first name char
	cld				;forward again
	mov	fileptr,di		;remember real filename start

;You MUST specify the type .ARC, .PAK, .ZIP, or .LZH.
;If .ARC or .PAK, we'll use the old code to display ARC-type files.
;Else if ZIP or LZH, it's a totally new format!
;We remember the type archiving format in 'ftype'.

;DS:SI -> filename's first char.

        mov     al,'.'                  ;find the separator
        mov     cx,word ptr 12          ;max of 12 chars
        repne   scasb                   ;find it
        jnz     BadType                 ;forget it

        mov     dx,di                   ;save pointer to file type
                                        ;(just past the separator)
	mov	ax,3			;3 chars constant

	mov	ftype,ZIP		;assume ZIP

	mov	si,offset ziptype	;is it a ZIP?
	mov	di,dx			;back to filename type
	mov	cx,ax			;3 chars
	repz	cmpsb			;compare
	jz	Got_Type		;a match

        mov     ftype,ARCPAK            ;ok, assume ARC or PAK

        mov     si,offset arctype       ;is it an ARC?
	mov	di,dx			;back to filename type
	mov	cx,ax			;3 chars
	repz	cmpsb			;compare
	jz	Got_Type		;a match

	mov	si,offset paktype	;is it a PAK?
	mov	di,dx			;back to filename type
	mov	cx,ax			;3 chars
	repz	cmpsb			;compare
	jz	Got_Type		;a match

;Adding .LZH types
        mov     ftype,LZH               ;ok, assume .LZH file

	mov	si,offset lzhtype	;is it an LZH?
	mov	di,dx			;back to filename type
	mov	cx,ax			;3 chars
	repz	cmpsb			;compare
	jz	Got_Type		;a match

BadType:
         mov    al,12                   ;'Invalid file type'
         ret                            ;back to Exit

Got_Type:

;	find first matching file

	push	ES
	mov	ah,2fh			; get current dta ptr
	int	21h			; returned in ES:bx
	mov	savedta,ES
	mov	savedta[2],bx
	pop	ES

	mov	dx,offset dta		; set local dta for murkers
	mov	ah,1ah
	int	21h

	call	OpenArc			; see if archive exists
	jnb	ArcV1			;ok
        jmp     ArcV_X                  ;nope, return, AL=error

;Display archive filename, header,
;then into a loop for each archive member.

ArcV1:  Print   vflnm
        mov     dx,fileptr              ;pointer to filename
        call    PrintS                  ;display, CR/LF
        jb      ArcV_X                  ;output failed

	Print	vhdr
        jb      ArcV_X                  ;output failed, AL = error

ArcVNext:
IF	DEBUG
	Print	debug1
	jmp	short debugj1
debug1	db	'Calling GetHdr',CR,LF,0
debugj1:
ENDIF
	call	GetHdr			; load next header
        jb      ArcV_NoHdr              ;failed somehow, AL=error
                                        ;(could be EOF, which is ok)
IF	DEBUG
	Print	debug2
	jmp	short debugj2
debug2	db	'Calling ArcVgo',CR,LF,0
debugj2:
ENDIF
	call	ArcVgo			;format, write out file report
        jb      Arcv_NoHdr              ;something failed, AL=error

IF	DEBUG
	Print	debug3
	jmp	short debugj3
debug3	db	'Calling Bump_ArcPtrs',CR,LF,0
debugj3:
ENDIF
        call    Bump_ArcPtrs            ;bump to next archive file
        jnb     ArcVNext                ;loop if ok, else AL=error
                                        ;(could be EOF)

ArcV_NoHdr:
	cmp	archdr.aCmpMeth,0	; archive eof?
        jnz     ArcV_X                  ;nope, something else happened

        cmp     totmbrs,0               ;any totals?
        jz      ArcV_X                  ;nope
        push    ax                      ;save previous error value
        call    Format_Totals           ;yep, format and output
        pop     ax                      ;restore prev err value

ArcV_X: ret                             ;AL=error

Start   endp


;Format, display single line for each member
; On success, return:
;    CF clear
;    AL = 0
; On error, return:
;    CF set (because of output write fail)
;    AL = error code

ArcVgo	proc	near
	mov	di,offset vname		; copy file name
	mov	si,offset archdr.aMbrName
	mov	cx,word ptr 13		;up to 12 chars long, AsciiZ 0
ArcV3:
	lodsb
        or      al,al                   ; end of name?
	je	ArcV4
        stosb
        loop    ArcV3
        jmp     short ArcV5

ArcV4:
	mov	al,' '			; pad with blanks
	rep	stosb
ArcV5:
; reduce the size/length to word values

        mov     bx,offset archdr.aCmpSiz        ;-> compressed size
        mov     cx,[bx]                 ;.lo
        mov     dx,2[bx]                ;.hi
        mov     bx,offset archdr.aUncmpSiz      ;-> uncompressed size
        mov     ax,2[bx]                ;.hi
        mov     bx,[bx]                 ;.lo

ArcV51: or	ax,ax			; big number?
	jz	ArcV52			; nope, can use it
        shr     ax,1                    ; yup, divide by two
        rcr     bx,1
        shr     dx,1
        rcr     cx,1
        jmp     short ArcV51

ArcV52:
	mov	ax,bx			; low word of actual size
	mov	sign,' '
	cmp	ax,cx			; arc member is larger?
	jb	ArcV520
        sub     ax,cx                   ; amount saved
        jmp     short ArcV56

ArcV520:
	sub	ax,cx
	neg	ax
	mov	sign,'-'

ArcV56:
	mul	hundred			; to percentage
	add	ax,50
	adc	dx,0			; round up percent
	or	bx,bx			; empty file?
	jnz	ArcV53
        mov     ax,100
        jmp     short ArcV54

ArcV53: div	bx
ArcV54:
	cmp	ax,100			; archive fouled?
	jbe	ArcV55
        sub     ax,ax
ArcV55:
        mov     di,offset vfactor-2     ;format stowage factor
	call	Asciify			;display AX

	mov	al,sign
	mov	vfactor,al

        mov     cx,word ptr 3           ;gonna need it in a sec
        cmp     ftype,LZH               ;LZH type? (compression method
                                        ; is already text)
        jnz     ArcV_GetStyles          ;nope

;The LZH compression method (5 chars) is still in inbuf.

        mov     si,offset inbuf.lCmpMeth        ;-> 5-char compression
                                                ;   method string
	mov	di,si
        add     di,5                    ;point to beyond chars
        mov     ax,'  '                 ;need 3 trailing blanks
	stosw
	stosb
        mov     di,offset vstyle+1      ;indent to be neat
        jmp     short ArcV_GotStyle     ;skip

ArcV_GetStyles:

        mov     si,offset zstyles       ;assume ZIP
        cmp     ftype,ZIP               ;ZIP file?
        jz      ArcV55A                 ;yep
        mov     si,offset styles        ;ARC or PAK
ArcV55A:
	sub	bx,bx			; determine style
	mov	bl,archdr.aCmpMeth
        dec     bl                      ;adjust for table offset
        shl     bx,cl                   ;multply by 8

        add     si,bx                   ;point into style table
	mov	di,offset vstyle
ArcV_GotStyle:
        inc     cx                      ;CX=4=words to move
        rep     movsw

        mov     bx,offset archdr.aCmpSiz        ;-> compressed size
        mov     ax,[bx]                 ;.lo
        mov     dx,2[bx]                ;.hi
        mov     bx,offset totsize       ;-> accumulated compressed size
        add     [bx],ax                 ;.lo
        adc     2[bx],dx                ;.hi

        mov     di,offset vsize         ;format file size
        call    Asciify_Long

        mov     bx,offset archdr.aUncmpSiz      ;-> uncompressed size
        mov     ax,[bx]                 ;.lo
        mov     dx,2[bx]                ;.hi
        mov     bx,offset totlen        ;-> total length accumulator
        add     [bx],ax                 ;.lo
        adc     2[bx],dx                ;.hi
	
        mov     di,offset vlength       ;format file length
        call    Asciify_Long

	mov	ax,archdr.aModDate	; format file date
	call	GetDate

	mov	ax,archdr.aModTime	; format file time
	call	GetTime

	mov	ax,archdr.aCrc16	; format crc in hex
	mov	di,offset vcrc
	call	Cvh

        inc     totmbrs                 ;NOW bump total count
	Print	vline			; display this file info
                                        ;(may return error)
	ret

ArcVgo	endp


	subttl	'--- load next archive header'
	page

;Adding ZIP file searching
; For ARC/PAK files, now testing to see if we're at the archive
; file end.  If so (a proper file), return with EOF (CF set but AL=0).
; Archive files may have picked up some garbage on the end
; (from XMODEM xfers, whatever).  We'll see if we at LEAST have
; enough data for an archive header.
; If not, assume EOF, ignoring garbage.
; If there's more than 29 bytes of garbage .. the header will be
; garbage and we're gonna report a format error .. but that's ok for now.
; Zip files have a definite ending (the central directory,
; and they'll look out for their own endings.
;
;Also returning CF and AL per any errors.

GetHdr  proc	near

	xor	ax,ax			;handy 0
	mov	archdr.aCmpMeth,al	;assume archive EOF

	cmp	ftype,ZIP		;doing ZIP files?
        jnz     GH_NotZip               ;nope
        jmp     Get_ZipHdr              ;yep, they look out for themselves

GH_NotZip:
        cmp     ftype,LZH               ;doing an LZH file?
        jnz     GH_ArcPak_Hdr           ;nope
        jmp     Get_LZHHdr              ;yep

GH_ArcPak_Hdr:

;New code
;ARC/PAK headers look like this:
;aMbrFlag	db	1AH		;unique header flag
;aCmpMeth	db	0		;  compression code
;aMbrName	db	13 dup (0)	;  file name
;aCmpSiz	dw	0,0		;  file size in archive
;aModDate	dw	0		;  creation date
;aModTime	dw	0		;  creation time
;aCrc16		dw	0		;  cyclic redundancy check
;aUncmpSiz	dw	0,0		;  true file size, bytes

	mov	dx,offset archdr	;read into here
	mov	cx,ARCHDRLEN		;nr bytes to read
	mov	bx,archdl		;archive file handle
	mov	ah,3FH			;read from file/device
	int	21H
        jnb     GH_ChkHdr               ;read ok
        ret                             ;return CF set, AL=error

GH_ChkHdr:
        mov     bx,dx                   ;DS:BX -> structure start

	cmp	[bx].aMbrFlag,ARCMARK	;start of header?
	jne	Hdr_InvalFmt		;'invalid format', exit CF set

	mov	al,[bx].aCmpMeth	;type compression
	cmp	al,ARCVER		;reasonable code?
	ja	Hdr_InvalFmt		;nope, funny stuff

	or	al,al			; archive eof?
	je	Hdr_RetCF		;yep, done, return CF set
                                        ;but AL=0 = not a REAL error
	cmp	al,1			; old format?
	jne	GetHdrX			; if so, it's short
        mov    si,offset archdr.aCmpSiz
        mov    di,offset archdr.aUncmpSiz
        movsw
        movsw
GetHdrX:
        xor     al,al                   ;return AL=0, success
	clc
	ret

Hdr_InvalFmt:
	mov	al,0BH			;'invalid format'
Hdr_EarlyEOF:
        mov     [bx].aCmpMeth,al        ;signal EOF or invalid format
Hdr_RetCF:
	stc				;return CF set, AL=error
	ret

GetHdr	endp


Get_ZipHdr	proc	near
;GetHdr Subroutine for ZIP files
;Reads in ZIP file entry.
;Then scans for the unique file entry signature.
; On success:
;    DS:BX -> file entry directory structure
;    CF clear
; Else CF set for failure

	call	Read_Zip_Entry
	jb	Get_ZHdrX			;failed, AL=ERRORLEVEL

	mov	bx,offset inbuf			;use for field base
	mov	di,offset archdr.aCmpMeth	;moving into this structure

;Remember, the ZIP header we'll be snarfing data from looks like this:
;zVerMade	dw	?	;version needed to extract	2 bytes
;zBitflag	dw	?	;general purpose bit flag	2 bytes
;zCmpMeth	dw	?	;compression method		2 bytes
;zModTime	dw	?	;last mod file time 		2 bytes
;zModDate	dw	?	;last mod file date		2 bytes
;zCrc32		dw	?,?	;crc-32   			4 bytes
;zCmpSiz	dw	?,?	;compressed size		4 bytes
;zUncmpSiz	dw	?,?	;uncompressed size		4 bytes
;zNameLen	dw	?	;filename length		2 bytes
;zExtraLen	dw	?	;extra field length		2 bytes
;zMbrName	db	?	;filename (variable size)
				;extra field (variable size)
;
;	and the ARC/PAK record we'll be formatting to
;	looks like this:
;aMbrFlag db	1AH
;aCmpMeth db	0			;  compression code
;aMbrName db	13 dup (0)		;  file name
;aCmpSiz dw	0,0			;  file size in archive
;aModDate dw	0			;  creation date
;aModTime dw	0			;  creation time
;aCrc16  dw	0			;  cyclic redunancy check
;aUncmpSiz  dw	0,0			;  true file size, bytes

	mov	ax,[bx].zCmpMeth		;compression method
	inc	al				;bump to be non-0
	stosb					;->  aCmpMeth

;For now, assuming a normal file name (no paths)

	mov	ax,[bx].zNameLen		;filename length
	and	ax,15				;constrain to max 12 chars
	mov	cx,ax				;into CX for move
	lea	si,[bx].zMbrName		;pointer to actual filename
	rep	movsb				;do the move
	xor	al,al				;terminating 0
	stosb

	mov	di,offset archdr.aCmpSiz	;bump past name
	mov	si,offset inbuf.zCmpSiz		;-> compressed size
	movsw					;aCmpSiz.lo
	movsw					;aCmpSiz.hi

	mov	ax,[bx].zModDate		;last mod date
	stosw					; -> aModDate
	mov	ax,[bx].zModTime		;last mod time
	stosw					; -> aModTime
	mov	ax,[bx].zCrc32			;CRC-32 value.lo
	stosw					; -> aCrc16
	mov	si,offset inbuf.zUncmpSiz	;-> uncompressed size
	movsw					;aUncmpSiz.lo
	movsw					;aUncmpSiz.hi

	xor	ax,ax				;return AX 0
	clc					;return CF clear
Get_ZHdrX:
	ret

Get_ZipHdr	endp		;GetHdr subroutine



Get_LZHHdr	proc	near
;       GetHdr Subroutine for LZH headers
;	LZH file header has already been read in to inbuf.
;
;	If all is ok, we move the appropriate LZH fields into the
;	standard ARC/PAK structure (archdr) (so far as we can).
;
;	Gleaning from the LHARCDOC documentation, the 'laCmpMeth' field
;	(5 characters) can be:
;		'-lh0-'		stored as is (no compression)
;		'-lh1-'		compressed by LZHuf coding
;	There appear to be at least two more possible compression codes
;	that may appear:  "LARC type 4 and type 5" (whatever they may be!).
;
;	Assuming this field will ALWAYS be text, we are NOT gonna try to
;	snarf some magic code number out of the field, but will just
;	protect the field (in inbuf) and move the text directly into our
;	formatted display line later.
;
;	The only way we can test this as an LZH header is to look
;	for a '-%%%-' starting at the 2d header byte (the laCmpMeth
;	field).
;
;	On success:
;	 DS:BX -> file entry directory structure
;	 CF clear
;	Else CF set for failure

;  LZH files don't have a decent, clean EOF header.
;  We have to test for near-EOF the hard way.

	mov	di,offset archdr.aMbrFlag	;moving into this structure
	mov	ax,001AH			;fake ARC/PAK flag
	stosw					; and EOF compression code

	xor	ax,ax			;handy 0
	mov	bx,offset filelen	;-> file length
	mov	dx,[bx]			;file length.lo
	mov	cx,2[bx]		;file length.hi

	mov	bx,offset curpsn	;for fast access	
	cmp	cx,2[bx]		;length.hi = psn.hi?
	jnz	GL_AddHdr		;nope
	cmp	dx,[bx]			;length.lo = psn.lo?
	jz	GL_TrueEof		;yep, we're exactly at EOF

GL_AddHdr:
	sub	dx,LZHHDRLEN		;sub header length
	sbb	cx,ax	;0		;handle the borrow
	jb	GL_Eof			;<0, beyond EOF
	sub	dx,[bx]			;- file psn.lo
	sbb	cx,2[bx]		;- file psn.hi, minus any borrows
	jnb	GL_NotEof		;not near end .. ok

;There must've been junk on the file end.
;However .. there ALWAYS seems to be junk on the end.
; So .. we'll return no message at all (AL=0)
;If we ever figure out how to detect a TRUE LZH EOF,
;we can enable this ERRORLEVEL=18 business.

GL_Eof:
;	mov	al,18			;'No further files to be found'
GL_TrueEof:
        stc                             ;CF set for EOF
	ret

GL_NotEof:

	push	di			;save ptr -> archdr.aMbrName
	call	Read_LZH_Entry
	pop	di
	jb	Get_LHdrX			;failed, AL=ERRORLEVEL

	mov	bx,offset inbuf			;use for field base

;Remember, the LZH header we'll be snarfing data from looks like this:
;lUnk1	db	?,?	;char unknown1[2];	;?
;lCmpMeth	db	5 dup(?) ;char method[5];	;compression method
;lCmpSiz	dw	?,?	;long csize;	;compressed size
;lUncmpSiz	dw	?,?	;long fsize;	;uncompressed size
;lModTime	dw	?	;int ftime;	;last mod file time
;						; (msdos format)
;lModDate	dw	?	;int fdate;	;last mod file date
;lfAttr		db	?	;char fattr;	;file attributes
;unknown2	db	?	;char unknown2;	;?
;lNameLen	db	?	;char namelen;	;filename length
;
;lMbrName	db	?	;char *fname;	;filename
;;lCrc16	dw	?	;int crc;	;crc-16
;
;	and the ARC/PAK record we'll be formatting to
;	looks like this:
;aMbrFlag db	1AH
;aCmpMeth db	0			;  compression code
;aMbrName db	13 dup (0)		;  file name
;aCmpSiz dw	0,0			;  file size in archive
;aModDate dw	0			;  creation date
;aModTime dw	0			;  creation time
;aCrc16  dw	0			;  cyclic redundancy check
;aUncmpSiz  dw	0,0			;  true file size, bytes

	mov	al,[bx].lNameLen		;filename length
	and	ax,15				;constrain to max 12 chars
	mov	cx,ax				;into CX for move
	mov	si,offset inbuf.lMbrName	;-> actual filename
	rep	movsb				;do the move
	xor	al,al				;terminating 0
	stosb

;In LZH headers, the 2-byte CRC16 word lies immediately
;after the filename.
;Snarf it now and stuff in the ARC header.

	lodsw					;lCrc16
	push	ax				;save a sec

	mov	di,offset archdr.aCmpSiz	;bump past name
	mov	si,offset inbuf.lCmpSiz		;-> compressed size
	movsw					;aCmpSiz.lo
	movsw					;aCmpSiz.hi

	mov	ax,[bx].lModDate		;last mod date
	stosw					; -> aModDate
	mov	ax,[bx].lModTime		;last mod time
	stosw					; -> aModTime
	pop	ax				;CRC-16 value
	stosw					; -> aCrc16
	mov	si,offset inbuf.lUncmpSiz	;-> uncompressed size
	movsw					;aUncmpSiz.lo
	movsw					;aUncmpSiz.hi

	xor	ax,ax				;return AX 0
	clc					;return CF clear
Get_LHdrX:
	ret

Get_LZHHdr      endp                    ;GetHdr Subroutine


Read_LZH_Entry  proc    near            ;GetHdr Subroutine

	mov	dx,offset inbuf			;read into here
	mov	cx,LZHHDRLEN			;entry structure size
						;(does NOT include variable
						; length filename, and the
						;two CRC bytes following the
						;filename)
	mov	bx,archdl			;file handle
	call	ReadZ_It			;try to read in header
						;(up to filename)
	jb	ReadL_Eof			;failed, AL=error

	mov	si,dx				;structure start
	mov	al,'-'				;test for '-l%-' or whatever
	cmp	[si].lCmpMeth,al		;first part of compression
						;method string?
	jnz	ReadL_InvalDat			;bogus, failed
        cmp     [si].lCmpMeth+4,al              ;how about last char?
        jz      ReadL_Ok1                       ;yep, fine
ReadL_InvalDat:
	mov	al,0DH				;force to 'invalid data'
ReadL_Eof:
	mov	archdr.aCmpMeth,al		;set per EOF or error
	stc					;return CF set
	ret

ReadL_Ok1:
	mov	dx,offset inbuf.lMbrName	;-> lMbrName psn
	mov	cl,inbuf.lNameLen		;length of member filename
	xor	ch,ch				;clear msb
	call	ReadZ_It			;read in the name
	jb	ReadL_Eof			;failed
	add	dx,cx				;bump buff ptr past name
	mov	cx,2				;LZH CRC is a word
	call	ReadZ_It			;read in the CRC word
	jb	ReadL_Eof			;failed
	ret					;success

Read_LZH_Entry  endp                            ;GetHdr Subroutine


Read_Zip_Entry  proc    near                    ;GetHdr Subroutine

	mov	dx,offset inbuf			;read into here
	mov	cx,ZIPHDRLEN			;entry structure size
						;(does NOT include filename or
						; Extra fields, which are
						;dynamic)
	mov	bx,archdl			;file handle
	call	ReadZ_It			;try to read in header
						;(up to filename)
        jb      ReadZ_Eof                       ;failed, AL=error

        mov     si,dx                           ;->file signature
        lodsw                                   ;snarf first 2 chars
        cmp     ax,ZSIG                         ;ZIP header?
        jnz     ReadZ_InvalDat                  ;nope, bogus
        lodsw                                   ;file or central sig
        cmp     ax,ZFILESIG                     ;next member?
        jz      ReadZ_Ok1                       ;yep, fine
        cmp     ax,ZDIRSIG                      ;central directory?
                                                ;(means we're done)
        mov     al,0                            ;assume yes, EOF
        jz      ReadZ_Eof                       ;yep

ReadZ_InvalDat:
        mov     al,0DH                          ;'Invalid data'
ReadZ_Eof:
        mov     archdr.aCmpMeth,al              ;set per EOF or error
        stc                                     ;return CF set
	ret

ReadZ_Ok1:
	mov	dx,offset inbuf.zMbrName	;move to zFilename psn
	mov	cx,inbuf.zNameLen		;length of member filename
                                                ;fall thru to ...

;Common subroutine for ReadZ and Read_LZH
;   DX -> buffer
;   CX = bytes to read
;   BX MUST have archdl .. so protect BX!

ReadZ_It:
        mov     ah,3FH                          ;read from file/device
        int     21H
        jb      ReadZ_ItX                       ;failed, error in AX

;We'll update our curpsn file pointers later
;when we try to read past compressed file contents.

        cmp     ax,cx                           ;read all we expected?
        mov     ax,0                            ;clear AX
        jz      ReadZ_ItX                       ;yep, return CF clear
        mov     al,0BH                          ;assume unexpected EOF
						;('invalid format')
        stc
ReadZ_ItX:
        ret                                     ;CF, AL set per error

Read_Zip_Entry  endp                            ;GetHdr subroutine


;Common subroutine
;Bumps archive file pointers to next entry
; On success, return:
;    CF clear
;    AL = 0
; On failure (e.g., couldn't move ptrs), return:
;    CF set
;    AL = error

Bump_ArcPtrs	proc	near

        cmp     ftype,ZIP               ;ZIP file?
        jz      Next_ZEntry             ;bump file ptr to next entry

;Entirely new code

        mov     bx,offset archdr.aCmpSiz        ;-> encoded file length
        mov     dx,[bx]                 ;.lo
	mov	cx,2[bx]		;.hi
	jmp	short Bump_Common	;common code


;Positions ZIP file pointer to next local entry.
;We've already read in the entire header, plus the filename,
;so the file pointer should be just beyond the filename
;(at the Extra field).
;Move file pointers beyond the Extra field, and then past
;the actual entry data (the compressed size).

Next_ZEntry:

	mov	bx,offset inbuf			;point back to structure
	mov	dx,[bx].zCmpSiz			;size.lo
	mov	cx,[bx].zCmpSiz[2]		;size.hi
	add	dx,[bx].zExtraLen		;add in extra field length
	adc	cx,0				;in case of carry

Bump_Common:

	mov	bx,archdl			;file handle
	mov	ax,4201H			;move pointer from current loc
	int	21H
        jb      Bump_X                          ;seek error
                                                ;return CF set, AL=error

;Updating curpsn variables now
;so the NEXT GetHdr call will have current data.
        mov     bx,offset curpsn
        mov     [bx],ax
        mov     2[bx],dx
        xor     ax,ax                           ;AX,CF clear
Bump_X:
	ret

Bump_ArcPtrs	endp


;Formats, displays totals

Format_Totals	proc	near

        mov     ax,totmbrs              ;total members
        mov     di,offset vtmbrs-2      ;format total members
        call    Asciify

        mov     bx,offset totlen        ;-> total actual file size
        mov     ax,[bx]                 ;.lo
        mov     dx,2[bx]                ;.hi

        push    ax                      ;save totlen.lo
        push    dx                      ; and totlen.hi

        mov     di,offset vtlen         ;format total actual file size
        call    Asciify_Long

        mov     bx,offset totsize       ;-> total compressed file sizes
        mov     ax,[bx]                 ;.lo
        mov     dx,2[bx]                ;.hi

        push    ax                      ;save totsize.lo
        push    dx                      ; and totsize.hi

        mov     di,offset vtsize        ;format total archive file size
        call    Asciify_Long

; reduce the total size/length to word values

        pop     dx                      ;totsize.hi
        pop     cx                      ;totsize.lo
        pop     ax                      ;totlen.hi
        pop     bx                      ;totlen.lo

ArcV2b: or	ax,ax			; big number?
	jz	ArcV2c			; nope, can use it
        shr     ax,1                    ; yup, divide by two
        rcr     bx,1
        shr     dx,1
        rcr     cx,1
        jmp     short ArcV2b

ArcV2c:
	mov	ax,bx
	mov	sign,' '		; whata kludge
	cmp	ax,cx			; arc is bigger than orig?
	jb	ArcV2c1
        sub     ax,cx                   ; amount saved
        jmp     short ArcV2f

ArcV2c1:
	sub	ax,cx
	neg	ax
	mov	sign,'-'

ArcV2f:
	mul	hundred			; to percentage
	add	ax,50
	adc	dx,0			; round up percent
	or	bx,bx			; empty file?
	jnz	ArcV2d
        mov     ax,100
        jmp     short ArcV2e

ArcV2d: div	bx
ArcV2e:
        mov     di,offset vtsf-2        ;format stowage factor
        call    Asciify                 ;AX

	mov	al,sign
	mov	vtsf,al
	Print	vthdr			; display totals
	ret

Format_Totals	endp


OpenArc proc	near			; open new archive

	mov	dx,offset arcname
        mov     ax,3d40h                ; for input
	int	21h
        jnb     Open_GetSize            ;opened ok
        ret                             ;return CF set, AL=error

Open_GetSize:
        mov     bx,ax                   ;handle into BX
	mov	archdl,ax		; save file handle

;We get the total file size now for later EOF testing.
	xor	dx,dx			;0 offset
	xor	cx,cx
	mov	ax,4202H		;from file end
	int	21H
	mov	filelen,ax		;length.low
	mov	filelen[2],dx		;length.hi
	xor	cx,cx			;back to start
	xor	dx,dx
	mov	ax,4200H		;psn file pointer from start
	int	21H
	ret				;CF should be clear

OpenArc endp


ClosArc proc	near

	mov	bx,archdl		; previous handle
	or	bx,bx			; already open?
	jz	Closed
        mov     ah,3eh                  ; yes, so close it
        int     21H
Closed:	mov	archdl,0		;flag as closed
	ret

ClosArc endp


;print null-terminated (AsciiZ) string like int 21h function 9
;Enter with DS:DX -> AsciiZ string
; destroys AX
; On success, return:
;    CF clear
;    AL = 0
; On failure (write fail), return:
;    CF set
;    AL = error

PrintS  proc	near

        push    di
	push	bx
	push	cx

        mov     cx,0FFFFH               ;max scan
        xor     al,al                   ;handy 0
        mov     di,dx                   ;string start
        repne   scasb                   ;find the terminator
        inc     cx                      ;adjust
        not     cx                      ;CX=length
        mov     bx,outhdl               ; using std out or temp file
        or      bx,bx                   ;never opened?
        jnz     Print_S1                ;nope, we got a handle
        inc     bx                      ;make it STDERR
Print_S1:
	mov	ah,40h			; write to file
	int	21h
        jnb     PrintS_Done             ;fine

;What happens if we're trying to write to an output file
;and THAT fails?  Even error msgs can't get out.
;Switch to StdOut instead..

        mov     di,ax                   ;save error level
        mov     bx,STDOUT               ;force to STDERR
        mov     outhdl,bx               ;and for future output
        mov     ah,40H                  ;write to STDOUT
        int     21H                     ;(CX,DX unchanged)
        mov     ax,di                   ;restore orig error
        stc                             ;return CF set
 
PrintS_Done:
	pop	cx			; recover registers
	pop	bx
	pop	di
	ret

PrintS  endp

	page
;
;	format the time (in AX)

time	record  hour:5,min:6,sec:5	;packed time

GetTime proc	near			;format the date
	mov	di,offset vtime
	or	ax,ax			;it is zero?
	jz	GotTime

	push	ax			;save date
	and	ax,mask hour		;get hour part
	mov	cl,hour			;bits to shift
	shr	ax,cl
	call	Cnvrt1
	stosw
	mov	al,':'
	stosb

GT3:	pop	ax			;get the time back
	and	ax,mask min		;get min part
	mov	cl,min			;bits to shift
	call	Cnvrt
	stosw
GotTime:ret
GetTime endp


Cnvrt2  proc	near			;convert to ascii
	call	Cnvrt
	cmp	al,'0'			;suppress leading zero
	jne	Cnvrtd
        mov     al,' '
        ret

Cnvrt:  shr	ax,cl
Cnvrt1: aam				;make al into bcd
	or	ax,'00'			; and to ascii
	xchg	al,ah
Cnvrtd: ret
Cnvrt2  endp

	page
;
;	format the date (in AX)

date	record  yr:7,mo:4,dy:5		;packed date

GetDate proc	near			;format the date
	or	ax,ax			;is it zero?
	jz	GotDate

	push	ax			;save date
	and	ax,mask yr		;get year part
	mov	cl,yr			;bits to shift
	call	Cnvrt
	mov	di,offset vyear
	or	al,'8'			;adjust for base year
	stosw

	pop	bx			;get the date back
	push	bx			;save it
	and	bx,mask mo		;get month part
	mov	cl,mo			;bits to shift
	shr	bx,cl
	add	bx,bx			; form month table index
	add	bx,bx
	lea	si,word ptr months-4[bx]
	mov	cx,word ptr 3
	mov	di,offset vmonth
	rep	movsb

	pop	ax			;get the date back
	and	ax,mask dy		;get day part
	mov	cl,dy			;bits to shift
	call	Cnvrt
	mov	di,offset vdate
	stosw
GotDate:ret
GetDate endp

	page

;A severely hacked single/double precision number conversion function.
;Originally from JMODEM, but severely hacked by Toad Hall.
;  ES:DI -> string
;  Destroys everything almost.

;Enter here if integer in AX
Asciify	proc	near

	xor	dx,dx			; clear fake long.hi
	mov	si,ax			;move integer into SI
	xor	ah,ah			;clear msb (flag)
	jmp	short Ascii_Ax		;jump into the code

;Enter here if long integer in DX:AX.
Asciify_Long:

	mov	si,ax			;move long.lo into SI
	xor	ah,ah			;clear msb (flag)
Comment	~
	MOV	CX,3B9AH		; Get billions
	MOV	BX,0CA00H
	CALL	Subtr			; Subtract them out

	MOV	CX,05F5H		; Get hundred-millions
	MOV	BX,0E100H
	CALL	Subtr			; Subtract them out
Comment	ends	~

        and     dx,4FFH                 ;seems likely
	MOV	CX,word ptr 0098H	; Get ten-millions
	MOV	BX,9680H
	CALL	Subtr			; Subtract them out

	MOV	CX,word ptr 000FH	; Get millions
	MOV	BX,4240H
	CALL	Subtr			; Subtract them out

	MOV	CX,word ptr 1		; Get hundred-thousands
	MOV	BX,86A0H
	CALL	Subtr			; Subtract them out

Ascii_Ax:
	xor	cx,cx			; Get ten-thousands
	MOV	BX,2710H
	CALL	Subtr			; Subtract them out
	MOV	BX,03E8H
	CALL	Subtr			; Subtract them out

	MOV	BX,word ptr 0064H
	CALL	Subtr			; Subtract them out
	MOV	BX,word ptr 10
	CALL	Subtr			; Subtract them out
	mov	ax,si			;residual in SI
	add	AL,'0'			; Add bias to residual
	stosb				; Put in the string
	RET

;Common subroutine for Asciify

Subtr:	mov	al,'0'-1

Subtr1:	INC	al			; Bump the digit character
	SUB	si,BX			; Dword subtraction
	SBB	DX,CX
	JNB	Subtr1			; Continue until a carry

	ADD	si,BX			; One too many, add back
	ADC	DX,CX			;   and the remainder

	cmp	al,'0'
	jnz	Subtr2			;nope, turn off leading flag, stuff
        or      ah,ah                   ;no more leading spaces?
        jnz     Sub_Stuff               ;right, stuff the '0'
        mov     al,' '                  ;make it neat with leading spaces
Sub_Stuff:
	stosb				;stuff the char
	RET

Subtr2:	inc	ah			;turn off leading space flag
	stosb
	ret
Asciify	ENDP


;Convert 16-bit binary word in AX
;to hex ASCII string at ES:DI
;(No need to save any registers)

hexchar db	'0123456789ABCDEF'

Cvh	proc	near

        mov     si,offset hexchar       ;for faster access
	mov	dx,ax			; save 16-bits

	mov	bl,dh			; third nibble
        mov     cx,0F04H                ;CL=4 for shifting,
                                        ;CH=0FH for masking
	shr	bl,cl
        mov     al,[si][bx]             ;snarf hex char
	stosb

	mov	bl,dh			; last nibble
        and     bl,ch                   ;0fh
        mov     al,[si][bx]             ;snarf hex char
	stosb

	mov	bl,dl			; first nibble
	sub	bh,bh
        shr     bl,cl                   ; isolate (CL still 4)
        mov     al,[si][bx]             ;snarf hex char
	stosb

	mov	bl,dl			; second nibble
        and     bl,ch   ;0fh            ; isolate
        mov     al,[si][bx]             ;snarf hex char
	stosb
	ret

Cvh	endp

	subttl	'--- i/o data areas'

ArcV	endp

archdr  db      30 dup (0)              ; i/o area for a header

inbuf	db	INBUFSZ dup (0)		;just big enough for ZIP
                                        ;directories and filenames

CSEG	ENDS
	END
