Metropoli BBS
VIEWER: pcunzip.asm MODE: TEXT (ASCII)
comment ~

               Deziper

~
_TEXT          SEGMENT PUBLIC 'CODE'
               ASSUME  CS:_TEXT,DS:_TEXT,ES:_TEXT,SS:_TEXT
               ORG     100H
START:         JMP     MAIN


;              DATA AREA
;              ---------
               DB      CR,SPACE,SPACE,SPACE,CR,LF

COPYRIGHT      DB      "PCUNZIP 1.0 Copyright (c) 1992 Michael J. Mefford",CR,LF
FIRST_RIGHTS   DB      "First Published in PC Magazine, March 31, 1992",CR,LF,LF

SYNTAX         DB      "Syntax: PCUNZIP filename [\path] [/v] [/e] [/o] [/?]",CR,LF,LF

               DB      "/e filespec   = extract specific file",CR,LF
               DB      "/v [filespec] = view zipped files",CR,LF
               DB      "/o            = overwrite any existing files",CR,LF,LF

               DB      "Putting a filename after /e will extract a specific; required",CR,LF
               DB      "Putting a filename after /v will list a specific file",CR,LF,LF,"$"

CR             =       13
LF             =       10
CTRL_Z         =       26
SPACE          =       32
BELL           =       7
Y_SCAN         =       15H
N_SCAN         =       31H
TRUE           =       1
FALSE          =       0

KILOBYTES      =       1024
PARAGRAPH      =       16

DTA            =       80H

MATCHING       STRUC
RESERVED       DB      21 DUP (?)
ATTR           DB              ?
               DW              ?
               DW              ?
               DW              ?
               DW              ?
FILE_NAME      DB      13 DUP (?)
MATCHING       ENDS

LOCAL_HEADER    STRUC
SIGNATURE       DB     "PK",3,4
EXTRACT_VER     DW     ?
BIT_FLAG        DW     ?
ZIP_TYPE        DW     ?
FILE_TIME       DW     ?
FILE_DATE       DW     ?
CRC_32          DW     ?,?
COMPRESS_SIZE   DW     ?,?
UNCOMPRESS_SIZE DW     ?,?
FILENAME_LEN    DW     ?
EXTRA_FIELD_LEN DW     ?
LOCAL_HEADER    ENDS

LOCAL_SIG      DB      "PK",3,4
CENTRAL_SIG    DB      "PK",1,2

;Unshrink stuff

MIN_CODE_SIZE  =       9
MAX_CODE_SIZE  =       13
SPECIAL_CODE   =       256
FIRST_FREE     =       257
TRIE_SIZE      =       (1 SHL MAX_CODE_SIZE) + 1
FREE           =       -1

PREFIX_SIZE    =       TRIE_SIZE * 2    ;Word
SUFFIX_SIZE    =       TRIE_SIZE * 2    ;Char + void
READ_SIZE      =       127 * 512
WRITE_SIZE     =       127 * 512
DIR_SIZE       =       66
LOCAL_STACK    =       512
PREFIX_P       =       PREFIX_SIZE / PARAGRAPH
SUFFIX_P       =       SUFFIX_SIZE / PARAGRAPH
READ_P         =       READ_SIZE / PARAGRAPH
WRITE_P        =       WRITE_SIZE / PARAGRAPH

;Explode stuff

SF_TREE        STRUC
BIT_LENGTH     DB      ?
VALUE          DB      ?
TREE_CODE      DW      ?
SF_TREE        ENDS

LIT_TREE_SIZE  =       256 * SIZE SF_TREE
LEN_TREE_SIZE  =        64 * SIZE SF_TREE
DIST_TREE_SIZE =        64 * SIZE SF_TREE
TREE_ENTRIES   =        - 2

;Expand stuff

FOLLOWER       STRUC
SET_LEN        DB      ?
SET            DB      32 DUP (?)
PAD            DB      ?
FOLLOWER       ENDS


READ_SEG       DW      ?
WRITE_SEG      DW      ?

DIR_LEN        =       65
NAME_LEN       =       12
ZIP_LEN        =       DIR_LEN + NAME_LEN
OUTFILE        DB      NAME_LEN DUP (?), 0
ZIP_NAME       DB      ZIP_LEN DUP (?), 0
DIR_NAME       DB      ZIP_LEN DUP (?), 0
SPECIFIC_NAME  DB      NAME_LEN DUP (?), 0
VIEW_NAME      DB      39 DUP (?)
               DB      CR,LF,"$"


SEARCH_LEN     DW      ZIP_LEN
SEARCH_NAME    DW      ZIP_NAME

FREE_NODE      DW      FIRST_FREE * 2
EXTRACTOR      DW      ?
RUNNING_CRC_32 DW      ?,?

BITS_LEFT      DB      ?
CODE_BITS      DB      ?
SAVE_BYTE      DB      ?

ATTRIBUTE      DB      ?
SPIN_CNT       DB      0
SPIN_INDEX     DW      0
SPINNERS       DB      "/|\"

SPECIFIC_FLAG  DB      FALSE                   ;=TRUE if extract individual file.
VIEW_FLAG      DB      FALSE                   ;=TRUE if view zipped files.
EXTRACTED_FLAG DB      FALSE                   ;=TRUE if a file was extracted.
OVERWRITE_FLAG DB      FALSE                   ;=TRUE if overwrite existing.

ZIP_EXT        DB      ".ZIP"
MEMORY_MSG     DB      "Not enough memory"
CR_LF_LF       DB      LF
CR_LF          DB      CR,LF,"$"
ZIP_MSG        DB      "Zip header not found$"
FAILED_MSG     DB      "Extracted file maybe corrupt",BELL,"$"
DISK_MSG       DB      "Not enough free disk space to extract file$"
ENCRYPTED_MSG  DB      "File is encrypted$"
NOT_FOUND_MSG  DB      "File not found$"
EXISTS_MSG     DB      " already exists.  Overwrite  (Y/N)?$"
UNKNOWN_MSG    DB      "Unknown compression method$"
INVALID_MSG    DB      "Invalid drive$"
DIR_MSG        DB      "Invalid target directory$"
SYNTAX_MSG     DB      "Syntax error$"

;              CODE AREA
;              ---------
MAIN           PROC    NEAR

               CLD

               XOR     BH,BH
               MOV     AH,8
               INT     10H
               MOV     ATTRIBUTE,AH

               MOV     BX,OFFSET STACK_POINTER + 15
               MOV     CL,4
               SHR     BX,CL
               ADD     BX,PREFIX_P + SUFFIX_P + READ_P + WRITE_P
               MOV     AH,4AH                  ;Allocate memory.
               INT     21H
               JNC     SETUP_STACK
               MOV     DX,OFFSET MEMORY_MSG
               JMP     SHORT ERROR_EXIT              ;If not enough, exit.

SETUP_STACK:   MOV     AX,OFFSET STACK_POINTER
               MOV     SP,AX                   ;Set up stack.
               ADD     AX,15
               MOV     CL,4
               SHR     AX,CL
               MOV     CX,DS
               ADD     AX,CX
               ADD     AX,PREFIX_P + SUFFIX_P
               MOV     READ_SEG,AX
               ADD     AX,READ_P
               MOV     WRITE_SEG,AX

               CALL    PARSE
               JC      ERROR_EXIT
               MOV     DX,OFFSET ZIP_NAME
               XOR     CX,CX
               MOV     AH,4EH
               INT     21H
               JMP     SHORT DO_EXTRACT

NEXT_EXTRACT:  CALL    WRITE_CRLF
NEXT_EXTRACT2: MOV     BX,READ_HANDLE
               MOV     AH,3EH
               INT     21H

               PUSH    CS
               POP     ES
               MOV     AH,4FH
               INT     21H
               JNC     DO_EXTRACT

GOOD_EXIT:     XOR     AL,AL
               JMP     SHORT EXIT

DO_EXTRACT:    MOV     SI,OFFSET DTA.FILE_NAME
               MOV     DI,OFFSET ZIP_NAME
               CALL    FORM_NAME

               MOV     DX,OFFSET ZIP_NAME
               MOV     AX,3D00H
               INT     21H
               MOV     DX,OFFSET NOT_FOUND_MSG
               JC      ERROR_EXIT

               MOV     READ_HANDLE,AX
               CALL    EXTRACT
               JNC     NEXT_EXTRACT
               CMP     DX,OFFSET ZIP_MSG
               JZ      NEXT_EXTRACT2

