/******************************************************************************
*
* project name:    TIGCC Tools Suite
* file name:       unpack.c (improved version of asm_upck.c)
* initial date:    13/03/2002
* authors:         albert@cs.tut.fi
*                  b.denis@libertysurf.fr
*                  thomas.nussbaumer@gmx.net
*                  gforce@calc.org
*                  sstear70@calvin.edu
*
* description:     unpack routine for exepack compression
*
*                  stripped down version - only handles files with
*                  maxgamma==7 ... but other files shouldn't be around, because
*                  this is the standard setting of ttpack
*
* --------------------------------------------------------------------------------------
* to generate the hexcode array (works with TIGCC 0.93 and 0.94):
* --------------------------------------------------------------------------------------
*
* (1) tigcc -Os -Wall -W -Wwrite-strings -fomit-frame-pointer -DSTANDALONE -DNO_CALC_DETECT -mpcrel unpack.c
* (2) ttstrip unpack.89z unpack.bin
* (3) ttextract unpack.bin unpack.bi2 UNPACK_0 UNPACK_1
* (4) ttbin2hex -b2 -c 10 -slash unpack.bi2 unpack.txt
* (5) del unpack.bin
* (6) del unpack.bi2
*
* file unpack.txt contains now the data of the hexcode array
*
*
* -----------------------------------------------------------------------------
*
* based on code from Pasi 'Albert' Ojala, albert@cs.tut.fi
* heavily reduced to fit to the needs by thomas.nussbaumer@gmx.net
* assembler optimizations by b.denis@libertysurf.fr
* assembler optimizations, and ability to run from the FLASH ROM by Greg Dietsche (gforce@calc.org)
* more optimizations by Samuel Stearley (sstear70@calvin.edu)
* code formatting fixed by Lionel Debroux (lionel_debroux@yahoo.fr)
*
* for details on the used algorithm see:
*
* http://www.cs.tut.fi/~albert/Dev/pucrunch/index.html
*
* $Id: unpack.c,v 1.2 2002/03/13 14:26:01 tnussb Exp $
*
******************************************************************************/
//#define ALL_CHECKS  --- just for testing purposes ...

#if STANDALONE
    #define USE_TI89             // only produce the TI89 version
    #define NO_EXIT_SUPPORT      // no exit support for this code
    #include <tigcclib.h>
#endif

//-----------------------------------------------------------------------------
// all structures and defines from ttunpack.h are used here as copies which are
// additionally prefixed with "__"
//-----------------------------------------------------------------------------
typedef struct {
    unsigned char  osize_lo;   // original size lowbyte
    unsigned char  osize_hi;   // original size highbyte
    unsigned char  magic1;     // must be equal to UNPACK_MAGIC1
    unsigned char  magic2;     // must be equal to UNPACK_MAGIC2
    unsigned char  csize_lo;   // compressed size lowbyte
    unsigned char  csize_hi;   // compressed size lowbyte
    unsigned char  esc1;       // escape >> (8-escBits)
    unsigned char  notused3;
    unsigned char  notused4;
    unsigned char  esc2;       // escBits
    unsigned char  gamma1;     // maxGamma + 1
    unsigned char  gamma2;     // (1<<maxGamma)
    unsigned char  extralz;    // extraLZPosBits
    unsigned char  notused1;
    unsigned char  notused2;
    unsigned char  rleentries; // rleUsed
} __PACKHEADER;

#define __MAGIC_CHAR1          0x54
#define __MAGIC_CHAR2          0x50

#define __ERRPCK_OKAY             0
#define __ERRPCK_NOESCFOUND     248
#define __ERRPCK_ESCBITS        249
#define __ERRPCK_MAXGAMMA       250
#define __ERRPCK_EXTRALZP       251
#define __ERRPCK_NOMAGIC        252
#define __ERRPCK_OUTBUFOVERRUN  253
#define __ERRPCK_LZPOSUNDERRUN  254

//---------------------------------------------------------------------------
// Global register variables
// Do NOT create any global variables that are not in a register, or this
// code will not run from the FLASH ROM
//---------------------------------------------------------------------------
register unsigned char *__ibuffer__ asm("a4");  // input buffer
register unsigned short __imask__ asm("d7");    // input buffer byte mask
register unsigned char *__bitTable__ asm("a5"); // lets us easily go to which
                                                // bit is set in the mask.

#define COMBINE_LOWHIGH(lo,hi)  (((unsigned short)lo) | ((unsigned short)(hi << 8)))

