/* OpenAmiga.org
 *  This file is part of the OpenAmiga "xfdmaster library project"
 *
 * For more information look at:
 *   http://openamiga.org/?function=viewproject&projectid=67
 */

/* XFD external slave for MMCMP format by Kyzer/CSG <kyzer@4u.net>
 * Based on code by Olivier Lapicque <olivierl@jps.net>
 * MMCMP format (C) Emmanuel Giasson
 *
 * This slave code is in the Public Domain.
 */

#define __NOLIBBASE__
#define __NOGLOBALIFACE__

#include <proto/xfdmaster.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <string.h>

static const char USED version[]="\0$VER: MMCMP 1.0 (12.08.2001) by <kyzer@4u.net>";

/* header format (little-endian):
 * 0       "ziRC"
 * 4       "ONia"
 * 8       header size.w (always 14)
 * 10      header version.w
 * 12      number of blocks.w
 * 14      unpacked file length.l
 * 18      offset to block offset table.l (usually 24)
 * 22      unknown.b
 * 23      unknown.b
 * 24      SIZEOF
 *
 * block offset table (little endian):
 * 0       offset to block[1].l
 * 4       ...
 * ...     offset to block[number of blocks].l
 *
 *
 * block format (little-endian):
 * 0       unpacked block length.l
 * 4       packed block length.l
 * 8       XOR checksum.l
 * 12      number of sub-blocks.w
 * 14      flags.w
 * 16      tt_entries.w
 * 18      num_bits.w
 * 20      SIZEOF
 *
 * sub-block format (little-endian):
 * 0       sub-block output offset in dest file.l
 * 4       sub-block length.l
 */

#define mmcmp_COMP	0x0001
#define mmcmp_DELTA	0x0002
#define mmcmp_16BIT	0x0004
#define mmcmp_STEREO	0x0100
#define mmcmp_ABS16	0x0200
#define mmcmp_ENDIAN	0x0400

#define EndGetI32(a)  ((((a)[3])<<24)|(((a)[2])<<16)|(((a)[1])<<8)|((a)[0]))
#define EndGetI16(a)  ((((a)[1])<<8)|((a)[0]))
#define GETLONG(x,n) EndGetI32(&x[n])
#define GETWORD(x,n) EndGetI16(&x[n])

#define ERROR(x)  { xbi->xfdbi_Error = XFDERR_##x ; return FALSE; }

const ULONG mmcmp_16bitcmds[16] = {
  0x0001, 0x0003, 0x0007, 0x000F, 0x001E, 0x003C, 0x0078, 0x00F0,
  0x01F0, 0x03F0, 0x07F0, 0x0FF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0
};
const UBYTE mmcmp_16bitfetch[16] = {
  4, 4, 4, 4, 3, 2, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0
};
const ULONG mmcmp_8bitcmds[8] = {
  0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8
};
const UBYTE mmcmp_8bitfetch[8] = {
  3, 3, 3, 3, 2, 1, 0, 0
};


struct bitbuf {
  ULONG buf;
  UBYTE *src, *end;
  int bits;
};

ULONG mmcmp_getbits(struct bitbuf *bb, int bits) {
  ULONG out;
  if (!bits) return 0;
  while (bb->bits < 24) {
    bb->buf |= ((bb->src < bb->end) ? *bb->src++ : 0) << bb->bits;
    bb->bits += 8;
  }
  out = bb->buf & ((1<<bits)-1);
  bb->buf >>= bits;
  bb->bits -= bits;
  return out;
}


BOOL MMCMP_recog(struct xfdMasterIFace *Self, UBYTE *buf, ULONG length,
  struct xfdRecogResult *rr)
{
  if (strncmp((char *)buf, "ziRCONia", 8) == 0) {
    rr->xfdrr_MinTargetLen = rr->xfdrr_FinalTargetLen = GETLONG(buf, 14);
    return TRUE;
  }
  return FALSE;
}