ERROR_EXIT:    PUSH    DX
               CALL    WRITE_CRLF
               POP     DX
               CALL    PRINT_STRING
               CALL    WRITE_CRLFLF
               MOV     DX,OFFSET COPYRIGHT
               CALL    PRINT_STRING
               MOV     AL,1

EXIT:          MOV     AH,4CH
               INT     21H

MAIN           ENDP

;**************
; SUBROUTINES *
;**************

;OUTPUT: CF=1 if parse fail.

PARSE:         MOV     SEARCH_LEN,ZIP_LEN
               MOV     SEARCH_NAME,OFFSET ZIP_NAME
               MOV     SI,81H
NEXT_PARSE:    CALL    PARSE_DELIMIT
               LODSB
               CMP     AL,CR
               JZ      PARSE_DONE
               CMP     AL,"-"
               JZ      SWITCH
               CMP     AL,"/"
               JNZ     DO_FILENAME
SWITCH:        CALL    PARSE_SWITCH
               JC      PARSE_END
               JMP     NEXT_PARSE

DO_FILENAME:   DEC     SI
               MOV     CX,SEARCH_LEN
               MOV     DI,SEARCH_NAME
               CALL    STORE_SEARCH
               MOV     SEARCH_LEN,DIR_LEN
               MOV     SEARCH_NAME,OFFSET DIR_NAME
               JMP     NEXT_PARSE


PARSE_DONE:    MOV     DX,OFFSET SYNTAX_MSG
               CMP     BYTE PTR ZIP_NAME,0
               STC
               JZ      PARSE_END

               MOV     DX,OFFSET ZIP_NAME
               XOR     CX,CX
               MOV     AH,4EH
               INT     21H
               JNC     FOUND_ZIP
               CALL    TACK_ZIP
               MOV     DX,OFFSET ZIP_NAME
               XOR     CX,CX
               MOV     AH,4EH
               INT     21H
               MOV     DX,OFFSET NOT_FOUND_MSG
               JC      PARSE_END

FOUND_ZIP:     CMP     DIR_NAME,0
               JZ      PARSE_END

               MOV     SI,OFFSET DIR_NAME
NEXT_DIR_END:  LODSB
               OR      AL,AL
               JNZ     NEXT_DIR_END
               DEC     SI
               DEC     SI
               CMP     BYTE PTR [SI],":"
               JZ      PARSE_END

               MOV     DX,OFFSET DIR_NAME
               MOV     CX,10H
               MOV     AH,4EH
               INT     21H
               MOV     DX,OFFSET DIR_MSG
               JC      PARSE_END
               TEST    BYTE PTR DS:[DTA.ATTR],10H
               STC
               JZ      PARSE_END

               CMP     BYTE PTR [SI],"\"
               JZ      PARSE_END
               MOV     BYTE PTR [SI+1],"\"
               CLC

PARSE_END:     RET

;----------------------;

PARSE_DELIMIT: LODSB
               CMP     AL,CR
               JZ      DELIMIT_END
               CMP     AL,SPACE
               JBE     PARSE_DELIMIT
DELIMIT_END:   DEC     SI
               RET

;----------------------;

PARSE_SWITCH:  CALL    PARSE_DELIMIT
               CMP     AL,CR
               JZ      SWITCH_ERROR
               LODSB
               AND     AL,5FH
               CMP     AL,"V"
               JNZ     CK_O
               MOV     VIEW_FLAG,TRUE
               CALL    PARSE_DELIMIT
               CMP     AL,CR
               JZ      SWITCH_GOOD
               JMP     SHORT DO_SPECIFIC

CK_O:          CMP     AL,"O"
               JNZ     CK_E
               MOV     OVERWRITE_FLAG,TRUE
               JMP     SHORT SWITCH_GOOD

CK_E:          CMP     AL,"E"
               JNZ     SWITCH_ERROR
               CALL    PARSE_DELIMIT
               CMP     AL,CR
               JZ      SWITCH_ERROR
               MOV     SPECIFIC_FLAG,TRUE

DO_SPECIFIC:   MOV     CX,NAME_LEN
               MOV     DI,OFFSET SPECIFIC_NAME
               CALL    STORE_SEARCH

SWITCH_GOOD:   CLC
               JMP     SHORT SWITCH_END

SWITCH_ERROR:  MOV     DX,OFFSET SYNTAX_MSG
               STC
SWITCH_END:    RET

;----------------------;
; INPUT: DI-> storage; CX=length.

STORE_SEARCH:  LODSB
               CMP     AL,CR
               JZ      STORE_DONE
               CMP     AL,SPACE
               JBE     STORE_END2
               CMP     AL,"a"
               JB      STORE_SEARCH2
               CMP     AL,"z"
               JA      STORE_SEARCH2
               AND     AL,5FH
STORE_SEARCH2: STOSB
               LOOP    STORE_SEARCH
               JMP     SHORT STORE_END2
STORE_DONE:    DEC     SI
STORE_END2:    RET

;----------------------;

TACK_ZIP:      MOV     SI,OFFSET ZIP_NAME
               MOV     CX,ZIP_LEN - 4
NEXT_TACK:     LODSB
               CMP     AL,"."
               JZ      TACK_IT
               CMP     AL,0
               JZ      TACK_IT
               LOOP    NEXT_TACK
               JMP     SHORT TACK_END

TACK_IT:       DEC     SI
               MOV     DI,SI
               MOV     SI,OFFSET ZIP_EXT
               MOV     CX,4
               REP     MOVSB

TACK_END:      RET

;----------------------------------------------;
; INPUT: SI-> name; DI-> storage.

FORM_NAME:     PUSH    SI
               MOV     SI,DI

NEW_END:       MOV     BX,SI
FIND_END:      LODSB
               CMP     AL,":"
               JZ      NEW_END
               CMP     AL,"\"
               JZ      NEW_END
               OR      AL,AL
               JNZ     FIND_END

               POP     SI
               MOV     DI,BX
FORM_NAME2:    LODSB
               STOSB
               OR      AL,AL
               JNZ     FORM_NAME2
               RET

;----------------------------------------------;

;OUTPUT: If CF=1 then DX = failed msg.

EXTRACT:       MOV     BYTES_TO_READ[0],SIZE LOCAL_HEADER
               MOV     BYTES_TO_READ[2],0
               CALL    READ_ZIP
               JNC     CK_HEADER
               JMP     FAILED

CK_HEADER:     MOV     ES,READ_SEG
               MOV     SI,OFFSET LOCAL_SIG
               XOR     DI,DI
               MOV     CX,4
               REPZ    CMPSB
               JZ      PROCESS

               MOV     SI,OFFSET CENTRAL_SIG
               XOR     DI,DI
               MOV     CX,4
               REPZ    CMPSB
               JNZ     PROCESS_ERR
               CLC
               JMP     EXTRACT_END

PROCESS_ERR:   MOV     DX,OFFSET ZIP_MSG
PROCESS_ERR2:  JMP     FAILED2

PROCESS:       CALL    PROCESS_HEADER
               JNC     PROCESS2
               JMP     FAILED2
PROCESS2:      CALL    READ_ZIP
               JC      LILLY_FAIL

               MOV     BYTES_TO_READ[0],AX
               MOV     BYTES_TO_READ[2],BX
               CALL    GET_FILENAME
               JC      EXTRACT

               MOV     DX,OFFSET DIR_NAME
               XOR     CX,CX
               MOV     AH,3CH
               INT     21H
LILLY_FAIL:    JC      FAILED
               MOV     WRITE_HANDLE,AX

               MOV     RUNNING_CRC_32[0],-1
               MOV     RUNNING_CRC_32[2],-1

               CMP     BYTES_TO_READ[0],0
               JNZ     PROCESS3
               CMP     BYTES_TO_READ[2],0
               JZ      PROCESS4

PROCESS3:      CALL    READ_ZIP
               JC      FAILED

               MOV     SAVE_BYTE,0
               MOV     BITS_LEFT,0

               CALL    [EXTRACTOR]

               PUSHF
               MOV     AL,SPACE
               CALL    SPIN_IT
               CALL    WRITE_CRLFLF
               POPF

               JC      FAILED
               CALL    WRITE
               JC      FAILED

