/* Decrunches Sinclair QL 'CPT' files. */
/* (C) 2002 Kyzer/CSG */

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

#define MAXSRCLEN  (64*1024)  /* max length of crunched data */
#define MAXDESTLEN (128*1024) /* max length of unpacked data */

static unsigned char *inbuf, bitbuf;
static int bitpos;

int get_bit() {
  if (bitpos < 0) { bitpos = 7; bitbuf = *inbuf++; }
  return (bitbuf & (1<<bitpos--)) ? 1 : 0;
}

int get_byte() {
  int result=0, i=8;
  while (i--) result = (result>>1) | (get_bit() ? 0x80 : 0);
  return result;
}

struct node {
  unsigned char not_leaf[2], value[2];
};

static int nodenum;
static struct node tree[256];

void read_node(struct node *node, int branch) {
  if ((node->not_leaf[branch] = get_bit())) {
    node->value[branch] = ++nodenum;
    if (nodenum >= 256) return; /* safety check */
    node = &tree[nodenum];
    read_node(node, 0);
    read_node(node, 1);
  }
  else {
    node->value[branch] = get_byte();
  }
}

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

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

    if (src[5]) {
      outlen = (src[6]<<24) | (src[7]<<16) | (src[8]<<8) | (src[9]);
      inbuf = &src[6+8];
    }
    else {
      outlen = (src[0]<<24) | (src[1]<<16) | (src[2]<<8) | (src[3]);
      inbuf = &src[6];
    }

    if (outlen > MAXDESTLEN) {
      printf("%s: too large unpacked!\n", file);
      return;
    }

    bitpos = -1;
    if (get_bit()) {
      /* huffman compressed */
      unsigned char *out = dest;
      int todo = outlen;

      nodenum = 0;
      read_node(&tree[0], 0); /* left of root node */
      read_node(&tree[0], 1); /* right of root node */

      while (todo--) {
        struct node *n = &tree[0];
        unsigned char x, y=1;
        for (n = &tree[0]; y; n = &tree[x]) {
          int branch = get_bit();
          x = n->value[branch];
          y = n->not_leaf[branch];
        }
        *out++ = x;
      }
    }
    else {
      /* not compressed */
      int todo = outlen;
      unsigned char *out = dest;
      while (todo--) *out++ = get_byte();
    }

    /* save file */
    if ((fd = fopen(file, "wb"))) {
      int savelen = fwrite(dest, 1, outlen, fd);
      if (savelen > 0) printf("%s: saved as %d bytes\n", file, savelen);
      else printf("%s: write failed\n", file);
      fclose(fd);
    }
  }
  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.cpt>\n", argv[0]);
    return 1;
  }

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

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