//---------------------------------------------------------------------------
// CORRECT_IN_MASK rotates the lower byte of __imask__ one to the right.
// If it "wraps around" from 0x01 to 0x80 pointer __ibuffer__ will be
// increased by one.
//---------------------------------------------------------------------------
//
// NOTE: IT IS VERY IMPORTANT THAT "memory" is part of the clobbered regs list!!
//       otherwise TIGCC produces NOT what we want (the code will crash)
//
// ADDITIONALLY: Note that "volatile" for asm blocks like this is VERY important.
//               Without declaring it volatile, the compiler removes them, because
//               it thinks they are useless!!
#define CORRECT_IN_MASK() ({               \
     asm volatile(                         \
"    ror.b    #1,%%d7\n"                   \
"    bcc.s   0f\n"                         \
"    addq.l  #1,%%a4\n"                  	 \
"0:\n" : :                                 \
:"cc","memory");})


//---------------------------------------------------------------------------
// returns >0 if next bit is set, otherwise 0
//---------------------------------------------------------------------------
#define NEXT_BIT_SET   (*__ibuffer__ & __imask__)

//-----------------------------------------------------------------------------
// Prototypes of the main functions
// UnpackBuffer is the entry point for the unpacking routine and needs to be the
// first code/data following the "UNPACK_0" ascii string
// _UnpackBuffer_ should follow directly after UnpackBuffer in the generated
// asm code
//-----------------------------------------------------------------------------
short UnpackBuffer(unsigned char *src, unsigned char *outbuffer);
short _UnpackBuffer_(unsigned char *src asm("a0"), unsigned char *outbuffer asm("a1"));

//-----------------------------------------------------------------------------
// Prototypes of internal asm functions. These functions are below the unpack
// function by intention. Hopefully the compiler will not rearrange them so
// we can produce the hexcode array style headerfile of the complete function
// block (all functions in this file) ...
//-----------------------------------------------------------------------------
unsigned short __GetBits(unsigned short arg asm("d1"));
unsigned short __GetValue(void);
unsigned short __Get8Bit(void);


//=============================================================================
// the decompression routine
//
// using it is very simple: feed in a filled source array and a buffer which is
// large enough to hold the decompression result
//
// returns 0 if okay
//=============================================================================
#if defined(STANDALONE)
asm(".data\n.ascii \"UNPACK_0\"");  // used to mark block for later extraction
#endif