PROCESS4:      MOV     BX,WRITE_HANDLE
               MOV     CX,TIME_STAMP
               MOV     DX,DATE_STAMP
               MOV     AX,5701H
               INT     21H
               JC      FAILED
               MOV     AH,3EH
               INT     21H

               CALL    CK_CRC
               JC      FAILED

               CMP     BYTES_TO_READ[0],0
               JNZ     FAILED
               CMP     BYTES_TO_READ[2],0
               JNZ     FAILED
               MOV     EXTRACTED_FLAG,TRUE
               JMP     EXTRACT

FAILED:        MOV     DX,OFFSET FAILED_MSG
FAILED2:       STC

EXTRACT_END:   RET

;----------------------------------------------;
; INPUT:CX=FILENAME_LEN; OUTPUT: CF=1 if skipped.

END_FILENAME   DW      ?

GET_FILENAME:  MOV     AX,CS
               MOV     ES,AX
               MOV     DI,OFFSET OUTFILE
               PUSH    DS
               MOV     DS,READ_SEG
               XOR     SI,SI

NEXT_FILENAME: LODSB
               CMP     AL,"/"
               JNZ     STORE_NAME
               MOV     DI,OFFSET OUTFILE
               JMP     SHORT STORE_NAME2

STORE_NAME:    STOSB
STORE_NAME2:   LOOP    NEXT_FILENAME
               MOV     SI,DI
               XOR     AL,AL
               STOSB
               POP     DS

               MOV     SI,OFFSET OUTFILE
               MOV     DI,OFFSET DIR_NAME
               CALL    FORM_NAME
               DEC     DI
               MOV     END_FILENAME,DI

               CALL    VIEW_OR_SPEC
               JC      FILENAME_END

               MOV     DX,OFFSET DIR_NAME
               MOV     AX,3D00H
               INT     21H
               CMC
               JNC     FILENAME_END

               MOV     BX,AX
               MOV     AH,3EH
               INT     21H

               CMP     OVERWRITE_FLAG,TRUE
               CLC
               JZ      FILENAME_END

               CALL    WRITE_FILENAME
               MOV     DX,OFFSET EXISTS_MSG
               CALL    PRINT_STRING

CK_KEY:        MOV     AH,1                    ;Is there a keystroke available.
               INT     16H
               JZ      NEXT_KEY
CLEAR_IT:      XOR     AH,AH
               INT     16H
               JMP     CK_KEY

NEXT_KEY:      XOR     AH,AH
               INT     16H
               CLC
               CMP     AH,Y_SCAN
               JZ      CONTINUE
               CMP     AL,3                    ;Ctrl break
               JNZ     CK_N
               JMP     GOOD_EXIT
CK_N:          CMP     AH,N_SCAN
               JNZ     NEXT_KEY

               MOV     DX,BYTES_TO_READ[0]
               MOV     CX,BYTES_TO_READ[2]
               MOV     BX,READ_HANDLE
               MOV     AX,4201H
               INT     21H
               STC

CONTINUE:      PUSHF
               CALL    WRITE_CRLFLF
               POPF

FILENAME_END:  RET

;----------------------;
; OUTPUT: CF=1 of should not be extracted.

VIEW_OR_SPEC:  CMP     VIEW_FLAG,TRUE
               JNZ     CK_SPECIFIC
               CMP     SPECIFIC_NAME,0
               JZ      DO_DISPLAY
               CALL    CK_MATCH
               JC      ADVANCE_FILE

DO_DISPLAY:    CALL    DISPLAY_NAME
               JMP     SHORT ADVANCE_FILE

CK_SPECIFIC:   CMP     SPECIFIC_FLAG,TRUE
               CLC
               JNZ     V_OR_S_END
               CALL    CK_MATCH
               JNC     V_OR_S_END

ADVANCE_FILE:  MOV     DX,BYTES_TO_READ[0]
               MOV     CX,BYTES_TO_READ[2]
               MOV     BX,READ_HANDLE
               MOV     AX,4201H
               INT     21H
               JC      V_OR_S_END
               CMC

V_OR_S_END:    RET

;----------------------;
; OUTPUT: CF=1 if no match.

CK_MATCH:      MOV     SI,OFFSET SPECIFIC_NAME
               MOV     DI,OFFSET OUTFILE

NEXT_NAME:     LODSB
               MOV     AH,[DI]
               CMP     AL,"*"
               JZ      FIND_DOT

CK_Q:          CMP     AL,"?"
               JNZ     COMPARE_NAME
               OR      AH,AH
               JZ      NEXT_NAME
               CMP     AH,"."
               JZ      NEXT_NAME
               INC     DI
               JMP     NEXT_NAME

COMPARE_NAME:  INC     DI
               CMP     AL,AH
               JNZ     NO_MATCH
               OR      AL,AL
               JZ      MATCH
               CMP     AL,"."
               JNZ     NEXT_NAME
               JMP     SHORT NEXT_EXT

FIND_DOT:      LODSB
               CMP     AL,"."
               JZ      DO_OUTFILE
               OR      AL,AL
               JNZ     FIND_DOT
               DEC     SI

DO_OUTFILE:    MOV     AL,[DI]
               OR      AL,AL
               JZ      NEXT_EXT
               INC     DI
               CMP     AL,"."
               JNZ     DO_OUTFILE

NEXT_EXT:      LODSB
               MOV     AH,[DI]
               CMP     AL,"*"
               JZ      MATCH
               CMP     AL,"?"
               JNZ     COMPARE_EXT
               OR      AH,AH
               JZ      NEXT_EXT
               INC     DI
               JMP     NEXT_EXT

COMPARE_EXT:   INC     DI
               CMP     AL,AH
               JNZ     NO_MATCH
               OR      AL,AL
               JNZ     NEXT_EXT

MATCH:         CLC
               JMP     SHORT CK_MATCH_END

NO_MATCH:      STC

CK_MATCH_END:  RET

;----------------------;
 
DISPLAY_NAME:  MOV     EXTRACTED_FLAG,TRUE

               MOV     DI,OFFSET VIEW_NAME
               MOV     AL,SPACE
               MOV     CX,SIZE VIEW_NAME
               REP     STOSB

               MOV     SI,OFFSET OUTFILE       ;Point to filename.
               MOV     DI,OFFSET VIEW_NAME
               MOV     CX,NAME_LEN             ;Store 12 bytes of filename.
               JMP     SHORT NEXT_STORE2

EXTENSION:     ADD     DI,CX
               MOV     CX,3
               SUB     DI,CX

NEXT_STORE2:   LODSB                           ;Get a byte.
               OR      AL,AL                   ;End of filename?
               JZ      END_STORE               ;If yes, finish with blanks.
               CMP     AL,"."                  ;Is it the period?
               JZ      EXTENSION

               STOSB                           ;Store byte.
               LOOP    NEXT_STORE2             ;Get next byte.
END_STORE:     ADD     DI,CX

STORE_SIZE:    PUSH    DI                      ;Save pointer.
               ADD     DI,8                    ;Move to end of bytes field.
               MOV     DX,SIZE_LOW             ;Retrieve high and low words
               MOV     AX,SIZE_HIGH            ; of size in bytes.
               MOV     BX,10
               STD                             ;Reverse direction.
NEXT_SIZE:     MOV     CX,DX                   ;Low word in CX.
               XOR     DX,DX                   ;Zero in high half.
               DIV     BX                      ;Convert to decimal.
               XCHG    AX,CX                   ;Retrieve low word.
               DIV     BX
               XCHG    AX,DX                   ;Retrieve remainder.
               ADD     AL,"0"                  ;Convert to ASCII.
               STOSB                           ;Store it.
               MOV     AX,CX                   ;Are we done?
               OR      CX,DX
               JNZ     NEXT_SIZE               ;If no, divide again.

               CLD                             ;Back to forward direction.
               POP     DI                      ;Retrieve pointer.
               ADD     DI,11                   ;Move to date field.