BOOL MMCMP_decrunch(struct xfdMasterIFace *Self, struct xfdBufferInfo *xbi) {
  struct xfdMasterBase *libBase = (struct xfdMasterBase *)Self->Data.LibBase;
  struct ExecIFace *IExec = libBase->xfdm_IExec;

  UBYTE *src  = (UBYTE *) xbi->xfdbi_SourceBuffer;
  UBYTE *dest = (UBYTE *) xbi->xfdbi_UserTargetBuf;
  UBYTE *ends = &src[xbi->xfdbi_SourceBufLen];
  UBYTE *endd = &dest[xbi->xfdbi_TargetBufSaveLen];
  UBYTE *inpos, *block, *outp, *endp, *ptable = NULL;

  int flags, blk, subblk, num_bits = 0, fetch;
  int num_subblocks, num_blocks = GETWORD(src, 12);
  ULONG x, oldx = 0, y, new_bits;

  struct bitbuf bb;

  for (blk = 0; blk < num_blocks; blk++) {
    /* get block pointer from block offset table */
    block = &src[GETLONG(src, 24 + (blk*4))];
    if ((block + 20) > ends) ERROR(CORRUPTEDDATA);

    num_subblocks = GETWORD(block, 12);
    flags = GETWORD(block, 14);

    /* point inpos at the start of actual block data
     * (after the block and subblock headers)
     */
    inpos = &block[20 + (8 * num_subblocks)];
    if (inpos > ends) ERROR(CORRUPTEDDATA);

    if (flags & mmcmp_COMP) {
      num_bits = GETWORD(block, 18);
      if (num_bits >= ((flags & mmcmp_16BIT) ? 16 : 8)) ERROR(CORRUPTEDDATA);

      /* initialise oldx for delta values */
      oldx = 0;

      /* initialise bit buffer */
      bb.bits = bb.buf = 0;
      bb.src  = &inpos[GETWORD(block, 16)];
      bb.end  = &inpos[GETLONG(block, 4)];

      /* initialise 8-bit lookup table */
      ptable = inpos;
    }

    /* decrunch each sub-block */
    for (subblk = 0; subblk < num_subblocks; subblk++) {

      /* each sub-block defines an offset in the decrunched file where
       * it should decrunch to, and how long its decrunched length is
       */
      outp = &dest[GETLONG(block, 20 + (subblk*8))];
      endp = &outp[GETLONG(block, 24 + (subblk*8))];

      if (endp > endd) ERROR(CORRUPTEDDATA);

      if (flags & mmcmp_COMP) {
        if (flags & mmcmp_16BIT) {
          /* 16-bit compression */
          while (outp < endp) {
            x = 0x10000;
            y = mmcmp_getbits(&bb, num_bits+1);
            if (y >= mmcmp_16bitcmds[num_bits]) {
              fetch = mmcmp_16bitfetch[num_bits];
              new_bits = ((y - mmcmp_16bitcmds[num_bits]) << fetch)
                       | mmcmp_getbits(&bb, fetch);
              if (new_bits != num_bits) {
                num_bits = new_bits & 0x0F;
              }
              else {
                if ((y = mmcmp_getbits(&bb, 4)) == 0x0F) {
                  if (mmcmp_getbits(&bb, 1) != 0) break;
                  x = 0xFFFF;
                }
                else {
                  x = 0xFFF0 + y;
                }
              }
            }
            else {
              x = y;
            }

            if (x < 0x10000) {
              x = (x & 1) ? (0xFFFFFFFF - (x >> 1)) : (x >> 1);
              if (flags & mmcmp_DELTA) {
                x += oldx;
                oldx = x;
              }
              else if (!(flags & mmcmp_ABS16)) {
                x ^= 0x8000;
              }
              /* output 16-bit word, little-endian format */
              *outp++ = (x)      & 0xFF;
              *outp++ = (x >> 8) & 0xFF;
            }
          }
        }
        else {
          /* 8-bit compression */
          while (outp < endp) {
            x = 0x100;
            y = mmcmp_getbits(&bb, num_bits+1);
            if (y >= mmcmp_8bitcmds[num_bits]) {
              fetch = mmcmp_8bitfetch[num_bits];
              new_bits = ((y - mmcmp_8bitcmds[num_bits]) << fetch)
                       | mmcmp_getbits(&bb, fetch);
              if (new_bits != num_bits) {
                num_bits = new_bits & 0x07;
              }
              else {
                if ((y = mmcmp_getbits(&bb, 3)) == 0x07) {
                  if (mmcmp_getbits(&bb, 1) == 0) x = 0xFF; else break;
                }
                else {
                  x = 0xF8 + y;
                }
              }
            }
            else {
              x = y;
            }

            if (x < 0x100) {
              x = ptable[x];
              if (flags & mmcmp_DELTA) {
                x += oldx;
                oldx = x;
              }
              /* output 8-bit byte */
              *outp++ = x & 0xFF;
            }

          }
        }
      }
      else {
        /* block is not compressed */
        if ((inpos + (endp-outp)) > ends) ERROR(CORRUPTEDDATA);
        IExec->CopyMem(inpos, outp, (endp-outp));
        inpos += (endp-outp);
      }
    }
  }

  /* good exit */
  return TRUE;
}

struct xfdSlave FirstSlave = {
  NULL, XFDS_VERSION, 39, "(MMCMP) Data Cruncher",
  XFDPFF_DATA|XFDPFF_RECOGLEN|XFDPFF_USERTARGET,
  0, (BOOL (*)()) MMCMP_recog, (BOOL (*)()) MMCMP_decrunch,
  NULL, NULL, 0, 0, 44
};

struct xfdForeMan xfdForeMan = {
  0x70FF4E75,
  XFDF_ID,
  XFDF_VERSION,
  0,
  NULL,
  ZERO,
  &FirstSlave
};

int _start(void) {
  return RETURN_FAIL;
}