//This Code must be at the top of the generated asm source code! It is the entry point
//for the _UnpackBuffer_ Routine which actually does the work.
//The purpose of this patch is to save the registers used for __ibuffer__ and __imask__
// and to load a0/a1 for passing to the real unpack function
asm(
"UnpackBuffer:\n"
" move.l 4(%sp),%a0\n"
" move.l 8(%sp),%a1\n"
" movem.l %a5/%a4/%d7,-(%sp)\n"
" move.w #0x0200,%d0\n"
" trap #1\n"
" move.w %d0,-(%a7)\n"
" bsr _UnpackBuffer_\n"
" move.w %d0,%d1\n"
" move.w (%a7)+,%d0\n"
" trap #1\n"
" move.w %d1,%d0\n"
" movem.l (%sp)+,%a5/%a4/%d7\n"
"	rts\n"
);
short _UnpackBuffer_(unsigned char *src asm("a0"), unsigned char *outbuffer asm("a1"))  {
    unsigned short startesc;
    unsigned char* bytecodevec;
    __PACKHEADER*  cth = (__PACKHEADER*)src;
    unsigned short escbits8;
    unsigned short escbits;
    unsigned short extralzposbits;
#ifdef ALL_CHECKS
    unsigned char* dest;
    unsigned char* pend_in;
    unsigned char* pend_out;
#endif

    //---------------------------------------------------------------------
    // check magic markers, gamma settings, escape bits and extra lz-bits
    // settings ... if there are problems return corresponding errorcode
    //---------------------------------------------------------------------
    if (cth->magic1 != __MAGIC_CHAR1 || cth->magic2 != __MAGIC_CHAR2) return __ERRPCK_NOMAGIC;
    if (cth->gamma1 != 8 || cth->gamma2 != 128)                       return __ERRPCK_MAXGAMMA;
    if ((escbits = cth->esc2) > 8)                                    return __ERRPCK_ESCBITS;
    if ((extralzposbits = cth->extralz) > 4)                          return __ERRPCK_EXTRALZP;

    startesc = cth->esc1;
    escbits8 = 8 - escbits;
    bytecodevec = &src[15];

#ifdef ALL_CHECKS
    dest = outbuffer;
#endif

    //--------------------------
    // initialize buffer globals
    //--------------------------
    __bitTable__ = alloca(260);
    char temp;
    short position = 1;

    // Create a table that tells us the highest bit that is set in a given byte.

    __bitTable__[0] =0;
    for (temp = 0; temp < 8; temp++) {
        short innerMax = 1<<temp;
        short t2;
        for (t2 = 0; t2<innerMax; t2++)
        __bitTable__[position++] = temp;
    }


    __ibuffer__ = src + sizeof(__PACKHEADER) + cth->rleentries;   // points at start of data
    __imask__   = 0x80;


#ifdef ALL_CHECKS
    pend_in  = __ibuffer__ + COMBINE_LOWHIGH(cth->csize_lo,cth->csize_hi);
    pend_out = dest        + COMBINE_LOWHIGH(cth->osize_lo,cth->osize_hi);
#endif

    while (1) {
        unsigned short sel;

#ifdef ALL_CHECKS
        if (outbuffer > pend_out)   return __ERRPCK_OUTBUFOVERRUN;
        if (__ibuffer__  > pend_in) return __ERRPCK_NOESCFOUND;
#endif

        sel = (escbits) ? __GetBits(escbits) : startesc;

        if (sel == startesc) {
            unsigned short lzpos, lzlen,i;
            unsigned short add = 0;

            lzlen = __GetValue();

            //-----------------------------------------------------------------
            // lzlen != 1 -> ZIP-decoding
            //-----------------------------------------------------------------
            if (lzlen != 1) {
                unsigned short lzposlo;
                unsigned short lzposhi = __GetValue() - 1;

                if (lzposhi == 254) {
                    if (lzlen > 3) {
                        add   = __Get8Bit();
                        lzpos = __Get8Bit() ^ 0xff;
                    }
                    else {
                        break;  // finish !!!
                    }
                }
                else {
                    if (extralzposbits) lzposhi = (lzposhi<<extralzposbits) | __GetBits(extralzposbits);

                    lzposlo = __Get8Bit() ^ 0xff;
                    lzpos   = COMBINE_LOWHIGH(lzposlo,lzposhi);
                }
            }
            //-----------------------------------------------------------------
            // lzlen == 1 -> RLE-decoding
            //-----------------------------------------------------------------
            else {
                if (NEXT_BIT_SET) {
                    unsigned short rlelen, bytecode, byte;

                    CORRECT_IN_MASK();

                    if (!NEXT_BIT_SET) {
                        unsigned short newesc;
                        CORRECT_IN_MASK();

                        newesc = __GetBits(escbits);

                        *outbuffer++ = (startesc<<escbits8) | __GetBits(escbits8);
                        startesc = newesc;
#ifdef ALL_CHECKS
                        if (outbuffer > pend_out) return __ERRPCK_OUTBUFOVERRUN;
#endif
                        continue;  // continue the main loop ...
                    }

                    CORRECT_IN_MASK();
                    rlelen = __GetValue();
                    if (rlelen >= 128) {
                        rlelen = ((rlelen-128)<<1) | __GetBits(1);
                        rlelen |= (((__GetValue())-1)<<8);
                    }
                    bytecode = __GetValue();
                    if (bytecode < 32) byte = bytecodevec[bytecode];
                    else               byte = ((bytecode-32)<<3) | __GetBits(3);

                    for (i=0; i<=rlelen; i++) *outbuffer++ = byte;
                    continue;   // continue the main loop ...
                }
                CORRECT_IN_MASK();
                lzpos = __Get8Bit() ^ 0xff;
            }


#ifdef ALL_CHECKS
            if (outbuffer + lzlen + 1 > pend_out) return __ERRPCK_OUTBUFOVERRUN;
#endif
            for (i=0; i<=lzlen; i++) {
                *outbuffer = *(outbuffer - lzpos - 1) + add;
                outbuffer++;
            }

        }

        //---------------------------------------------------------------------
        // sel != startesc
        //---------------------------------------------------------------------
        else {
            *outbuffer++ = (sel<<escbits8) | __GetBits(escbits8);
#ifdef ALL_CHECKS
            if (outbuffer > pend_out) return __ERRPCK_OUTBUFOVERRUN;
#endif
        }
    }

    return __ERRPCK_OKAY;
}

