; ZIP.Z80
;
; Create ZIP archives for CPM
; J.G.Harston 06-Nov-2000
;
; Vers 100 -- 31-Aug-2023 -- J.G.Harston
;	Rewritten for ZMac.  Can be assembled with ZMAC with:
;
;	ZMAC -o ZIP.COM ZIP100.Z80
;
; Vers 101 -- 08-Dec-2023 -- Lars Nelson
;	Fixed a problem where the computed CRC for some but
;	not all files was not being written into the Local 
;	File Header especially on CP/M 2.2 and compatible
;	systems.  This caused a problem for CP/M unzip 
;	programs because they use the information in the 
;	Local File Header to extract files.  Lack of a valid
;	CRC causes the unzip programs to skip the file.  
;	Note, central directory was correct so on PCs or
;	Macs all files could be extracted.  nAdded 
;	ability to get file modification date from CP/M Plus
;	and CP/M 2.2 using routines from DSLIB and ZSLIB.  This
;	necessitated making the code relocatable.
;
; To do: add DU: support.  Add ability to add file to a zip
;	file.
;
; Production:
; File is now best assembled and linked with Alex Hawley's ZMAC
; & ZML as opposed to some other assembler named ZMAC.  Set SLR
; true below.  Libraries DSLIB.REL, ZSLIB.REL & SYSLIB.REL are
; required to be in the default directory
;
;	zmac zip101
;	zml zip101
;
; Z80ASM & SLRNK can also be used.  If want to use SLR .REL
; files instead of Microsoft .REL files, add an 'S' to the end
; of the library names, i.e. use DSLIBS.REL, ZSLIBSREL &
; SYSLIBS.REL.
;
Vers		equ	101
VersDD		equ	08
VersMM		equ	12
VersYY		equ	2023
;
false		equ	0
true		equ	not false
;
SLR		equ	true		; set true if using SLR assembler
;
bdos		equ	0005h
FCB1		equ	005Ch
FCB2		equ	006Ch
FCB1PTR		equ	FCB1+32	; Output pointer bytes
DMA		equ	0080h
CCPSIZE		equ	0800h
NAMELEN		equ	12	; "DFILENAMEEXT" in FCB
@FT		EQU	9	; File type
;
bd_conout	equ	2
bd_string	equ	9
bd_open		equ	15
bd_close	equ	16
bd_sfirst	equ	17
bd_snext	equ	18
bd_delete	equ	19
bd_read		equ	20
bd_write	equ	21
bd_make		equ	22
bd_setdma	equ	26
bd_rndrd	equ	33
bd_rndwr	equ	34
bd_getsize	equ	35
bd_setptr	equ	36
;
; These appear to be high memory ROM routines on Mr. Harston's
; CP/M machine so they should be avoided.
;PR1HEX		equ	0FFAAh
;PR2HEX		equ	0FFADh
;OSNEWL		equ	0FFE7h
;
global	ENVPTR,$MEMRY
;
	.request	dslib
	extrn	timini,DOSTYP,DOSVER,TIMTYP,@BDOS,GSTAMP
	extrn	U2MTIM
;
;	.request	zslib
;	extrn	getstp,gstpcp
;
	.request	syslib		; sylib usually last
;
	cseg			; to read in .rel library routines

	jp    Start
	db    'Z3ENV',1		; Z3 signiture
ENVPTR:	dw    0
;
; Code run once, could be overwritten
Start:	ld   (OldStack),sp	; save old stack
	ld   hl,(bdos+1)	; start of BDOS
	ld   de,-CCPSIZE	; allow for CCP
	add  hl,de
	ld   sp,hl		; put stack at top of memory
	ld	de,(ENVPTR)	; check for z-system
	ld	a,d
	or	e			 
	jr	nz,strt
	xor	a		; not running zsystem
strt:	ld	(zflag),a	; clear flag
	call	timini		; check for datestamping support
	; Global variables returned:
	; DOSTYP ='S'=>ZSDOS, 'D'=>Datestamper & '3'=>CP/M Plus.
	;    0=>no datestamping
	; DOSVER has version number, 22=>CP/M 2.2 and
	; compatiblle. 11 for ZSDOS
