; Not copyrighted by Paul Kienitz, 20 June 94.  Last modified 21 Feb 96.
;
; 68000 assembly language version of inflate_codes(), for Amiga.  Prototype:
;
;   int inflate_codes(__GPRO__ struct huft *tl, struct huft *td,
;                     int bl, int bd);
;
; Where __GPRO__ expands to "struct Globals *G," if REENTRANT is defined,
; otherwise to nothing.  In the latter case G is a global variable.
;
; Define the symbol FUNZIP if this is for fUnZip.  It overrides REENTRANT.
;
; Define AZTEC to use the Aztec C macro version of getc() instead of the
; library getc() with FUNZIP.  AZTEC is ignored if FUNZIP is not defined.
;
; Define NO_CHECK_EOF to not use the fancy paranoid version of NEEDBITS --
; this is equivalent to removing the #define CHECK_EOF from inflate.c.
;
; Define INT16 if ints are short, otherwise it assumes ints are long.
;
; *DO NOT* define WSIZE -- this only works with the default value of 32K.

                IFD     INT16
MOVINT           MACRO
        move.w          \1,\2
                 ENDM
INTSIZE equ     2
                ELSE    ; !INT16
MOVINT           MACRO
        move.l          \1,\2
                 ENDM
INTSIZE equ     4
                ENDC

                IFD     REENTRANT
                 IFND   FUNZIP
REENT_G equ     1
                 ENDC
                ENDC

; struct huft is defined as follows:
;
;   struct huft {
;     uch e;                /* number of extra bits or operation */
;     uch b;                /* number of bits in this code or subcode */
;     union {
;       ush n;              /* literal, length base, or distance base */
;       struct huft *t;     /* pointer to next level of table */
;     } v;
;   };                      /* sizeof(struct huft) == 6 */
;
; so here we define the offsets of the various members of this struct:

h_e             equ     0
h_b             equ     1
h_n             equ     2
h_t             equ     2
SIZEOF_HUFT     equ     6

; The following include file is generated from globals.h, and gives us equates
; that give the offsets in struct Globals of the fields we use, which are:
;       ulg bb
;       unsigned int bk, wp
;       (either array of or pointer to unsigned char) slide
; For fUnZip:
;       FILE *in
; For regular UnZip but not fUnZip:
;       int incnt, mem_mode
;       long csize
;       uch *inptr
; It also defines a value SIZEOF_slide, which tells us whether the appropriate
; slide field in G (either area.Slide or redirect_pointer) is a pointer or an
; array instance.  It is 4 in the former case and a large value in the latter.
; Lastly, this include will define CRYPT as 1 if appropriate.

                IFD     FUNZIP
        INCLUDE "amiga/G_offs.fa"
                ELSE
        INCLUDE "amiga/G_offs.a"
                ENDC

; G.bb is the global buffer that holds bits from the huffman code stream, which
; we cache in the register variable b.  G.bk is the number of valid bits in it,
; which we cache in k.  The macros NEEDBITS(n) and DUMPBITS(n) have side effects
; on b and k.

                IFD     REENT_G
G_SIZE  equ     4
G_PUSH           MACRO          ; this macro passes "__G__" to functions
        move.l          G,-(sp)
                 ENDM
                ELSE
        xref    _G              ; struct Globals
G_SIZE  equ     0
G_PUSH           MACRO
        ds.b            0       ; does nothing; the assembler dislikes MACRO ENDM
                 ENDM
                ENDC    ; REENT_G

        xref    _mask_bits      ; const ush near mask_bits[17];
                IFD     FUNZIP
                 IF     CRYPT
        xref    _encrypted      ; int -- boolean flag
        xref    _update_keys    ; int update_keys(__GPRO__ int)
        xref    _decrypt_byte   ; int decrypt_byte(__GPRO)
                 ENDC   ; CRYPT
                ELSE    ; !FUNZIP
        xref    _memflush       ; int memflush(__GPRO__ uch *, ulg)
        xref    _readbyte       ; int readbyte(__GPRO)
                ENDC    ; FUNZIP

        xref    _getc           ; int getc(FILE *)
        xref    _flush          ; if FUNZIP:  int flush(__GPRO__ ulg)
                                ; else:  int flush(__GPRO__ uch *, ulg *, int)