STORE_DATE:    MOV     DX,DATE_STAMP           ;Retrieve date.
               MOV     AX,DX
               MOV     CL,5                    ;Shift to lowest bits.
               SHR     AX,CL
               AND     AX,1111B                ;Mask off all but month.
               MOV     CL,0FFH                 ;Flag as no leading zeros.
               MOV     CH,"-"                  ;Delimiting character.
               CALL    STORE_WORD              ;Store it.

               MOV     AX,DX                   ;Retrieve date.
               AND     AX,11111B               ;Mask off all but day.
               XOR     CL,CL                   ;Flag include leading zeros.
               CALL    STORE_WORD              ;Store it.

               MOV     AX,DX                   ;Retrieve date for last time.
               MOV     CL,9
               SHR     AX,CL                   ;Mask off all but year.
               ADD     AX,80                   ;Adjust to ASCII.
               CMP     AX,100                  ;Past year 2000?
               JB      DISPLAY_DATE            ;If no, display. Else, adjust for
               SUB     AX,100                  ; next century. (Planning ahead!)
DISPLAY_DATE:  XOR     CL,CL                   ;Display leading zeros.
               MOV     CH,SPACE
               CALL    STORE_WORD              ;Store it.

TIME:          INC     DI                      ;Move to time field.
               MOV     DX,TIME_STAMP           ;Retrieve time.
               MOV     AX,DX
               MOV     CL,11                   ;Shift to hours bits.
               SHR     AX,CL
               PUSH    AX
               CMP     AX,12                   ;Past noon?
               JBE     MERIDIAN
               SUB     AX,12                   ;If yes, adjust.
MERIDIAN:      CMP     AX,0                    ;Midnight?
               JNZ     NOT_MIDNIGHT
               MOV     AX,12                   ;If yes, adjust.
NOT_MIDNIGHT:  MOV     CL,0FFH                 ;Suppress leading zeros.
               MOV     CH,":"
               CALL    STORE_WORD              ;Store it.

               MOV     AX,DX                   ;Retrieve time.
               MOV     CL,5                    ;Shift to minutes bits.
               SHR     AX,CL
               AND     AX,111111B              ;Mask off all but minutes.
               XOR     CL,CL
               POP     DX                      ;Retrieve hours.
               MOV     CH,"p"                  ;Assume PM.
               CMP     DX,12                   ;Is it PM?
               JAE     PM
               MOV     CH,"a"                  ;If no, AM.

PM:            CALL    STORE_WORD              ;Store it.
               MOV     DX,OFFSET VIEW_NAME
               MOV     AH,9H
               INT     21H
               RET

;-----------------------------------------------------------------------;
; Converts a two byte hex number to decimal followed by delimiter.      ;
; INPUT: AX = hex number; BL = 10; CH = delimiter character to store.   ;
;   CL = 0 if zeros are to be stored; CL = -1 if leading zeros ignored. ;
;   ES:DI points to storage.                                            ;
;-----------------------------------------------------------------------;
STORE_WORD:    DIV     BL                      ;Divide by ten.
               ADD     AX,"00"                 ;Convert to ASCII.
               CMP     CL,0                    ;Are we to display leading zero?
               JZ      STORE_IT                ;If yes, store as is.
               CMP     AL,"0"                  ;Is it a leading zero?
               JNZ     STORE_IT                ;If no, store it.
               MOV     AL,SPACE                ;Else, store a space.
STORE_IT:      STOSW
               MOV     AL,CH                   ;Store delimiter character also.
               STOSB
               RET

;----------------------------------------------;
;OUTPUT: AX=COMPRESS_SIZE[0]; BX=COMPRESS_SIZE[2]; CX=FILENAME_LEN;
;        CF=1 if failed.

COMPRESS_FLAG  DB      ?
TIME_STAMP     DW      ?
DATE_STAMP     DW      ?
FILE_CRC       DW      ?,?
SIZE_HIGH      DW      ?
SIZE_LOW       DW      ?

REDUCE_L       DW      ?
REDUCE_F       DW      ?
REDUCE_D1      DB      ?
REDUCE_D2      DW      ?

PROCESS_HEADER:PUSH    DS
               MOV     DS,READ_SEG
               MOV     AX,DS:BIT_FLAG
               MOV     CS:COMPRESS_FLAG,AL
               TEST    AX,1
               MOV     DX,OFFSET ENCRYPTED_MSG
               JNZ     PROCESS_FAIL

               CALL    PROCESS_TYPE
               MOV     DX,OFFSET UNKNOWN_MSG
               JC      PROCESS_FAIL

               XOR     DL,DL
               MOV     AH,36H
               INT     21H
               CMP     AX,0FFFFH
               MOV     DX,OFFSET INVALID_MSG
               JZ      PROCESS_FAIL
               MUL     BX
               MUL     CX
               SUB     AX,DS:UNCOMPRESS_SIZE[0]
               SBB     DX,DS:UNCOMPRESS_SIZE[2]
               MOV     DX,OFFSET DISK_MSG
               JC      PROCESS_FAIL

               MOV     AX,DS:UNCOMPRESS_SIZE[0]
               MOV     CS:SIZE_LOW,AX
               MOV     AX,DS:UNCOMPRESS_SIZE[2]
               MOV     CS:SIZE_HIGH,AX

               MOV     CX,DS:FILENAME_LEN
               MOV     DX,DS:EXTRA_FIELD_LEN
               ADD     DX,CX
               MOV     AX,DS:COMPRESS_SIZE[0]
               MOV     BX,DS:COMPRESS_SIZE[2]
               MOV     SI,DS:FILE_TIME
               MOV     DI,DS:FILE_DATE
               MOV     BP,DS:CRC_32[0]
               MOV     CS:FILE_CRC[0],BP
               MOV     BP,DS:CRC_32[2]
               CLC
               JMP     SHORT PROCESS_END

PROCESS_FAIL:  STC

PROCESS_END:   POP     DS
               MOV     FILE_CRC[2],BP
               MOV     BYTES_TO_READ[0],DX
               MOV     TIME_STAMP,SI
               MOV     DATE_STAMP,DI
               RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

PROCESS_TYPE:  MOV     BX,DS:ZIP_TYPE
               MOV     AX,OFFSET UNSTORE
               OR      BX,BX
               JZ      STORE_EXTRACT

               MOV     AX,OFFSET UNSHRINK
               DEC     BX
               JZ      STORE_EXTRACT

               MOV     AX,OFFSET EXPAND
               MOV     DX,7FH
               MOV     CX,701H
               DEC     BX
               JZ      STORE_EXPAND

               MOV     DX,3FH
               MOV     CX,603H
               DEC     BX
               JZ      STORE_EXPAND

               MOV     DX,1FH
               MOV     CX,507H
               DEC     BX
               JZ      STORE_EXPAND

               MOV     DX,0FH
               MOV     CX,40FH
               DEC     BX
               JNZ     CK_EXPLODE

STORE_EXPAND:  MOV     CS:REDUCE_L,DX
               MOV     CS:REDUCE_F,DX
               MOV     CS:REDUCE_D1,CH
               XOR     CH,CH
               MOV     CS:REDUCE_D2,CX
               JMP     SHORT STORE_EXTRACT

CK_EXPLODE:    MOV     AX,OFFSET EXPLODE
               DEC     BX
               JZ      STORE_EXTRACT
               STC
               JMP     SHORT TYPE_END

STORE_EXTRACT: MOV     CS:EXTRACTOR,AX
               CLC
TYPE_END:      RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

CK_CRC:        MOV     AX,RUNNING_CRC_32[0]
               MOV     DX,RUNNING_CRC_32[2]
               XOR     AX,0FFFFH
               XOR     DX,0FFFFH
               CMP     AX,FILE_CRC[0]
               STC
               JNZ     CRC_END
               CMP     DX,FILE_CRC[2]
               STC
               JNZ     CRC_END
               CLC
CRC_END:       RET

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

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

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

EXTRACT_MSG    DB      "Extracting $"

UNSTORE:       MOV     DX,OFFSET EXTRACT_MSG
               CALL    PRINT_STRING
               CALL    WRITE_FILENAME

UNSTORE_SPIN:  CALL    SPINNER

NEXT_UNSTORE:  INC     SPIN_CNT
               JZ      UNSTORE_SPIN

               MOV     ES,READ_SEG
               MOV     SI,READ_PTR
               CMP     SI,BYTES_READ
               JZ      GET_BYTES2
               MOV     AL,ES:[SI]
               INC     READ_PTR
               CALL    STORE_CHAR
               JNC     NEXT_UNSTORE
               CLC
               JMP     SHORT UNSTORE_END