;
; force zip extention if no extension given on command line
	LD	A,(FCB1+@FT)	; See if any filetype was specified
	CP	' '		;
	JR	NZ,SKPZIP	; If so, leave it as is

	LD	HL,'PI'		; Else inject a ".ZIP" extension
	LD	(FCB1+@FT+1),HL	;
	LD	A,'Z'		;
	LD	(FCB1+@FT+0),A	;

SKPZIP:
	call Start0		; call main program
Exit:	ld   sp,(OldStack)	; recover stack
	ret

Start0:	ld   hl,ZeroStart
	ld   de,ZeroStart+1
	ld   bc,ZeroEnd-ZeroStart-1
	ld   (hl),0
	ldir			; Zero workspace
        ld   a,(FCB2+1)
	cp   32
        jp   z,ZipSyntax	; No filenames
;	ld   hl,0081h		; hl=>start of line, zero terminated
; note: documentation claims the command line at &0080 is unterminated, but
; DRCCP terminates the line with &00, as do other CCPs that follow the same
; practice.
	ld	hl,0080h	; command buffer
	ld	l,(hl)		; get length
	set	7,l		; point after end of line
	inc	hl
	ld	(hl),0		; put terminator in
	ld	l,81h		; hl=>start of line
OptLp1:	ld   bc,0		; b=0, options
	ld   a,(hl)		; get character
	inc  hl
	cp   ' '
	jr   z,OptLp1		; skip spaces
	jr   c,OptDone		; end of command line
	cp   '['
	jr   z,Opts		; start of options string
Optlp3:	ld   a,(hl)
	inc  hl
	cp   ' '
	jr   z,OptLp1
	jr   nc,OptLp3		; skip filename
	jr   OptDone		; end of command line
Opts:	ld   a,(hl)
	inc  hl
	cp   ' '
	jr   z,OptLp1		; another filename
	cp   '?'
	jp   z,ZipSyntax	; ZIP [?] - display usage
	and  05Fh		; upper case
	jr   z,OptDone		; end of command line
	cp   ']'
	jr   nc,OptDone		; end of options
	or   b
	ld   b,a
	jr   Opts
OptDone:
	ld   (Options-1),bc	; note options, also zero Pass
; bit 1 = [O]verwrite
; bit 4 = [Q]uiet
;
; Check for output file
	BIT  1,B
	JR   Z,DeleteOk		; [O]verwrite off
	LD   DE,FCB1
	LD   C,bd_delete	; Delete any existing file
	CALL bdos
DeleteOk:

; GRRRRR Any call to bd_make kills FindFirst and FindNext
; So, have to build up a list of source files first, and then
; do the copying.
; ARAGH!G!HG!I^%!&^$!^%#!!
;
	LD   DE,($MEMRY)		; DE=>heap to make list of names
        LD   C,bd_sfirst
ScanLoop:
	PUSH BC
	PUSH DE
        LD   DE,FCB2
        CALL bdos		; Search directory
        POP  DE
        POP  BC
	INC  A
	JR   Z,ScanDone		; No more files
        ADD  A,A
        ADD  A,A
        ADD  A,A
        ADD  A,A
        ADD  A,A		; Multiply by 32 to point to entry
	ADD  A,DMA-32		; Point to start of filename
	LD   L,A
        LD   H,DMA/256		; HL=>entry
        LD   A,(FCB2+0)		; Source drive
        LD   (HL),A		; Put into file list
	LD   BC,NAMELEN		; DRIVE+FILENAME+EXT
	LDIR			; Copy name to heap
	LD   HL,-0180h		; 128buffer+128buffer+128stack
	ADD  HL,SP		; HL=SP-&180
	SBC  HL,DE		; HL=SP-&180-heaptop
	JP   C,ErrOutOfMem
        LD   C,bd_snext
	JR   ScanLoop
ScanDone:
	DEC  A
	LD   (DE),A		; &FF terminator
	INC  DE
	LD   (OutputBuffer),DE
	LD   HL,128
	ADD  HL,DE
	LD   (InputBuffer),HL