; Here are our register variables.

b       equr    d2              ; ulg
k       equr    d3              ; ush <= 32
e       equr    d4              ; ush < 256 for most use
w       equr    d5              ; unsigned int
n       equr    d6              ; ush
d       equr    d7              ; unsigned int

; We always maintain w and d as valid unsigned longs, though they may be short.

t       equr    a2              ; struct huft *
mask    equr    a3              ; ush *
G       equr    a6              ; struct Globals *

; Couple other items we need:

savregs reg     d2-d7/a2/a3/a6

WSIZE   equ     $8000           ; 32k... be careful not to treat as negative!
EOF     equ     -1

                IFD     FUNZIP
; This does getc(in).  Aztec version is based on #define getc(fp) in stdio.h

                 IFD    AZTEC
        xref    __filbuf
GETC              MACRO
        move.l          in(G),a0
        move.l          (a0),a1         ; in->_bp
        cmp.l           4(a0),a1        ; in->_bend
        blo.s           gci\@
        move.l          a0,-(sp)
        jsr             __filbuf
        addq            #4,sp
        bra.s           gce\@
gci\@:  moveq           #0,d0           ; must be valid as longword
        move.b          (a1)+,d0
        move.l          a1,(a0)
gce\@:
                  ENDM
                 ELSE   ; !AZTEC
GETC              MACRO
        move.l          in(G),-(sp)
        jsr             _getc
        addq            #4,sp
                  ENDM
                 ENDC   ; AZTEC
                ENDC    ; FUNZIP

; Input depends on the NEXTBYTE macro.  This exists in three different forms.
; The first two are for fUnZip, with and without decryption.  The last is for
; regular UnZip with or without decryption.  The resulting byte is returned
; in d0 as a longword, and d1, a0, and a1 are clobbered.

; FLUSH also has different forms for UnZip and fUnZip.  Arg must be a longword.
; The same scratch registers are trashed.

                IFD     FUNZIP

NEXTBYTE         MACRO
        GETC
                  IF    CRYPT
        tst.w           _encrypted+INTSIZE-2    ; test low word if long
        beq.s           nbe\@
        MOVINT          d0,-(sp)                ; save thru next call
        G_PUSH
        jsr             _decrypt_byte
        eor.w           d0,G_SIZE+INTSIZE-2(sp) ; becomes arg to update_keys
        jsr             _update_keys
        addq            #INTSIZE+G_SIZE,sp
nbe\@:
                   IFEQ INTSIZE-2
        ext.l           d0              ; assert -1 <= d0 <= 255
                   ENDC
                  ENDC  ; !CRYPT
                 ENDM

FLUSH            MACRO
        move.l          \1,-(sp)
        G_PUSH
        jsr             _flush
        addq            #4+G_SIZE,sp
                 ENDM

                ELSE    ; !FUNZIP

NEXTBYTE         MACRO
;;        subq.l          #1,csize(G)
;;        bge.s           nbg\@
;;        moveq           #EOF,d0
;;        bra.s           nbe\@
nbg\@:  subq.w          #1,incnt+INTSIZE-2(G)   ; treat as short
        bge.s           nbs\@
        G_PUSH
        jsr             _readbyte
                IFNE    G_SIZE
        addq            #G_SIZE,sp
                ENDC
        bra.s           nbe\@
nbs\@:  moveq           #0,d0
        move.l          inptr(G),a0
        move.b          (a0)+,d0
        move.l          a0,inptr(G)
nbe\@:
                 ENDM

FLUSH            MACRO
        MOVINT          #0,-(sp)                ; unshrink flag: always false
        move.l          \1,-(sp)                ; length
                  IFGT  SIZEOF_slide-4
        pea             slide(G)                ; buffer to flush
                  ELSE
        move.l          slide(G),-(sp)
                  ENDC
        G_PUSH
        tst.w           mem_mode+INTSIZE-2(G)   ; test lower word if long
        beq.s           fm\@
        jsr             _memflush               ; ignores the unshrink flag
        bra.s           fe\@