GET_BYTES2:    CALL    READ_ZIP
               JNC     NEXT_UNSTORE
               CLC

UNSTORE_END:   RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

EXPAND_MSG     DB      "Expanding $"

STATE          DW      ?
LAST_CHAR      DB      ?

EXPAND:        MOV     DX,OFFSET EXPLODE_MSG
               CALL    PRINT_STRING
               CALL    WRITE_FILENAME

               MOV     WRITE_FLAG,0
               MOV     LAST_CHAR,0
               MOV     STATE,OFFSET STATE_ZERO

               CALL    LOAD_FOLLOWERS
               JC      EXPAND_END

EXPAND_SPIN:   CALL    SPINNER

NEXT_EXPAND:   INC     SPIN_CNT
               JZ      EXPAND_SPIN

               MOV     AX,SIZE FOLLOWER
               MUL     LAST_CHAR
               MOV     BP,AX

               CMP     BYTE PTR FOLLOWERS[BP],0
               JNZ     GET_BIT

               MOV     BL,8
               CALL    GET_CODE
               JNC     DO_EXPAND
               JMP     SHORT EXPAND_END

GET_BIT:       MOV     BL,1
               CALL    GET_CODE
               JC      EXPAND_END
               OR      AL,AL
               JZ      GET_FOLLOW

               MOV     BL,8
               CALL    GET_CODE
               JNC     DO_EXPAND
               JMP     SHORT EXPAND_END

GET_FOLLOW:    MOV     BH,FOLLOWERS[BP]
               SUB     BH,1
               JC      EXPAND_ERR
               MOV     BL,8
               JMP     SHORT NEXT_BIT2
NEXT_BIT:      DEC     BL
NEXT_BIT2:     SHL     BH,1
               JNC     NEXT_BIT
               CALL    GET_CODE
               JC      EXPAND_END
               MOV     SI,AX
               INC     SI
               MOV     AL,FOLLOWERS[BP+SI]
               XOR     AH,AH

DO_EXPAND:     MOV     LAST_CHAR,AL
               CALL    [STATE]
               JNC     NEXT_EXPAND
EXPAND_ERR:    STC
               JMP     SHORT EXPAND_END2

EXPAND_END:    CLC
EXPAND_END2:   RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

LOAD_FOLLOWERS:MOV     BP,255 * SIZE FOLLOWER
NEXT_FOLLOW:   MOV     BL,6
               CALL    GET_CODE
               JC      FOLLOWERS_END
               MOV     FOLLOWERS[BP],AL
               OR      AL,AL
               JZ      LOOP_FOLLOW
               XOR     DI,DI

NEXT_FOLLOW2:  MOV     BL,8
               CALL    GET_CODE
               JC      FOLLOWERS_END
               INC     DI
               MOV     FOLLOWERS[BP+DI],AL
               MOV     AX,DI
               CMP     AL,FOLLOWERS[BP]
               JB      NEXT_FOLLOW2

LOOP_FOLLOW:   SUB     BP,SIZE FOLLOWER
               JNC     NEXT_FOLLOW
               CLC

FOLLOWERS_END: RET

;----------------------------------------------;
; INPUT: AL=Char; OUTPUT: CF=1 if failed.

DLE            =       144

STATE_ZERO:    CMP     AL,DLE
               JZ      CHANGE_STATE
               CALL    STORE_CHAR
               RET

CHANGE_STATE:  MOV     STATE,OFFSET STATE_ONE
               CLC
ZERO_END:      RET

;----------------------------------------------;
; INPUT: AL=Char; OUTPUT: CF=1 if failed.

SAVE_REDUCE    DW      ?
EXPAND_LEN     DW      ?

STATE_ONE:     OR      AL,AL
               JZ      WRITE_DLE
               MOV     SAVE_REDUCE,AX
               AND     AX,REDUCE_L
               MOV     EXPAND_LEN,AX
               MOV     BX,OFFSET STATE_THREE
               CMP     AX,REDUCE_F
               JNZ     SAVE_STATE
               MOV     BX,OFFSET STATE_TWO
SAVE_STATE:    MOV     STATE,BX
               CLC
               RET

WRITE_DLE:     MOV     AL,DLE
               CALL    STORE_CHAR
               MOV     STATE,OFFSET STATE_ZERO
               RET

;----------------------------------------------;
; INPUT: AL=Char; OUTPUT: CF=1 if failed.

STATE_TWO:     ADD     EXPAND_LEN,AX
               MOV     STATE,OFFSET STATE_THREE
               CLC
               RET

;----------------------------------------------;
; INPUT: AL=Char; OUTPUT: CF=1 if failed.

STATE_THREE:   MOV     BX,SAVE_REDUCE
               MOV     CL,REDUCE_D1
               SHR     BX,CL
               AND     BX,REDUCE_D2
               MOV     CL,8
               SHL     BX,CL
               ADD     BX,AX
               INC     BX

               MOV     CX,EXPAND_LEN
               INC     CX
               INC     CX
               INC     CX
               CALL    BACKWARDS
               MOV     STATE,OFFSET STATE_ZERO
               RET

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

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

EXPLODE_MSG    DB      "Exploding $"

DISTANCE       DW      ?
DICT_BITS      DB      ?
MIN_MATCH_LEN  DW      ?
WRITE_FLAG     DB      ?

EXPLODE:       MOV     DX,OFFSET EXPLODE_MSG
               CALL    PRINT_STRING
               CALL    WRITE_FILENAME

               MOV     WRITE_FLAG,0
               CALL    LOAD_TREES
               JC      LILLY_EXIT4

EXPLODE_SPIN:  CALL    SPINNER

NEXT_EXPLODE:  INC     SPIN_CNT
               JZ      EXPLODE_SPIN

               MOV     BL,1
               CALL    GET_CODE
               JC      LILLY_EXIT3
               OR      AX,AX
               JZ      SLIDING_DICT
               TEST    COMPRESS_FLAG,100B
               JZ      GET_A_CODE
               MOV     BP,OFFSET LIT_TREE
               CALL    READ_TREE
               JC      LILLY_EXIT3
               JMP     SHORT STORE_BYTE

GET_A_CODE:    MOV     BL,8
               CALL    GET_CODE
               JC      LILLY_EXIT3

STORE_BYTE:    CALL    STORE_CHAR
               JNC     NEXT_EXPLODE
               JMP     EXPLODE_END2

LILLY_EXIT3:   CLC
LILLY_EXIT4:   JMP     EXPLODE_END2


SLIDING_DICT:  MOV     BL,DICT_BITS
               CALL    GET_CODE
               JC      EXPLODE_END2
               MOV     DISTANCE,AX

               MOV     BP,OFFSET DISTANCE_TREE
               CALL    READ_TREE
               JC      EXPLODE_END2
               MOV     CL,DICT_BITS
               SHL     AX,CL
               OR      DISTANCE,AX

               MOV     BP,OFFSET LENGTH_TREE
               CALL    READ_TREE
               JC      EXPLODE_END2
               MOV     DI,AX
               ADD     AX,MIN_MATCH_LEN
               CMP     DI,63
               JNZ     DO_BACKWARDS
               MOV     DI,AX
               MOV     BL,8
               CALL    GET_CODE
               JC      EXPLODE_END
               ADD     AX,DI


DO_BACKWARDS:  MOV     CX,AX                   ;Length
               MOV     BX,DISTANCE
               INC     BX
               CALL    BACKWARDS
               JNC     NEXT_EXPLODE
               JMP     SHORT EXPLODE_END2

EXPLODE_END:   CLC
EXPLODE_END2:  RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

LOAD_TREES:    MOV     AL,COMPRESS_FLAG
               MOV     AH,7
               TEST    AL,10B
               JNZ     STORE_DICT
               DEC     AH
STORE_DICT:    MOV     DICT_BITS,AH

               MOV     MIN_MATCH_LEN,2
               TEST    AL,100B
               JZ      OTHER_TREES

               MOV     MIN_MATCH_LEN,3
               MOV     BP,OFFSET LIT_TREE
               MOV     WORD PTR TREE_ENTRIES[BP],256
               CALL    LOAD_TREE
               JC      LOAD_TR_END