;
; Open output file
	ld	hl,FCB2
	ld	de,FCB2+1
	ld	bc,19
	ld	(hl),0
	ldir			; clear out FCB!
	LD   DE,FCB1
	LD   C,bd_sfirst	; Test for output file
	CALL bdos
	INC  A
	JP   NZ,ErrFileExists
	LD   DE,FCB1
	LD   C,bd_make		; Create output file
	CALL bdos
	INC  A
	JP   Z,ErrDirFull
	ld	de,FCB1
	ld	c,bd_open	; open output file
	call	bdos
	ld	de,DMA
	ld	c,bd_setdma
	call	bdos
	ld	de,FCB1
	ld	c,bd_rndwr	; write anything don't inc CR
; Can no longer use FCB2 as it is now the metadata for FCB1

;
; Now go through list of names
FileStart:
	LD   HL,($MEMRY)	; HL=>heap to make list of names
FileLoop:
	LD   A,(HL)		; HL=>filename
	INC  A
	JP   Z,FilesDone	; No more files
	LD   DE,FCB3
	LD   BC,NAMELEN
	LDIR			; Copy name to FCB3
	PUSH HL
	LD   HL,Header+0	; Clear header workspace
	LD   DE,Header+1
	LD   BC,HeaderEnd-Header-1
	LD   (HL),0
	LDIR
	 if  SLR
	ld	hl,'KP'		; PK little endian
	 else
	LD   HL,'PK'
	 endif
	LD   A,(Pass)
	AND  A
	JR   Z,FileData		; Pass=0, data segment
	LD   (DirSig+0),HL	; Directory signiture
	LD   HL,0201h
	LD   (DirSig+2),HL
	LD   HL,DirName		; HL=>name store in header
	JR   FileDataGo
FileData:
	LD   (HdrSig+0),HL
	LD   HL,0403h		; Put data signiture in
	LD   (HdrSig+2),HL
	LD   HL,NumFiles
	INC  (HL)
	JP   Z,ShutTooManyFiles
	LD   HL,HdrName		; HL=>name store in header
FileDataGo:
;  Move filename in FCB3 to Local Header, print it and get
;  length for Local Header in BC.  Note original code needed
;  FCB3 to be far enough away from record boundry and
;  could not be made relocatable.  Thus, it has been
;  rewritten so that absolute addresses not needed at
;  assembly time.
	LD   DE,FCB3+1		; DE=>name in FCB3
;	LD   BC,0		; BC= Filename length
	ld	b,11		; up to 11 chars to process
	ld	c,0		; filename length
NameLoop:
;	LD   A,E
	ld	a,b		; characters output so far
;	SUB  FCB3 AND 255	; doesn't work in CSEG since
;				; FCB3 is in DSEG
;	CP   12
	cp	3		; finished with name?
;	JR   Z,NameDone		; All 12 chars done
;	CP   9
	CALL Z,NameDot		; If yes, Insert dot & start on ext.
	LD   A,(DE)
	INC  DE
;	CP   ' '
;	JR   Z,NameLoop		; Truncate spaces
	CALL NamePut
	djnz	NameLoop
	jr	NameDone
NameDot:
	LD   A,(DE)
	CP   '!'
	LD   A,'.'
	RET  C			; Don't output dot if no extension
NamePut:
	cp	' '
	ret	z		; remove whitespace
	LD   (HL),A		; Store it in header
	CALL PRCHAR		; Print character
	INC  HL
;	INC  BC
	inc	c
	RET
NameDone:
	LD   (HdrNameSz),BC	; Store name size
	LD   DE,FCB3
	LD   C,bd_getsize	; get file size
	CALL bdos
	LD   DE,(FCB3PTR+0)
	LD   HL,(FCB3PTR+2)
	LD   E,0
	SRL  H
	RR   L
	RR   D
	RR   E			; HLDE=records*128
	LD   (HdrFSize+0),DE
	LD   (HdrSize+0),DE
	LD   (HdrFSize+2),HL
	LD   (HdrSize+2),HL
