/*
 * parse-ipmve.c: A simple parser for Interplay MVE multimedia files
 * by Mike Melanson (mike at multimedia.cx)
 *
 * Use this program however you see fit.
 *
 * The most common use for this program is to compile it as:
 *   cc parse-ipmve.c -o parse-ipmve
 * and then run it against an Interplay MVE files:
 *   parse-ipmve <file.mve>
 *
 * For more information on the Interplay MVE file format, visit:
 *   http://www.multimedia.cx/codecs/
 */

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

#define SIGNATURE "Interplay MVE File\x1A\x00"
#define SIGNATURE_SIZE 20
#define CHUNK_PREAMBLE_SIZE 4
#define OPCODE_PREAMBLE_SIZE 4

#define CHUNK_INIT_AUDIO  0x0000
#define CHUNK_AUDIO_ONLY  0x0001
#define CHUNK_INIT_VIDEO  0x0002
#define CHUNK_VIDEO       0x0003
#define CHUNK_SHUTDOWN    0x0004
#define CHUNK_END         0x0005

#define OPCODE_END_OF_STREAM           0x00
#define OPCODE_END_OF_CHUNK            0x01
#define OPCODE_CREATE_TIMER            0x02
#define OPCODE_INIT_AUDIO_BUFFERS      0x03
#define OPCODE_START_STOP_AUDIO        0x04
#define OPCODE_INIT_VIDEO_BUFFERS      0x05
#define OPCODE_UNKNOWN_06              0x06
#define OPCODE_SEND_BUFFER             0x07
#define OPCODE_AUDIO_FRAME             0x08
#define OPCODE_SILENCE_FRAME           0x09
#define OPCODE_INIT_VIDEO_MODE         0x0A
#define OPCODE_CREATE_GRADIENT         0x0B
#define OPCODE_SET_PALETTE             0x0C
#define OPCODE_SET_PALETTE_COMPRESSED  0x0D
#define OPCODE_UNKNOWN_0E              0x0E
#define OPCODE_SET_DECODING_MAP        0x0F
#define OPCODE_UNKNOWN_10              0x10
#define OPCODE_VIDEO_DATA              0x11
#define OPCODE_UNKNOWN_12              0x12
#define OPCODE_UNKNOWN_13              0x13
#define OPCODE_UNKNOWN_14              0x14
#define OPCODE_UNKNOWN_15              0x15

/* macro to fetch 16-bit little-endian words from a bytestream */
#define LE_16(x)  ((*x) | ((*(x+1)) << 8))