OTHER_TREES:   MOV     BP,OFFSET LENGTH_TREE
               MOV     WORD PTR TREE_ENTRIES[BP],64
               CALL    LOAD_TREE
               JC      LOAD_TR_END

               MOV     BP,OFFSET DISTANCE_TREE
               MOV     WORD PTR TREE_ENTRIES[BP],64
               CALL    LOAD_TREE

LOAD_TR_END:   RET

;----------------------------------------------;
; INPUT: BP=Tree index. OUTPUT: CF=1 if failed.

LOAD_TREE:     CALL    READ_LEN
               JC      LOAD_TREE_END
               CALL    SORT_LEN
               CALL    MAKE_TREE
               CALL    REVERSE_BITS
               CLC
LOAD_TREE_END: RET

;----------------------------------------------;
; INPUT: BP=Tree index. OUTPUT: CF=1 if failed.

TREE_BYTES     DB      ?

READ_LEN:      MOV     BL,8
               CALL    GET_CODE
               JC      READ_LEN_END
               INC     AX
               MOV     TREE_BYTES,AL
               XOR     DI,DI

NEXT_LEN:      MOV     BL,8
               CALL    GET_CODE
               JC      READ_LEN_END
               MOV     AH,AL
               MOV     CL,4
               SHR     AH,CL
               AND     AL,0FH
               INC     AH
               INC     AL

NEXT_LEN2:     MOV     CX,DI
               MOV     SI,DI
               SHL     SI,1
               SHL     SI,1
               MOV     BIT_LENGTH[BP+SI],AL
               MOV     VALUE[BP+SI],CL
               INC     DI
               DEC     AH
               JNZ     NEXT_LEN2

               DEC     TREE_BYTES
               JNZ     NEXT_LEN
               CLC

READ_LEN_END:  RET

;----------------------------------------------;
; INPUT: BP=Tree index.

SORT_LEN:      MOV     AX,CS
               MOV     ES,AX
               MOV     AX,SIZE SF_TREE
               MUL     WORD PTR TREE_ENTRIES[BP]
               ADD     AX,BP
               MOV     CX,BP

NEXT_SORT:     MOV     BX,CX
               MOV     DX,CX
               ADD     DX,SIZE SF_TREE
               CMP     DX,AX
               JAE     SORT_END

NEXT_SORT2:    MOV     SI,BX
               MOV     DI,DX
               CMPSB
               JB      LOOP_SORT
               JA      SORT_IT

               CMPSB
               JBE     LOOP_SORT
               DEC     SI
               DEC     DI

SORT_IT:       DEC     SI
               DEC     DI
               PUSH    [DI]
               MOVSW
               POP     [BX]

LOOP_SORT:     ADD     DX,SIZE SF_TREE
               CMP     DX,AX
               JB      NEXT_SORT2
               ADD     CX,SIZE SF_TREE
               JMP     NEXT_SORT

SORT_END:      RET

;----------------------------------------------;
; INPUT: BP=Tree index.

MAKE_TREE:     MOV     AX,SIZE SF_TREE
               MUL     WORD PTR TREE_ENTRIES[BP]
               ADD     AX,BP
               MOV     SI,AX
               XOR     AX,AX                   ;Code
               XOR     BL,BL                   ;Last bit length
               XOR     DI,DI                   ;Code increment

NEXT_MAKE:     SUB     SI,SIZE SF_TREE
               CMP     SI,BP
               JB      MAKE_END
               ADD     AX,DI
               MOV     TREE_CODE[SI],AX
               MOV     BH,BIT_LENGTH[SI]
               CMP     BH,BL
               JZ      NEXT_MAKE

               MOV     BL,BH
               MOV     CL,16
               SUB     CL,BL
               MOV     DI,1
               SHL     DI,CL
               JMP     NEXT_MAKE

MAKE_END:      RET

;----------------------------------------------;
; INPUT: BP=Tree index.

REVERSE_BITS:  MOV     DX,TREE_ENTRIES[BP]
               MOV     SI,BP
NEXT_REV:      INC     SI
               INC     SI                      ;Code
               LODSW
               XOR     BX,BX
               MOV     CX,16
NEXT_REV2:     SHR     AX,1
               RCL     BX,1
               LOOP    NEXT_REV2
               MOV     [SI-2],BX
               DEC     DX
               JNZ     NEXT_REV
               RET

;----------------------------------------------;
; INPUT: BP=Tree index. OUTPUT: AX=code; CF=1 if failed.

INDEX          DW      ?
BITS           DB      ?

READ_TREE:     XOR     DI,DI                   ;Code
               XOR     DX,DX                   ;Bit length
               XOR     CL,CL                   ;Bits
NEXT_READ_T:   MOV     BITS,CL
               MOV     BL,1
               CALL    GET_CODE
               JC      READ_T_END2
               MOV     CL,BITS
               SHL     AX,CL
               OR      DI,AX
               INC     CL
               MOV     SI,DX

NEXT_READ_T2:  SHL     SI,1
               SHL     SI,1
               CMP     BIT_LENGTH[BP+SI],CL
               JA      NEXT_READ_T
               JZ      NEXT_READ_T3
               INC     DX
               MOV     SI,DX
               CMP     SI,TREE_ENTRIES[BP]
               JB      NEXT_READ_T2
               JMP     TREE_ERROR

NEXT_READ_T3:  CMP     TREE_CODE[BP+SI],DI
               JNZ     LOOP_TREE
               MOV     AL,VALUE[BP+SI]
               XOR     AH,AH
               JMP     SHORT READ_T_END

LOOP_TREE:     INC     DX
               MOV     SI,DX
               CMP     SI,TREE_ENTRIES[BP]
               JAE     TREE_ERROR
               SHL     SI,1
               SHL     SI,1
               CMP     BIT_LENGTH[BP+SI],CL
               JZ      NEXT_READ_T3
               JMP     NEXT_READ_T

TREE_ERROR:    STC
               JMP     SHORT READ_T_END2

READ_T_END:    CLC
READ_T_END2:   RET

;----------------------------------------------;
; INPUT: CX=Length; BX=Distance; OUTPUT: CF=1 if failed.

BACKWARDS:     MOV     ES,WRITE_SEG
               MOV     SI,WRITE_PTR
               SUB     SI,BX
               JNC     NEXT_BACK
               CMP     WRITE_FLAG,1
               JZ      END_BUFF

NEXT_NULL:     OR      SI,SI
               JZ      NEXT_BACK
               XOR     AL,AL
               CALL    STORE_CHAR
               JC      BACKWARDS_END
               INC     SI
               LOOP    NEXT_NULL
               JMP     BACKWARDS_END

END_BUFF:      ADD     SI,WRITE_SIZE
NEXT_BACK:     MOV     AL,ES:[SI]
               CALL    STORE_CHAR
               JC      BACKWARDS_END
               INC     SI
               CMP     SI,WRITE_SIZE
               JNZ     LOOP_BACK
               XOR     SI,SI
LOOP_BACK:     LOOP    NEXT_BACK
               CLC
BACKWARDS_END: RET

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

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

BASE_CHAR      DB      ?
PREV_CODE      DW      ?
SAVE_CODE      DW      ?

UNSHRINK_MSG   DB      "Unshrinking $"

UNSHRINK:      MOV     DX,OFFSET UNSHRINK_MSG
               CALL    PRINT_STRING
               CALL    WRITE_FILENAME

               CALL    INIT_UNSHRINK

               MOV     BL,CODE_BITS
               CALL    GET_CODE
               JC      LILLY_EXIT
               MOV     PREV_CODE,AX
               MOV     BASE_CHAR,AL
               CALL    STORE_CHAR
               JC      LILLY_EXIT2

UNSHRINK_SPIN: CALL    SPINNER

NEXT_UNSHRINK: INC     SPIN_CNT
               JZ      UNSHRINK_SPIN

               MOV     BL,CODE_BITS
               CALL    GET_CODE
               JC      LILLY_EXIT
               MOV     SAVE_CODE,AX
               XOR     CX,CX                   ;String counter.

               CMP     AX,SPECIAL_CODE
               JB      STORE_UNSHRUNK
               JA      CK_ERROR

               MOV     BL,CODE_BITS
               CALL    GET_CODE
               JC      LILLY_EXIT
               DEC     AX
               JNZ     CK_CLEAR
               INC     CODE_BITS
               JMP     NEXT_UNSHRINK