;	CALL PR2HEX
;	EX   DE,HL
;	CALL PR2HEX

	CALL SavePTR		; save output PTR
	CALL SaveHeader		; save header, also set CRC=&FFFFFFFF
;
	XOR  A
	LD   (FCB3+12),A	; ex=0 for open
	LD   DE,FCB3
	LD   C,bd_open		; open input file
	CALL bdos
	INC  A
	JP   Z,ShutInputFailed
;
; Now that input file is open, get the modify date/time stamp.
;   For CP/M Plus, the date stamp is 4 bytes. 2 bytes for days
;   since 1978 and 2 bytes of bcd Hours and minutes.  For CP/M
;   2.2 /w Datestamper or ZSDOS, the stamp is 5 bytes of
;   bcd year, month, day, hour, & minutes.  ZIP files
;   use the MS-DOS 32 bit packed binary format. Time is 16-bits 
;   with Bits [15:11] = hour, Bits [10:5]  = minutes and
;   Bits [4:0]  = seconds/2 (not displayed).  Date is 16-bits
;   with Bits [15:9] = year - 1980, Bits [8:5] = month and
;   Bits [4:0] = day. Datestamp is stored little endian so time
;   comes first then date.  The DSLIB routine GSTAMP will return
;   either CP/M Plus or Datestamper stamps in Datestamp format, 
;   while U2MTIM will convert Datestamper to MS-DOS format . 
;   (Note ZSDOS can use CP/M style date stamping but returns 
;   stamp in Datestamper format).    
;   QPM, DOSPlus or Z80DOS stamping are not supported at this
;   time.
;
; copy FCB because CP/M Plus returns date in the FCB
	ld	hl,FCB3
	ld	de,FCB4
	ld	bc,36
	ldir
	ld	de,FCB4
	ld	hl,dsbuf
	call	gstamp
	jr	z,NoDS		; no dsatestamp found
; convert packed bcd modify time to MS-DOS fornat.  Note:
; CP/M date stamps have no seconds.
	ld	de,dsbuf+10	; point to modify stamp
	ld	hl,HdrTime	; put MS-DOS time in header
	call	U2MTIM		; convert stamp to MS-DOS
NoDS:
	LD   HL,0
	LD   (FCB3PTR+0),HL	; Set PTR to zero
	LD   (FCB3PTR+2),HL
	LD   A,(Options)
	BIT  4,A
	JR   NZ,CopyLoop	; [Q]uiet
	LD   DE,MsgAdding
	LD   C,bd_string
	CALL bdos
;
; copy data
CopyLoop:
	LD   DE,(InputBuffer)
	LD   C,bd_setdma
	CALL bdos
	LD   DE,FCB3
	LD   C,bd_read
	CALL bdos		; read 128-byte record
	AND  A
	JR   NZ,CopyEOF		; end of input file
	LD   HL,128
	LD   DE,(InputBuffer)
	CALL SaveData		; write 128 bytes to output
	JR   CopyLoop
CopyEOF:
;	CALL PRHEX
	LD   DE,FCB3
	LD   C,bd_close		; close input file
	CALL bdos
;	LD   HL,(HdrCRC+2)
;	CALL PR2HEX
;	LD   HL,(HdrCRC+0)
;	CALL PR2HEX
	
; Update header
	CALL RestorePTR		; move back to saved header
	CALL SaveHeader		; save updated header, also CRC=CRC EOR &FFFFFFFF
	CALL RestorePTR		; back to end of file
;
	CALL CRLF
	POP  HL
	JP   FileLoop

FilesDone:
; At the end of pass zero, all files with local
; headers have been written.  In the next pass through
; the file, we need to build the central directory by
; rewinding the output file, adding file location info
; and writing expanded header info to the central
; directory.  This again requires hoping back and forth
; from the central directory and the local headers as
; we progrss down the output file.

	LD   A,16
	LD   (Options),A	; On second pass, set to [Q]uiet
	LD   A,(Pass)
	AND  A			; end of pass zer0?
	CALL Z,SavePTR		; Save pointer to end of data/start of directory
	CALL Z,ConvertPTR	; Convert saved PTR to absolute offset
				;   for use in central directory
	INC  A
	LD   (Pass),A		; increment pass flag
	CP   2
	JP   C,FileStart	; Do two passes only

