/* Decrunches 'Eye of the Beholder' CPS files. */
/* (C) 2000 Kyzer/CSG */

/* fails on XANATH1.CPS & XDEATH3.CPS (they both 'overflow') */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define GETWORD(x) ((x)[0]|((x)[1]<<8))
#define GETLONG(x) ((x)[0]|((x)[1]<<8)|((x)[2]<<16)|((x)[3]<<24))
#define MAXSRCLEN  (64*1024) /* max length of crunched data */
#define MAXDESTLEN (1024*1024) /* max length of unpacked data */

#define EMPTYFILE    (0)
#define OVERFLOW     (-1)
#define UNKNOWNCOMP  (-2)


/* compression mode 0 = no compression */
int cps_copy(unsigned char *src, unsigned char *dest) {
  int len, x;

  /* get the length of the 'uncompressed' data */
  if ((len = GETLONG(src)) == 0) return EMPTYFILE;

  /* check for source overflow. we can't overflow the destination */
  if (len < 0 || len > (MAXSRCLEN - GETWORD(&src[4]) - 6)) return OVERFLOW;

  /* skip to the start of data and do a direct copy from source to dest */
  src += GETWORD(&src[4]) + 6;
  for(x = len; x--;) *dest++ = *src++;

  return len;
}


/* compression mode 3 = run-length encoding */
int cps_rle(unsigned char *src, unsigned char *dest) {
  int len, x;
  char rep;

  /* get the length of the 'uncompressed' data */
  if ((len = GETLONG(src)) == 0) return EMPTYFILE;

  /* check for destination overflow. */
  if (len < 0 || len > MAXDESTLEN) return OVERFLOW;

  /* skip to start of data */
  src += GETWORD(&src[4]) + 6;

  /* run while we have room left in output buffer */
  for (x = len; x > 0;) {
    int rlen = *src++;
    if (rlen < 128) {
      /* direct copy, no RLE */
      x -= rlen; if (x < 0) return OVERFLOW;
      while (rlen--) *dest++ = *src++;
    }
    else {
      /* repeated character RLE */
      if (rlen == 0) { rlen = GETWORD(src); src+=2; } else rlen = 256-rlen;
      x -= rlen; if (x < 0) return OVERFLOW;
      rep = *src++; while (rlen--) *dest++ = rep;
    }
  }
  return len - x;
}


/* compression mode 4 = LZ77 style compression */
int cps_lz77(unsigned char *src, unsigned char *dest) {
  unsigned char *s = src, *d = dest, *rep;
  unsigned int len, code;

  while ((s-src) < MAXSRCLEN) {
    code = *s++;
    if (code == 0xFE) {
      /* mini-RLE */
      len = GETWORD(s); s += 2;
      code = *s++;
      if ((d+len-dest) > MAXDESTLEN) return OVERFLOW;
      while (len--) *d++ = code;
    }
    else {
      if (code >= 0xC0) {
        if (code == 0xFF) {len=GETWORD(s); s+=2;} else {len=(code & 0x3F)+3;}
        rep = dest + GETWORD(s); s += 2;
      }
      else if (code >= 0x80) {
        if (code == 0x80) break;
        len = code & 0x3F; rep = s; s += len;
      }
      else /* code < 0x80 */ {
        len = (code >> 4) + 3; rep = d-(((code & 0x0F) << 8) | *s++);
      }

      if ((d+len-dest) > MAXDESTLEN) return OVERFLOW;
      while (len--) *d++ = *rep++;
    }
  }
  return d - dest;
}


void decrunch(char *file, unsigned char *src, unsigned char *dest) {
  FILE *fd;
  int len, slen;

  if ((fd = fopen(file, "rb"))) {
    len = fread(src, 1, MAXSRCLEN+2, fd);
    fclose(fd);

    slen = GETWORD(src); src += 2;
    if (slen != len && (slen+2) != len) {
      printf("%s: length warning: stored=%d, actual=%d\n", file, slen+2, len);
    }

    switch (*src) {
    case 0:  len = cps_copy(src+2, dest); break;
    case 3:  len = cps_rle (src+2, dest); break;
    case 4:  len = cps_lz77(src+8, dest); break;
    default:
      printf("%s: unknown compression type %d\n", file, *src); return;
    }

    switch (len) {
    case EMPTYFILE:
      printf("%s: zero-length file\n", file); return;
    case OVERFLOW:
      printf("%s: output overflow\n", file); return;
    default:
      /* save file - last char of filename is changed to '_' */
      file[strlen(file)-1] = '_';
      if ((fd = fopen(file, "wb"))) {
        slen = fwrite(dest, 1, len, fd);
        if (slen > 0) printf("%s: saved as %d bytes\n", file, slen);
        else printf("%s: write failed\n", file);
        fclose(fd);
      }
      else {
        printf("%s: save failed", file);
      }
    } /* end switch */
  }
  else {
    printf("%s: can't open file\n", file);
  }
}

int main(int argc, char *argv[]) {
  unsigned char *src, *dest;

  if (argc <= 1) {
    printf("Usage: %s <file.cps>\n", argv[0]);
    return 1;
  }

  src  = (unsigned char *) malloc(MAXSRCLEN  + 512);
  dest = (unsigned char *) malloc(MAXDESTLEN + 512);
  
  if (!src || !dest) {
    printf("not enough memory\n");
    return 1;
  }

  argv++; while (*argv) decrunch(*argv++, src, dest);
  return 0;
}