int main(int argc, char *argv[]) {

  FILE *f;
  unsigned char signature[SIGNATURE_SIZE];
  unsigned char chunk_preamble[CHUNK_PREAMBLE_SIZE];
  int chunk_type;
  int chunk_size;
  unsigned char opcode_preamble[OPCODE_PREAMBLE_SIZE];
  unsigned char opcode_type;
  unsigned char opcode_version;
  int opcode_size;

  printf ("Interplay MVE file analysis tool\n");
  printf ("  by Mike Melanson (mike at multimedia.cx)\n\n");

  if (argc < 2) {
    printf ("usage: parse-ipmve <file.mve>\n");
    return 0;
  }

  f = fopen(argv[1], "r");
  if (!f) {
    perror(argv[1]);
    return 0;
  }

  /* check the header */
  if (fread(signature, SIGNATURE_SIZE, 1, f) != 1) {
    perror(argv[1]);
    fclose(f);
    return 0;
  }
  if (memcmp(signature, SIGNATURE, SIGNATURE_SIZE) != 0) {
    printf ("invalid signature, %s is not an Interplay MVE file\n", argv[1]);
    fclose(f);
    return 0;
  }

  printf ("Signature is valid, %s is an Interplay MVE file\n", argv[1]);

  /* skip the next 6 bytes */
  fseek(f, 6, SEEK_CUR);

  /* iterate through the chunks in the file */
  while(chunk_type != CHUNK_END) {
    if (fread(chunk_preamble, CHUNK_PREAMBLE_SIZE, 1, f) != 1) {
      printf ("could not read from file (EOF?)\n");
      fclose(f);
      return 0;
    }

    chunk_size = LE_16(&chunk_preamble[0]);
    chunk_type = LE_16(&chunk_preamble[2]);

    printf ("\nchunk type %04X, 0x%04X bytes: ", chunk_type, chunk_size);
    switch (chunk_type) {

      case CHUNK_INIT_AUDIO:
        printf ("initialize audio\n");
        break;

      case CHUNK_AUDIO_ONLY:
        printf ("audio only\n");
        break;

      case CHUNK_INIT_VIDEO:
        printf ("initialize video\n");
        break;

      case CHUNK_VIDEO:
        printf ("video (and audio)\n");
        break;

      case CHUNK_SHUTDOWN:
        printf ("shutdown\n");
        break;

      case CHUNK_END:
        printf ("end\n");
        break;

      default:
        printf (" *** unknown chunk type\n");
        break;
    }

    printf ("------------------------------------------------------\n");

    /* iterate through individual opcodes */
    while (chunk_size > 0) {
      if (fread(opcode_preamble, OPCODE_PREAMBLE_SIZE, 1, f) != 1) {
        printf ("could not read from file (EOF?)\n");
        fclose(f);
        return 0;
      }
      chunk_size -= 4;

      opcode_size = LE_16(&opcode_preamble[0]);
      opcode_type = opcode_preamble[2];
      opcode_version = opcode_preamble[3];

      chunk_size -= opcode_size;
      printf ("  opcode type %02X, version %d, 0x%04X bytes: ",
        opcode_type, 
        opcode_version, 
        opcode_size);
      switch (opcode_type) {

        case OPCODE_END_OF_STREAM:
          printf ("end of stream\n");
          break;

        case OPCODE_END_OF_CHUNK:
          printf ("end of chunk\n");
          break;

        case OPCODE_CREATE_TIMER:
          printf ("create timer\n");
          break;

        case OPCODE_INIT_AUDIO_BUFFERS:
          printf ("initialize audio buffers\n");
          break;

        case OPCODE_START_STOP_AUDIO:
          printf ("start/stop audio\n");
          break;

        case OPCODE_INIT_VIDEO_BUFFERS:
          printf ("initialize video buffers\n");
          break;

        case OPCODE_UNKNOWN_06:
        case OPCODE_UNKNOWN_0E:
        case OPCODE_UNKNOWN_10:
        case OPCODE_UNKNOWN_12:
        case OPCODE_UNKNOWN_13:
        case OPCODE_UNKNOWN_14:
        case OPCODE_UNKNOWN_15:
          printf ("unknown (but documented) opcode %02X\n", opcode_type);
          break;

        case OPCODE_SEND_BUFFER:
          printf ("send buffer\n");
          break;

        case OPCODE_AUDIO_FRAME:
          printf ("audio frame\n");
          break;

        case OPCODE_SILENCE_FRAME:
          printf ("silence frame\n");
          break;

        case OPCODE_INIT_VIDEO_MODE:
          printf ("initialize video mode\n");
          break;

        case OPCODE_CREATE_GRADIENT:
          printf ("create gradient\n");
          break;

        case OPCODE_SET_PALETTE:
          printf ("set palette\n");
          break;

        case OPCODE_SET_PALETTE_COMPRESSED:
          printf ("set palette compressed\n");
          break;

        case OPCODE_SET_DECODING_MAP:
          printf ("set decoding map\n");
          break;

        case OPCODE_VIDEO_DATA:
          printf ("set video data\n");
          break;

        default:
          printf (" *** unknown opcode type\n");
          break;

      }

      /* skip over the meat of the opcode */
      fseek(f, opcode_size, SEEK_CUR);
    }
  }

  fclose(f);

  return 0;
}