; Central directory now written so we need to Create END of
;   Central Direcotry Header
	 if	SLR
	ld	hl,'KP'		; Put signiture in
	 else
	LD   HL,'PK'		; Put signiture in
	 endif
	LD   (EOFSig+0),HL
	LD   HL,0605h
	LD   (EOFSig+2),HL
	LD   HL,0
	LD   (EOFDiskThis),HL
	LD   (EOFDiskStart),HL
	LD   (EOFCommentSz),HL
	LD   HL,(NumFiles)
	LD   H,0
	LD   (EOFFilesThis),HL
	LD   (EOFFilesTotal),HL

	LD   HL,(SavedPTR+0)
	LD   (EOFDirOffset+0),HL
	LD   HL,(SavedPTR+2)
	LD   (EOFDirOffset+2),HL

	CALL  SavePTR
	CALL  ConvertPTR
	AND  A
	LD   HL,(SavedPTR+0)
	LD   DE,(EOFDirOffset+0)
	SBC  HL,DE
	LD   (EOFDirSize+0),HL

	LD   HL,(SavedPTR+2)
	LD   DE,(EOFDirOffset+2)
	SBC  HL,DE
	LD   (EOFDirSize+2),HL
	
	LD   HL,EOFEnd-Header
	CALL SaveEOF		; Save EOF segment
	JR   CloseOutput

ClosePad:
	CALL PutZero
CloseOutput:
	LD   A,(OutputOffset)
	AND  A
	JR   NZ,ClosePad	; Pad to end of sector
ShutOutput:
	LD   DE,FCB1
	LD   C,bd_close		; Close output file
	JP   bdos

SaveHeader:
	LD   HL,HdrCRC
	LD   B,4
SaveCRC:
	LD   A,(HL)		; CRC = CRC EOR &FFFFFFFF
	CPL
	LD   (HL),A
	INC  HL
	DJNZ SaveCRC
	LD   BC,(HdrNameSz)	; Length of name field
	LD   HL,HdrName-Header
	ADD  HL,BC		; HL=length of header
	LD   A,(Pass)
	AND  A
	JR   Z,SaveEOF		; Pass=0, data header
	LD   BC,16		; dir header is 16 bytes longer
	ADD  HL,BC
	LD   DE,Directory
	SCF			; CS=Header, don't build CRC
	JR   SaveDataGo
SaveEOF:
	LD   DE,Header
	SCF			; CS=Header, don't build CRC
	JR   SaveDataGo
;
; HL=count, DE=start of data, FCB1=output file, CC=update CRC
SaveData:
	EXX
	LD   DE,(HdrCRC+0)	; Incoming CRC
	LD   HL,(HdrCRC+2)
	EXX
	AND  A			; CC=Data, build CRC
SaveDataGo:
	PUSH AF
SaveDataLp:
	POP  AF			; Get Carry flag back
	PUSH AF
	LD   A,(DE)
	JR   C,SaveData2	; Don't update CRC

; The following code updates the CRC with the byte in A
	PUSH AF			; Save the byte
	EXX
	XOR  E			; XOR byte into CRC bottom byte
	LD   B,8		; Prepare to rotate 8 bits
CRClp:	SRL  H			; Rotate CRC
	RR   L
	RR   D
	RRA
	JR   NC,CRCclear	; b0 was zero
	LD   E,A		; Put CRC low byte back into E
	LD   A,H
	XOR  0EDh		; CRC=CRC XOR &EDB88320, ZIP polynomic
	LD   H,A
	LD   A,L
	XOR  0B8h
	LD   L,A
	LD   A,D
	XOR  083h
	LD   D,A
	LD   A,E
	XOR  020h		; And get CRC low byte back into A
CRCclear:
	DJNZ CRClp		; Loop for 8 bits
	LD   E,A		; Put CRC low byte back into E
	EXX
	LD   A,(Pass)
	AND  A
	JR   NZ,SaveData0	; Not data segment, don't output
	POP  AF			; Get the byte back
	CALL PutByte		; Output data byte
	JR   SaveData3