CK_CLEAR:      DEC     AX
               STC
               JNZ     LILLY_EXIT2
               CALL    CLEAR
               JMP     NEXT_UNSHRINK

LILLY_EXIT:    CLC
LILLY_EXIT2:   JMP     UNSHRINK_END

CK_ERROR:      CMP     AX,TRIE_SIZE
               JA      ERROR

               MOV     BX,AX
               SHL     BX,1
               CMP     WORD PTR PREFIX[BX],FREE
               JNZ     FIND_STRING2
               PUSH    WORD PTR BASE_CHAR
               INC     CX

               MOV     BX,PREV_CODE
               CMP     BX,FIRST_FREE
               JB      GOT_UNSHRUNK

FIND_STRING:   SHL     BX,1
FIND_STRING2:  PUSH    SUFFIX[BX]
               INC     CX
               MOV     BX,PREFIX[BX]
               CMP     BX,FIRST_FREE
               JAE     FIND_STRING

GOT_UNSHRUNK:  MOV     AL,BL
STORE_UNSHRUNK:MOV     BASE_CHAR,AL

               CALL    STORE_CHAR
               JC      UNSHRINK_END
               JCXZ    ADD_CODE
NEXT_STORE:    POP     AX
               CALL    STORE_CHAR
               JC      UNSHRINK_END
               LOOP    NEXT_STORE

ADD_CODE:      MOV     DI,FREE_NODE
               MOV     AL,BASE_CHAR
               MOV     SUFFIX[DI],AL
               MOV     AX,PREV_CODE
               MOV     PREFIX[DI],AX

NEXT_FREE:     INC     DI
               INC     DI
               CMP     DI,TRIE_SIZE * 2
               JA      ERROR
               CMP     WORD PTR PREFIX[DI],FREE
               JNZ     NEXT_FREE
               MOV     FREE_NODE,DI

               MOV     AX,SAVE_CODE
               MOV     PREV_CODE,AX
               JMP     NEXT_UNSHRINK

ERROR:         STC

UNSHRINK_END:  RET

;----------------------------------------------;
INIT_UNSHRINK: PUSH    CS
               POP     ES
               MOV     DI,OFFSET PREFIX
               MOV     AX,FREE
               MOV     CX,TRIE_SIZE
               REP     STOSW

               MOV     FREE_NODE,FIRST_FREE * 2
               MOV     CODE_BITS,MIN_CODE_SIZE
               RET

;----------------------------------------------;
; INPUT: BITS_LEFT; BL=CODE_BITS; OUTPUT: AX=code; CF=1 if EOF or failed.

GET_CODE:      MOV     CL,BL
               XOR     AX,AX
               MOV     BH,BITS_LEFT
               OR      BH,BH
               JZ      GET_BITS
               MOV     CH,SAVE_BYTE

NEXT_SHIFT2:   SHR     CH,1
               RCR     AX,1
               DEC     BL
               JZ      GOT_CODE
               DEC     BH
               JNZ     NEXT_SHIFT2

GET_BITS:      MOV     SI,READ_PTR
               CMP     SI,BYTES_READ
               JZ      GET_BYTE
               MOV     ES,READ_SEG
               MOV     CH,ES:[SI]
               INC     READ_PTR
               MOV     BH,8
               JMP     NEXT_SHIFT2

GET_BYTE:      CALL    READ_ZIP
               JNC     GET_BITS
               JMP     SHORT GET_CODE_END

GOT_CODE:      DEC     BH
               MOV     SAVE_BYTE,CH
               MOV     BITS_LEFT,BH
               NEG     CL
               ADD     CL,16
               SHR     AX,CL
               CLC
GET_CODE_END:  RET

;----------------------------------------------;
CLEAR:
;Mark all nodes as potentially unused.
               MOV     DX,FREE_NODE
               MOV     AX,8000H
               MOV     CX,FIRST_FREE * 2
               MOV     SI,CX
NEXT_MARK:     CMP     SI,DX
               JZ      CK_REF
               OR      PREFIX[SI],AX
               INC     SI
               INC     SI
               JMP     NEXT_MARK

;Unmark those that are used by other nodes
CK_REF:        MOV     SI,CX
NEXT_REF:      CMP     SI,DX
               JZ      DO_CLEAR
               MOV     AX,PREFIX[SI]
               INC     SI
               INC     SI
               AND     AX,7FFFH
               CMP     AX,FIRST_FREE
               JB      NEXT_REF
               MOV     DI,AX
               SHL     DI,1
               AND     PREFIX[DI],7FFFH
               JMP     NEXT_REF

;Clear the ones that are still marked
DO_CLEAR:      MOV     SI,CX
               MOV     AX,FREE
               JMP     SHORT NEXT_CLEAR2
NEXT_CLEAR:    INC     SI
               INC     SI
NEXT_CLEAR2:   CMP     SI,DX
               JZ      FIND_FREE
               MOV     DI,PREFIX[SI]
               AND     DI,8000H
               JZ      NEXT_CLEAR
               MOV     PREFIX[SI],AX
               JMP     NEXT_CLEAR

;Find first free node
FIND_FREE:     MOV     SI,FIRST_FREE * 2
               JMP     SHORT NEXT_FREE3
NEXT_FREE2:    INC     SI
               INC     SI
NEXT_FREE3:    CMP     PREFIX[SI],AX
               JNZ     NEXT_FREE2
               MOV     FREE_NODE,SI

               RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed or EOF.

BYTES_TO_READ  DW      ?,?
READ_PTR       DW      ?
BYTES_READ     DW      ?
READ_HANDLE    DW      ?

READ_ZIP:      PUSH    AX
               PUSH    BX
               PUSH    CX
               PUSH    DX
               MOV     READ_PTR,0

               MOV     CX,READ_SIZE
               CMP     BYTES_TO_READ[2],0
               JNZ     READ_IT
               MOV     AX,BYTES_TO_READ[0]
               OR      AX,AX
               STC
               JZ      READ_ZIP_END
               CMP     CX,AX
               JBE     READ_IT
               MOV     CX,AX

READ_IT:       PUSH    DS
               MOV     BX,READ_HANDLE
               XOR     DX,DX
               MOV     DS,READ_SEG
               MOV     AH,3FH
               INT     21H
               POP     DS
               JC      READ_ZIP_END

               CMP     AX,CX
               STC
               JNZ     READ_ZIP_END
               MOV     BYTES_READ,AX
               SUB     BYTES_TO_READ[0],AX
               SBB     BYTES_TO_READ[2],0
               CLC

READ_ZIP_END:  POP     DX
               POP     CX
               POP     BX
               POP     AX
               RET

;----------------------------------------------;
; INPUT: AL=Char; OUTPUT: CF=1 if failed.

WRITE_PTR      DW      ?

STORE_CHAR:    MOV     ES,WRITE_SEG
               MOV     DI,WRITE_PTR
               STOSB
               INC     WRITE_PTR

               MOV     BX,RUNNING_CRC_32[0]
               MOV     DX,RUNNING_CRC_32[2]

               XOR     AL,BL
               XOR     AH,AH
               MOV     BP,AX
               SHL     BP,1
               SHL     BP,1

               MOV     AL,8
NEXT_SHIFT:    SAR     DX,1                    ;DX:BX
               RCR     BX,1
               DEC     AL
               JNZ     NEXT_SHIFT
               AND     DX,00FFH

               XOR     BX,WORD PTR CRC_32_TABLE[BP]
               XOR     DX,WORD PTR CRC_32_TABLE[BP+2]

               MOV     RUNNING_CRC_32[0],BX
               MOV     RUNNING_CRC_32[2],DX

               CMP     DI,WRITE_SIZE
               CLC
               JNZ     STORE_END
               CALL    WRITE
STORE_END:     RET

;----------------------------------------------;
; OUTPUT: CF=1 if failed.

WRITE_HANDLE   DW      ?

WRITE:         PUSH    CX
               MOV     CX,WRITE_PTR
               OR      CX,CX
               JZ      WRITE_END
               MOV     BX,WRITE_HANDLE
               XOR     DX,DX
               PUSH    DS
               MOV     DS,WRITE_SEG
               MOV     AH,40H
               INT     21H
               POP     DS
               MOV     WRITE_PTR,0
               MOV     WRITE_FLAG,1