fm\@:   jsr             _flush
fe\@:   lea             8+INTSIZE+G_SIZE(sp),sp
                 ENDM

                ENDC    ; ?FUNZIP

; Here are the two bit-grabbing macros, defined in their NO_CHECK_EOF form:
;
;   #define NEEDBITS(n) {while(k<(n)){b|=((ulg)NEXTBYTE)<<k;k+=8;}}
;   #define DUMPBITS(n) {b>>=(n);k-=(n);}
;
; Without NO_CHECK_EOF, NEEDBITS reads like this:
;
;   {while(k<(n)){int c=NEXTBYTE;if(c==EOF)return 1;b|=((ulg)c)<<k;k+=8;}}
;
; NEEDBITS clobbers d0, d1, a0, and a1, none of which can be used as the arg
; to the macro specifying the number of bits.  The arg can be a shortword memory
; address, or d2-d7.  The result is copied into d1 as a word ready for masking.
; DUMPBITS has no side effects; the arg must be a d-register (or immediate in the
; range 1-8?) and only the lower byte is significant.

NEEDBITS        MACRO
nb\@:   cmp.w           \1,k            ; assert 0 < k <= 32 ... arg may be 0
        bhs.s           ne\@
        NEXTBYTE                        ; returns in d0.l
                 IFND   NO_CHECK_EOF
        cmp.w           #EOF,d0
        bne.s           nok\@
        moveq           #1,d0           ; PK_WARN?
        bra             return
                 ENDC   ; !NO_CHECK_EOF
nok\@:  lsl.l           k,d0
        or.l            d0,b
        addq.w          #8,k
        bra.s           nb\@
ne\@:   move.w          b,d1
                ENDM

DUMPBITS        MACRO
        lsr.l           \1,b            ; upper bits of \1 are ignored??
        sub.b           \1,k
                ENDM


; ******************************************************************************
; Here we go, finally:

        xdef    _inflate_codes

_inflate_codes:
        link            a5,#-4
        movem.l         savregs,-(sp)
; 8(a5) = tl, 12(a5) = td, 16(a5) = bl, 18|20(a5) = bd... add 4 for REENT_G
; -2(a5) = ml, -4(a5) = md.  Here we cache some globals and args:
                IFD     REENT_G
        move.l          8(a5),G
                ELSE
;;      move.l          _G,G            ; old global pointer version
        lea             _G,G            ; G is now a global instance
                ENDC
        lea             _mask_bits,mask
        move.l          bb(G),b
        MOVINT          bk(G),k
                IFD     INT16
        moveq           #0,w            ; keep this usable as longword
                ENDC
        MOVINT          wp(G),w
        moveq           #0,e            ; keep this usable as longword too
        MOVINT          16+G_SIZE(a5),d0
        add.w           d0,d0
        move.w          (mask,d0.w),-2(a5)      ; ml = mask_bits[bl]
        MOVINT          16+INTSIZE+G_SIZE(a5),d0
        add.w           d0,d0
        move.w          (mask,d0.w),-4(a5)      ; md = mask_bits[bd]

main_loop:
        NEEDBITS        14+INTSIZE+G_SIZE(a5)   ; bl, lower word if long
        and.w           -2(a5),d1               ; ml
        mulu            #SIZEOF_HUFT,d1
        move.l          8+G_SIZE(a5),a0         ; tl
        lea             (a0,d1.l),t
        move.b          h_e(t),e
        cmp.w           #16,e
        bls.s           topdmp
intop:   moveq          #1,d0
         cmp.w          #99,e
         beq            return          ; error in zipfile
         move.b         h_b(t),d0
         DUMPBITS       d0
         sub.w          #16,e
         NEEDBITS       e
         move.w         e,d0
         add.w          d0,d0
         and.w          (mask,d0.w),d1
         mulu           #SIZEOF_HUFT,d1
         move.l         h_t(t),a0
         lea            (a0,d1.l),t
         move.b         h_e(t),e
         cmp.w          #16,e
         bgt.s          intop