SaveData0:
	POP  AF			; Get the byte back
	JR   SaveData3
;
SaveData2:
	CALL PutByte		; Output header byte
SaveData3:
	INC  DE
	DEC  HL
	LD   A,H
	OR   L
	JR   NZ,SaveDataLp
	POP  AF			; Get Carry back
	RET  C			; Don't update CRC
	EXX
	LD   (HdrCRC+0),DE	; Outgoing CRC
	LD   (HdrCRC+2),HL
	EXX
	RET
;
; ConvertPTR -- convert the saved random record field
;   to number of bytes from the beginning of the
;   output file.  Four byte result saved at SavedPTR.
;   Uses A, HL & DE.  Preserves CPU Flags
;
ConvertPTR:
	PUSH AF			; preserve flag
	LD   A,(FCB1Saved+4)
	LD   DE,(FCB1Saved+0)
	LD   HL,(FCB1Saved+2)
	ADD  A,A
	LD   E,A
	SRL  H			; shift HLDE right
	RR   L
	RR   D
	RR   E			; HLDE=records*128+Offset
	LD   (SavedPTR+0),DE	; save result
	LD   (SavedPTR+2),HL
	POP  AF			; restore flags
	RET
; Save PTR - Save the current location in the zip file for later
; return.  Location is saved by using the random record field of
; the FCB.  BDOS function 36 is used set the field.  This three
; byte field is then saved for later use by RestorPTR & ConverPTR.
; Preserves AF & HL.
;
SavePTR:
	PUSH AF			; preserve AF & HL
	PUSH HL
	LD   DE,FCB1
	LD   C,bd_setptr
	CALL bdos		; Set randpm record field
	LD   HL,(FCB1PTR+0)	;save CR & R0
	LD   (FCB1Saved+0),HL
	LD   HL,(FCB1PTR+2)	; save R1 & R2
	LD   (FCB1Saved+2),HL
	LD   A,(OutputOffset)
	LD   (FCB1Saved+4),A
	POP  HL
	POP  AF
	RET
;
; RestorePTR - Move to location in zip file saved by SavePTR.  
; Also saves the current location and moves file access to the 
; location using a random read.  Since random read doesn't
; change the record pointer, subsequent sequential reads or
; writes occur at the same location.  Preserves DE & HL.
;
RestorePTR:
        PUSH HL
        PUSH DE
	LD   DE,FCB1
	LD   C,bd_setptr
	CALL bdos		; Set randpm record field
	LD   HL,(FCB1PTR+0)	; push current random record &
	PUSH HL			; OutputOffset on stack
	LD   HL,(FCB1PTR+2)
	PUSH HL
	LD   A,(OutputOffset)
	PUSH AF
	or	a
	jr	z,RPTR0
	ld	de,(OutputBuffer) ; random write OutputBuffer
	ld	c,bd_setdma	; if offset not zero
	call	bdos
	ld	de,FCB1
	ld	c,bd_rndwr
	call	bdos
RPTR0:
	LD   HL,(FCB1Saved+0)	; get saved random record field
	LD   (FCB1PTR+0),HL	; and put it into the FCB
	LD   HL,(FCB1Saved+2)
	LD   (FCB1PTR+2),HL
	ld	de,(OutputBuffer) ; fill buffer
	ld	c,bd_setdma
	call	bdos
	LD   DE,FCB1
	LD   C,bd_rndrd		; using random read
	CALL bdos
	LD   A,(FCB1Saved+4)
	LD   (OutputOffset),A	; and set offset within buffer
	POP  AF
	LD   (FCB1Saved+4),A
	POP  HL			; save current location
	LD   (FCB1Saved+2),HL
	POP  HL
	LD   (FCB1Saved+0),HL
	POP  DE
	POP  HL
	RET

FlushSave:
        PUSH HL
        PUSH DE
        JR   PutByteSave
PutZero:
	XOR  A
