/* atimgext.c */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>

#define SWAP32(x) ((((x)>>24)&0xFF) | (((x)<<8)&0xFF0000) | (((x)>>8)&0xFF00) | (((x)<<24)&0xFF000000))

#pragma pack(push, 1)
typedef struct {
  uint32_t mark;
  uint32_t size;
} frmchunk;

typedef struct {
  uint8_t  IDLength;
  uint8_t  ColorMapType;
  uint8_t  ImageType;
  uint16_t CMapStart;
  uint16_t CMapLength;
  uint8_t  CMapDepth;
  uint16_t XOffset;
  uint16_t YOffset;
  uint16_t Width;
  uint16_t Height;
  uint8_t  PixelDepth;
  uint8_t  ImageDescriptor;
} tga_head;
#pragma pack(pop)

void PicDecoder(uint8_t *p, uint8_t *u) {
int32_t i, offs, size;
  i = 0;
  while (1) {
    while (1) {
      i >>= 1;
      if (!(i & 0xFF00)) {
        i = 0xFF00 | *p;
        p++;
      }
      if (i & 1) { break; }
      *u = *p;
      u++;
      p++;
    }
    if (*p >= 96) {
      offs = *p - 256;
      size = 3;
      p++;
    } else {
      size = (*p & 0xF0) >> 4;
      offs = (*p & 0x0F) << 8;
      p++;
      offs |= *p;
      p++;
      if (!offs) { break; }
      offs = -offs;
      if (size == 5) {
        size = *p + 9;
        p++;
      } else {
        size = size + 4;
      }
    }
    while (--size) {
      *u = u[offs];
      u++;
    }
  }
}

int main(int argc, char *argv[]) {
FILE *fl, *f;
frmchunk data;
tga_head th;
char s[5], name[16];
uint16_t pal[256];
uint32_t i, n, l, sz;
uint8_t *p, *u;
  printf(
    "Alien Trilogy .B16 to .TGA image converter v1.0\n("
    "c) CTPAX-X Team 2020\nhttp://www.CTPAX-X.org/\n\n"
  );
  if (argc < 4) {
    printf(
      "Usage: atimgext <filename.b16> <frame0 width> <frame0 height> [...]\n\n"
      "Example:\natimgext MM9.B16 40 68 40 88 76 84\n\n"
      ".BND images supported, but without proper palette since it is stored elsewhere.\n"
      "If you don't know exactly frames width or height make them big enough so the\n"
      "bottom rows on the image will be filled with 255 (0xFF) bytes. From there you\n"
      "can change width and reduce height to find actual frame dimensions.\n\n"
    );
    return(1);
  }
  /* init tga header */
  memset(&th, 0, sizeof(th));
  th.ImageDescriptor = 0x20;
  th.ColorMapType = 1;
  th.ImageType = 1;
  th.CMapLength = 256;
  th.CMapDepth = 16; /* 24 */
  th.PixelDepth = 8;
  fl = fopen(argv[1], "rb");
  if (!fl) {
    printf("Error: can't open input file.\n\n");
    return(2);
  }
  /* read header */
  memset(&data, 0, sizeof(data));
  fread(&data, sizeof(data), 1, fl);
  sz = SWAP32(data.size);
  /* "FORM" */
  if ((data.mark != 0x4D524F46) || (!sz)) {
    fclose(fl);
    printf("Error: invalid input file format.\n\n");
    return(3);
  }
  s[4] = 0;
  fread(s, 4, 1, fl);
  n = atoi(s);
  printf("Frames: %u\n\n", n);
  if (argc < (2 + (n * 2))) {
    fclose(fl);
    printf(
      "Error: %d arguments are given, but %d required:\n"
      "(1 input filename + %d pairs of width and height for each frame).\n\n",
      argc - 1, 1 + (n * 2), n
    );
    return(4);
  }
  /* create some default palette in case of .BND (Bitmap iNdexeD color images) */
  for (l = 0; l < 256; l++) {
    pal[l] = 0x8000 | ((l & 0xF0) << 6) | ((l & 0x0F) << 5) | 0x001F;
  }
  /* transparent to purple */
  pal[0] = 0xFC1F;
  sz += 8;
  i = 0;
  while (ftell(fl) < sz) {
    memset(&data, 0, sizeof(data));
    fread(&data, sizeof(data), 1, fl);
    data.size = SWAP32(data.size);
    memcpy(s, &data.mark, 4);
    if (!data.mark) { break; }
    printf("%08lX: %4s %08X\n", ftell(fl) - sizeof(data), s, data.size);
    /* Frame */
    if ((data.mark & 0xFF) == 'F') {
      th.Width = atoi(argv[2 + (i * 2)]);
      th.Height = atoi(argv[2 + (i * 2) + 1]);
      l = th.Width * th.Height;
      p = (uint8_t *) malloc(data.size);
      u = (uint8_t *) malloc(l);
      if (p && u) {
        memset(u, 0xFF, l);
        fread(p, data.size, 1, fl);
        PicDecoder(p, u);
        sprintf(name, "FRAME%02u.TGA", i);
        f = fopen(name, "wb");
        if (f) {
          fwrite(&th, sizeof(th), 1, f);
          fwrite(&pal, sizeof(pal), 1, f);
          fwrite(u, l, 1, f);
          fclose(f);
        }
      }
      if (u) { free(u); }
      if (p) { free(p); }
      i++;
      continue;
    }
    /* Colormap */
    if ((data.mark & 0xFF) == 'C') {
      /* in case of less than 256 colors in Colormap */
      l = sizeof(pal);
      memset(pal, 0, l);
      l = (l < data.size) ? l : data.size;
      fread(pal, l, 1, fl);
      if (l < data.size) { fseek(fl, data.size - l, SEEK_CUR); }
      /* fix palette */
      for (l = 0; l < 256; l++) {
        /*        T     R     G     B
          F39C -> 1 11100 11100 11100
        */
        pal[l] =
          (pal[l] & 0x8000) | ((pal[l] & 0x7C00) >> 10) |
          (pal[l] & 0x03E0) | ((pal[l] & 0x001F) << 10);
      }
      /* transparent to purple */
      pal[0] = 0xFC1F;
      continue;
    }
    /* unknown block - skip */
    fseek(fl, data.size, SEEK_CUR);
  }
  fclose(fl);
  /* replace palette in already extracted frames */
  for (i = 0; i < n; i++) {
    sprintf(name, "FRAME%02u.TGA", i);
    f = fopen(name, "r+b");
    if (f) {
      fseek(fl, sizeof(th), SEEK_SET);
      fwrite(&pal, sizeof(pal), 1, f);
      fclose(f);
    }
  }
  printf("\ndone\n\n");
  return(0);
}