//=============================================================================
// unsigned short __GetBits(unsigned short bits)
// -- get number of bits from inputbuffer ... d1.w is used as input parameter
//
// unsigned short __Get8Bit(void)
// -- get 8 bits from inputbuffer
//
//
// unsigned short __GetValue(void)
// -- get next value from inputbuffer
//=============================================================================
asm(".data \n"
"    .even \n"
"__Get8Bit: \n"
"    move.b   (%a4)+,%d0 \n"
"    lsl.w    #8,%d0 \n"
"    move.b   (%a4),%d0 \n"
"    move.b   0(%a5,%d7.w),%d1 | get byte that says which bit is set. \n"
"    addq.b   #1,%d1 \n"
"    lsr.w    %d1,%d0 \n"
"    and.w    #0x00FF,%d0 \n"
"    rts \n"
"__GetValue: \n"
"    move.w   %d7,%d2 \n"
"    move.b   (%a4),%d0 \n"
"    and.b    %d0,%d2 \n"
"    beq.s    __Return1 \n"
"    add.b    %d2,%d2 \n"
"    subq.b   #1,%d2 \n"
"    move.b   0(%a5,%d7.w),%d1 \n"
"    and.b    %d2,%d0 \n"
"    eor.b    %d0,%d2 \n"
"    bne.s    __BitsSetInThisByte \n"
"    move.b   1(%a4),%d2 \n"
"    not.b    %d2 \n"
"    addq.b   #8,%d1 \n"
"__BitsSetInThisByte: \n"
"    sub.b    0(%a5,%d2.w),%d1\n"
"    move.b   %d1,%d2 \n"
"    addq.b   #1,%d2 \n"
"    cmp.b    #7,%d1 \n"
"    bcs.s    __LessThan7 \n"
"    moveq    #7,%d1 \n"
"    moveq    #7,%d2 \n"
"__LessThan7: \n"
"    move.w   %d7,%d0 \n"
"    ror.b    %d2,%d7 \n"
"    cmp.b    %d0,%d7 \n"
"    bcs.s    __SameByte \n"
"    addq.l   #1,%a4 \n"
"__SameByte: \n"
"    bsr.s    __GetBits \n"
"    addq.b   #1,%d1 \n"
"    bset.b   %d1,%d0 \n"
"    rts \n"
"__Return1: \n"
"    moveq    #1,%d0 \n"
"    ror.b    #1,%d7 \n"
"    bcc.s    __TheRTS \n"
"    addq.l	  #1,%a4 \n"
"__TheRTS: \n"
"    rts \n"
"__GetBits: \n"
"    moveq    #0,%d0 \n"
"    subq.b   #1,%d1 \n"
"    bcs.s    __inl_exit \n"
"    beq.s    __GetBitsSpecialCase \n"
"    move.w   %d7,%d0 \n"
"    ror.b    %d1,%d7 \n"
"    cmp.b    %d0,%d7 | maxgamma must never be bigger than 8. \n"
"    bcs.s    __StillInSameByte \n"
"    add.w    %d0,%d0 \n"
"    subq.w   #1,%d0 \n"
"    and.b    (%a4)+,%d0 \n"
"    lsl.w    #8,%d0 \n"
"    move.b   (%a4),%d0 \n"
"    move.b   0(%a5,%d7),%d2 \n"
"    lsr.w    %d2,%d0 \n"
"    ror.b    #1,%d7 \n"
"    bcc.s    __inl_exit \n"
"    addq.l   #1,%a4 \n"
"__inl_exit: \n"
"    rts \n"
"__StillInSameByte:\n"
"    add.w    %d0,%d0 \n"
"    subq.w   #1,%d0 \n"
"    and.b    (%a4),%d0 \n"
"    move.b   0(%a5,%d7),%d2 \n"
"    lsr.b    %d2,%d0 \n"
"    ror.b    #1,%d7 \n"
"    bcc.s    __inl_exit \n"
"    addq.l	  #1,%a4 \n"
"    rts \n"
"__GetBitsSpecialCase: \n"
"    move.b   (%a4),%d0 \n"
"    and.b    %d7,%d0 \n"
"    sne      %d0 \n"
"    and.b    #1,%d0 \n"
"    ror.b    #1,%d7 \n"
"    bcc.s    __inl_exit \n"
"    addq.l   #1,%a4 \n"
"    rts \n");

#if defined(STANDALONE)
asm(".data \n"
".ascii \"UNPACK_1\"");   // used to mark block for later extraction
#endif


#if defined(STANDALONE)
void _main(void) {
    UnpackBuffer(NULL,NULL);
}
#endif

//=============================================================================
// Revision History
//=============================================================================
//
// $Log: unpack.c,v $
//
// Revision 1.3  2003/
// Major update: now the routine is faster, smaller, it can run from Flash
// memory (it can be integrated into FlashApps)...
// Thanks to Greg Dietsche and Samuel Stearley for their work !
//
// Revision 1.2  2002/03/13 14:26:01  tnussb
// Minor improvements done and many comments added
//
// Revision 1.1  2002/03/13 11:13:32  tnussb
// Initial version
//
//