PutByte:
        PUSH HL
        PUSH DE
        LD   DE,(OutputOffset)
        LD   D,0
        LD   HL,(OutputBuffer)
        ADD  HL,DE
	LD   (HL),A
	LD   A,E
	INC  A
        LD   (OutputOffset),A
        CP   080h
        JR   C,PutByteDone	; Buffer not full yet
	XOR  A
        LD   (OutputOffset),A
PutByteSave:
	LD   DE,(OutputBuffer)
	LD   C,bd_setdma
	CALL bdos
	LD   DE,FCB1
	LD   C,bd_write
	CALL bdos
PutByteDone:
	POP  DE
	POP  HL
	RET

CRLF:	LD   A,10
	CALL PRCHAR
	LD   A,13
;
; Output A, save all registers
PRCHAR:	PUSH BC
	PUSH DE
	PUSH HL
	LD   E,A
	LD   A,(Options)
	BIT  4,A
	JR   NZ,PRDONE		; [Q]uiet
	LD   C,bd_conout
	CALL bdos
PRDONE:	POP  HL
	POP  DE
	POP  BC
	RET

ShutInputFailed:
	CALL ShutOutput
	LD   DE,MsgInputFailed
	JR   MsgAbort
ErrOutOfMem:
	LD   DE,MsgOutOfMem
	JR   MsgAbort
ShutTooManyFiles:
	CALL ShutOutput
	LD   DE,MsgTooManyFiles
	JR   MsgAbort
ErrFileExists:
	LD   DE,MsgFileExists
	JR   MsgAbort
ShutDirFull:
	CALL ShutOutput
ErrDirFull:
	LD   DE,MsgDirFull
	JR   MsgAbort
ShutDiskFull:
	CALL ShutOutput
ErrDiskFull:
	LD   DE,MsgDiskFull
	JR   MsgAbort
ZipSyntax:
        LD   DE,MsgSyntax
MsgAbort:
        LD   C,bd_string
        CALL bdos
        JP   Exit

;	dseg

; Initialised data
MsgSyntax:
	DEFM "ZIP    v"
	DEFB '0'+((Vers / 100) MOD 10),'.'
	DEFB '0'+((Vers / 10) MOD 10),'0'+(Vers MOD 10)," - JGH "
; convert VersDD, VersMM, VersYY to date string in dd-mmm-yyyy format
	defb	'0'+(VersDD / 10),'0'+(VersDD mod 10),'-'
	if (VersMM lt 1) or (VersMM gt 12)
	  defb	'xxx'
	else
	  if VersMM eq 1
	    defb	'Jan'
	  endif
	  if VersMM eq 2
	    defb	'Feb'
	  endif
	  if VersMM eq 3
	    defb	'Mar'
	  endif
	  if VersMM eq 4
	    defb	'Apr'
	  endif
	  if VersMM eq 5
	    defb	'May'
	  endif
	  if VersMM eq 6
	    defb	'Jun'
	  endif
	  if VersMM eq 7
	    defb	'Jul'
	  endif
	  if VersMM eq 8
	    defb	'Aug'
	  endif
	  if VersMM eq 9
	    defb	'Sep'
	  endif
	  if VersMM eq 10
	    defb	'Oct'
	  endif
	  if VersMM eq 11
	    defb	'Nov'
	  endif
	  if VersMM eq 12
	    defb	'Dec'
	  endif
	endif
	defb	'-','0'+((VersYY / 1000) mod 10),'0'+((VersYY / 100) mod 10)
	defb	'0'+((VersYY / 10) mod 10),'0'+(VersYY mod 10)
	DEFM 10,13
        DEFM "Usage: ZIP <zipfile>name[.zip] <afn> [/OQ]",10,13
	db	'Options:',13,10
	db	'  Q - Quiet',13,10
	db	'  O - Overwrite existing file',13,10
	db	'  / - Print this helP',13,10
        DEFM "Eg: ZIP OUT.ZIP *.COM",10,13
        DEFM "    ZIP ALLFILES.ZIP *.*",10,13,"$"
MsgAdding:
	DEFM " -- adding","$"
MsgFileExists:
	DEFM "ZipFile already exists",10,13,"$"