WRITE_END:     POP     CX
               RET

;----------------------------------------------;
WRITE_FILENAME:MOV     DX,OFFSET DIR_NAME
               MOV     SI,END_FILENAME
               PUSH    [SI]
               MOV     BYTE PTR [SI],"$"
               CALL    PRINT_STRING
               POP     [SI]

               MOV     DL,SPACE
               MOV     AH,2
               INT     21H
               RET

;----------------------------------------------;
WRITE_CRLF:    MOV     DX,OFFSET CR_LF
               JMP     SHORT NEW_LINE

WRITE_CRLFLF:  MOV     DX,OFFSET CR_LF_LF
NEW_LINE:      CALL    PRINT_STRING
               RET

;----------------------------------------------;
PRINT_STRING:  MOV     AH,9                    ;Print string via DOS.
               INT     21H
               RET

;----------------------------------------------;
SPINNER:       MOV     SI,SPIN_INDEX
               SUB     SI,1
               JNC     STORE_SPIN
               MOV     SI,2
STORE_SPIN:    MOV     SPIN_INDEX,SI
               MOV     AL,SPINNERS[SI]

SPIN_IT:       XOR     BH,BH
               MOV     CX,1
               MOV     BL,ATTRIBUTE
               MOV     AH,9
               INT     10H
               RET

;----------------------------------------------;

CRC_32_TABLE   LABEL DWORD
DD 000000000h, 077073096h, 0EE0E612Ch, 0990951BAh, 0076DC419h, 0706AF48Fh, 0E963A535h, 09E6495A3h
DD 00EDB8832h, 079DCB8A4h, 0E0D5E91Eh, 097D2D988h, 009B64C2Bh, 07EB17CBDh, 0E7B82D07h, 090BF1D91h
DD 01DB71064h, 06AB020F2h, 0F3B97148h, 084BE41DEh, 01ADAD47Dh, 06DDDE4EBh, 0F4D4B551h, 083D385C7h
DD 0136C9856h, 0646BA8C0h, 0FD62F97Ah, 08A65C9ECh, 014015C4Fh, 063066CD9h, 0FA0F3D63h, 08D080DF5h
DD 03B6E20C8h, 04C69105Eh, 0D56041E4h, 0A2677172h, 03C03E4D1h, 04B04D447h, 0D20D85FDh, 0A50AB56Bh
DD 035B5A8FAh, 042B2986Ch, 0DBBBC9D6h, 0ACBCF940h, 032D86CE3h, 045DF5C75h, 0DCD60DCFh, 0ABD13D59h
DD 026D930ACh, 051DE003Ah, 0C8D75180h, 0BFD06116h, 021B4F4B5h, 056B3C423h, 0CFBA9599h, 0B8BDA50Fh
DD 02802B89Eh, 05F058808h, 0C60CD9B2h, 0B10BE924h, 02F6F7C87h, 058684C11h, 0C1611DABh, 0B6662D3Dh
DD 076DC4190h, 001DB7106h, 098D220BCh, 0EFD5102Ah, 071B18589h, 006B6B51Fh, 09FBFE4A5h, 0E8B8D433h
DD 07807C9A2h, 00F00F934h, 09609A88Eh, 0E10E9818h, 07F6A0DBBh, 0086D3D2Dh, 091646C97h, 0E6635C01h
DD 06B6B51F4h, 01C6C6162h, 0856530D8h, 0F262004Eh, 06C0695EDh, 01B01A57Bh, 08208F4C1h, 0F50FC457h
DD 065B0D9C6h, 012B7E950h, 08BBEB8EAh, 0FCB9887Ch, 062DD1DDFh, 015DA2D49h, 08CD37CF3h, 0FBD44C65h
DD 04DB26158h, 03AB551CEh, 0A3BC0074h, 0D4BB30E2h, 04ADFA541h, 03DD895D7h, 0A4D1C46Dh, 0D3D6F4FBh
DD 04369E96Ah, 0346ED9FCh, 0AD678846h, 0DA60B8D0h, 044042D73h, 033031DE5h, 0AA0A4C5Fh, 0DD0D7CC9h
DD 05005713Ch, 0270241AAh, 0BE0B1010h, 0C90C2086h, 05768B525h, 0206F85B3h, 0B966D409h, 0CE61E49Fh
DD 05EDEF90Eh, 029D9C998h, 0B0D09822h, 0C7D7A8B4h, 059B33D17h, 02EB40D81h, 0B7BD5C3Bh, 0C0BA6CADh
DD 0EDB88320h, 09ABFB3B6h, 003B6E20Ch, 074B1D29Ah, 0EAD54739h, 09DD277AFh, 004DB2615h, 073DC1683h
DD 0E3630B12h, 094643B84h, 00D6D6A3Eh, 07A6A5AA8h, 0E40ECF0Bh, 09309FF9Dh, 00A00AE27h, 07D079EB1h
DD 0F00F9344h, 08708A3D2h, 01E01F268h, 06906C2FEh, 0F762575Dh, 0806567CBh, 0196C3671h, 06E6B06E7h
DD 0FED41B76h, 089D32BE0h, 010DA7A5Ah, 067DD4ACCh, 0F9B9DF6Fh, 08EBEEFF9h, 017B7BE43h, 060B08ED5h
DD 0D6D6A3E8h, 0A1D1937Eh, 038D8C2C4h, 04FDFF252h, 0D1BB67F1h, 0A6BC5767h, 03FB506DDh, 048B2364Bh
DD 0D80D2BDAh, 0AF0A1B4Ch, 036034AF6h, 041047A60h, 0DF60EFC3h, 0A867DF55h, 0316E8EEFh, 04669BE79h
DD 0CB61B38Ch, 0BC66831Ah, 0256FD2A0h, 05268E236h, 0CC0C7795h, 0BB0B4703h, 0220216B9h, 05505262Fh
DD 0C5BA3BBEh, 0B2BD0B28h, 02BB45A92h, 05CB36A04h, 0C2D7FFA7h, 0B5D0CF31h, 02CD99E8Bh, 05BDEAE1Dh
DD 09B64C2B0h, 0EC63F226h, 0756AA39Ch, 0026D930Ah, 09C0906A9h, 0EB0E363Fh, 072076785h, 005005713h
DD 095BF4A82h, 0E2B87A14h, 07BB12BAEh, 00CB61B38h, 092D28E9Bh, 0E5D5BE0Dh, 07CDCEFB7h, 00BDBDF21h
DD 086D3D2D4h, 0F1D4E242h, 068DDB3F8h, 01FDA836Eh, 081BE16CDh, 0F6B9265Bh, 06FB077E1h, 018B74777h
DD 088085AE6h, 0FF0F6A70h, 066063BCAh, 011010B5Ch, 08F659EFFh, 0F862AE69h, 0616BFFD3h, 0166CCF45h
DD 0A00AE278h, 0D70DD2EEh, 04E048354h, 03903B3C2h, 0A7672661h, 0D06016F7h, 04969474Dh, 03E6E77DBh
DD 0AED16A4Ah, 0D9D65ADCh, 040DF0B66h, 037D83BF0h, 0A9BCAE53h, 0DEBB9EC5h, 047B2CF7Fh, 030B5FFE9h
DD 0BDBDF21Ch, 0CABAC28Ah, 053B39330h, 024B4A3A6h, 0BAD03605h, 0CDD70693h, 054DE5729h, 023D967BFh
DD 0B3667A2Eh, 0C4614AB8h, 05D681B02h, 02A6F2B94h, 0B40BBE37h, 0C30C8EA1h, 05A05DF1Bh, 02D02EF8Dh

EVEN
STACK_POINTER  =  $ + LOCAL_STACK + (TRIE_SIZE * 2)
PREFIX         =  OFFSET STACK_POINTER
SUFFIX         =  OFFSET PREFIX + PREFIX_SIZE

LENGTH_TREE    =  OFFSET STACK_POINTER + 2
DISTANCE_TREE  =  OFFSET LENGTH_TREE + LEN_TREE_SIZE + 2
LIT_TREE       =  OFFSET DISTANCE_TREE + DIST_TREE_SIZE + 2

FOLLOWERS      =  OFFSET STACK_POINTER

_TEXT          ENDS
               END     START

[ RETURN TO DIRECTORY ]