topdmp: move.b          h_b(t),d0
        DUMPBITS        d0

        cmp.w           #16,e           ; is this huffman code a literal?
        bne             lenchk          ; no
        move.w          h_n(t),d0       ; yes
                IFGT    SIZEOF_slide-4
        lea             slide(G),a0
                ELSE
        move.l          slide(G),a0
                ENDC
        move.b          d0,(a0,w.l)     ; stick in the decoded byte
        addq.w          #1,w
        cmp.w           #WSIZE,w
        blo             main_loop
        FLUSH           w
        moveq           #0,w
        bra             main_loop       ; do some more

lenchk: cmp.w           #15,e           ; is it an end-of-block code?
        beq             finish          ; if yes, we're done
        NEEDBITS        e               ; no: we have a duplicate string
        move.w          e,d0
        add.w           d0,d0
        and.w           (mask,d0.w),d1
        move.w          h_n(t),n
        add.w           d1,n            ; length of block to copy
        DUMPBITS        e
        NEEDBITS        14+(2*INTSIZE)+G_SIZE(a5)       ; bd, lower word if long
        and.w           -4(a5),d1                       ; md
        mulu            #SIZEOF_HUFT,d1
        move.l          12+G_SIZE(a5),a0                ; td
        lea             (a0,d1.l),t
        move.b          h_e(t),e
        cmp.w           #16,e
        bls.s           middmp
inmid:   moveq          #1,d0
         cmp.w          #99,e
         beq            return          ; error in zipfile
         move.b         h_b(t),d0
         DUMPBITS       d0
         sub.w          #16,e
         NEEDBITS       e
         move.w         e,d0
         add.w          d0,d0
         and.w          (mask,d0.w),d1
         mulu           #SIZEOF_HUFT,d1
         move.l         h_t(t),a0
         lea            (a0,d1.l),t
         move.b         h_e(t),e
         cmp.w          #16,e
         bgt.s          inmid
middmp: move.b          h_b(t),d0
        DUMPBITS        d0
        NEEDBITS        e
        move.w          e,d0
        add.w           d0,d0
        and.w           (mask,d0.w),d1
        move.l          w,d
        sub.w           h_n(t),d
        sub.w           d1,d            ; distance back to block to copy
        DUMPBITS        e

indup:   move.w         #WSIZE,e        ; violate the e < 256 rule
         and.w          #WSIZE-1,d
         cmp.w          d,w
         blo.s          ddgw
          sub.w         w,e
         bra.s          dadw
ddgw:     sub.w         d,e
dadw:    cmp.w          n,e
         bls.s          delen
          move.w        n,e
delen:   sub.w          e,n             ; size of sub-block to copy
                IFGT    SIZEOF_slide-4
         lea            slide(G),a0
                ELSE
         move.l         slide(G),a0
                ENDC
         move.l         a0,a1
         add.l          w,a0            ; w and d are valid longwords
         add.l          d,a1
         move.w         e,d0
         subq           #1,d0           ; assert >= 0 if sign extended
dspin:    move.b        (a1)+,(a0)+     ; string is probably short, so
          dbra          d0,dspin        ; don't use any fancier copy method
         add.w          e,w
         add.w          e,d
         cmp.w          #WSIZE,w
         blo.s          dnfl
         FLUSH          w
         moveq          #0,w
dnfl:    tst.w          n               ; need to do more sub-blocks?
         bne            indup           ; yes
        moveq           #0,e            ; restore zeroness in upper bytes
        bra             main_loop       ; do some more

finish: MOVINT          w,wp(G)         ; restore cached globals
        MOVINT          k,bk(G)
        move.l          b,bb(G)
        moveq           #0,d0           ; return "no error"
return: movem.l         (sp)+,savregs
        unlk            a5
        rts