MsgDiskFull:
	DEFM "Disk full",10,13,"$"
MsgDirFull:
	DEFM "Dir. full",10,13,"$"
MsgOutOfMem:
	DEFM "Out of memory",10,13,"$"
MsgTooManyFiles:
	DEFM "Too many input files",10,13,"$"
MsgInputFailed:
	DEFM 10,13,"Couldn't open input file",10,13,"$"

$MEMRY:		DS	2

	dseg

; Uninitialised data
OldStack:	DEFW 0000h	; Also end of saved data
ZeroStart:			; Start of area to be zeroed
Pass:		DEFB 0		; 0=local list, 1=directory, 2=EOF
Options:	DEFB 0		; command line options i.e. O and/or Q
NumFiles:	DEFB 0		; Number of entries
;FileOffset:	DS   4		; Offset to data segment entry
InputOffset:	DEFB 0		; Index into input buffer
OutputOffset:	DEFB 0		; Index into output buffer
InputBuffer:	DEFW 0000h	; Input buffer
OutputBuffer:	DEFW 0000h	; Output buffer
ZeroEnd:			; End of area to be zeroed on startup
FCB3:		DEFS 36		; Input file control block
FCB4:		ds	36	; Copy FCB3 here
FCB3PTR		EQU  FCB3+32	; Input file pointer bytes
FCB1SaveOffset	DEFS 1		; Saved output offset
FCB1Saved	DEFS 5		; Saved output PTR
SavedPTR	DEFS 4		; Saved absolute PTR to output file
dsbuf		ds	128	; date stamp buffer
zflag		ds	1	; Zsystem Flag
;
; Central directory overlaps local header
Directory:
DirSig:		DS	2	; Header signature PK,&01,&02
DirMadeBy	EQU	DirSig+4; Version made by (&0000)
;
; Local header
Header:
HdrSig:		DS	4	; Header signature PK,&03,&04
HdrVersion:	DS	2	; Version needed to extract (&0000)
HdrFlags:	DS	2	; General purpose bit flag (&0000)
HdrMethod:	DS	2	; Compression method
HdrTime:	DS	2	; Modification time (&0000)
HdrDate:	DS	2	; Modification date (&0000)
HdrCRC:		DS	4	; 32-bit CRC
HdrSize:	DS	4	; Compressed size
HdrFSize:	DS	4	; Uncompressed size
HdrNameSz:	DS	2	; Filename length
HdrExtraSz:	DS	2	; Extra field length (&0000)
HdrName:	DS	0	; Filename, overlapping next data
;
; Central directory also has:
DirCommentSz:	DS	2	; Comment length (&0000)
DirDisk:	DS	2	; Starting disk number (&0000)
DirAttrsIn:	DS	2	; Internal file attributes (&0000)
DirAttrsEx:	DS	4	; External file attributes (&0000)
DirOffset:	DS	4	; Relative offset of local header
DirName:	DS	12	; Filename, CP/M are max "12345678.123"
HeaderEnd:
;
; EOF header:
EOFSig		EQU	Header+0  ; Header signiture      4 bytes PK,&05,&06
EOFDiskThis	EQU	Header+4  ; Number of this disk   2 bytes (&0000)
EOFDiskStart	EQU	Header+6  ; Number of start disk  2 bytes (&0000)
EOFFilesThis	EQU	Header+8  ; Number of entries     2 bytes (NumFiles)
EOFFilesTotal	EQU	Header+10 ; Total num. entries    2 bytes (NumFiles)
EOFDirSize	EQU	Header+12 ; Size of central dir   4 bytes (OffsetEOF-OffsetDir)
EOFDirOffset	EQU	Header+16 ; Offset to central dir 4 bytes (OffsetDir)
EOFCommentSz	EQU	Header+20 ; Comment length        2 bytes (&0000)
EOFEnd		EQU	Header+22
;
; This is a heap of all data used
; List of filenames read from source in 12-byte FCB format
; &FF terminator
; 128-byte output buffer
; 128-byte input buffer
; ...
; stack
; Top of memory at BDOS-&800