/*
 * gb2pdb  -  Convert a gameboy game image into a palm database
 *            for use with Phoinix
 *
 *  Taken from Version 1.4 of the PalmBoy converter
 *  Now CVS-History:
 *  $Log $
 */

/* Layout of database:
 *
 * record-#   size         content
 * -----------------------------------------------------
 * 0          32768        ROM banks 0 and 1 (same for banked and unbanked)
 * 1..2^n     16384        ROM banks 2 to last (if applicable)
 */

#define DB_CREATOR  "Phnx"
#define DB_TYPE     "ROMU"

#define ROM_BANK       16384U
#define MAX_ROM_BANKS  256

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

#ifdef __mac_os
#pragma options align=packed
#include <console.h>
#endif

typedef unsigned char ubyte;
typedef signed char sbyte;
typedef unsigned short uword;
typedef short word;
typedef unsigned long udword;
typedef long dword;

typedef struct {
  ubyte name[32];
  uword fileAttributes;
  uword version;
  udword creationDate;
  udword modificationDate;
  udword lastBackupDate;
  udword modificationNumber;
  udword appInfoArea;
  udword sortInfoArea;
  ubyte databaseType[4];        
  ubyte creatorID[4];
  udword uniqueIDSeed;
  udword nextRecordListID;
  uword numberOfRecords;
}
#ifdef __GNUC__
  __attribute__ ((packed))
#endif
  PdbHeader;

typedef struct {
  udword recordDataOffset;
  ubyte recordAttributes;
  ubyte uniqueID[3];
}
#ifdef __GNUC__
  __attribute__ ((packed))
#endif
  RecHeader;

void 
WriteWord(void *addr, uword data) {
  ubyte *to = (ubyte*)addr;

  to[0] = (data>>8)&0xff; 
  to[1] = (data>>0)&0xff;
}

void 
WriteDWord(void *addr, udword data) {
  ubyte *to = (ubyte*)addr;

  to[0] = (data>>24)&0xff; 
  to[1] = (data>>16)&0xff;
  to[2] = (data>>8)&0xff;
  to[3] = (data>>0)&0xff;
}

typedef  struct {
  int   id;
  int   mcb;
  char  *name;
} CARTR_TYPE;

static  CARTR_TYPE  known_types[]={
  { 0x00, 0,"ROM ONLY" },
  { 0x01, 1,"MBC1" },
  { 0x02, 1,"MBC1+RAM" },
  { 0x03, 1,"MBC1+RAM+BAT" },
  { 0x05, 2,"MBC2" },
  { 0x06, 2,"MBC2+BAT" },
  { 0x08, 0,"RAM" },
  { 0x09, 0,"RAM+BAT" },
  { 0x0B,-1,"MMM01" },
  { 0x0C,-1,"MMM01+RAM" },
  { 0x0D,-1,"MMM01+RAM+BAT" },
  { 0x0F, 3,"MBC3+BAT+RTC" },
  { 0x10, 3,"MBC3+RAM+BAT+RTC" },
  { 0x11, 3,"MBC3" },
  { 0x12, 3,"MBC3+RAM" },
  { 0x13, 3,"MBC3+RAM+BAT" },
  { 0x15,-1,"MBC4" },
  { 0x16,-1,"MBC4+RAM" },
  { 0x17,-1,"MBC4+RAM+BAT" },
  { 0x19, 5,"MBC5" },
  { 0x1A, 5,"MBC5+RAM" },
  { 0x1B, 5,"MBC5+RAM+BAT" },
  { 0x1C, 5,"MBC5+MOT" },
  { 0x1D, 5,"MBC5+RAM+MOT" },
  { 0x1E, 5,"MBC5+RAM+BAT+MOT" },
  { 0xFC,-1,"Nintendo Pocket Camera" },
  { 0xFD,-1,"Bandai TAMA5" },
  { 0xFE,-1,"Hudson HuC-3" },
  { 0xFF,-1,"Hudson HuC-1" },
  { -1,  -1,"Unknown" }
};

typedef  struct {
  unsigned char  unused1[0x134];
  char           name[17];
  unsigned char  unused2[2];
  unsigned char  ctype;
  unsigned char  romsz;
  unsigned char  ramsz;
}  CARTR_HEADER;

char gb_buffer[ROM_BANK];

int main(int argc, char **argv) {
  FILE *outfile, *infile;
  CARTR_HEADER gb_header;
  int type, i;
  char *p,*outname;
  PdbHeader pdb_header;
  RecHeader rec_header;
  unsigned long offset;
  int rom_banks, nrecs;
  size_t j;
  unsigned short filler;

#ifdef __mac_os
  argc = ccommand(&argv);
#endif

  printf("gb2pdb for Phoinix V1.0 (c) 1999-2001 by Bodo Wenzel\n");
  printf("Minor modifications for MacOS Classic compatibility by Julian Hsiao\n");

  if ((argc != 2) && (argc != 3) && (argc != 4)) {
    puts("usage: gb2pdb infile [outfile [dbname]]");
    return 1;
  }

  infile = fopen(argv[1], "rb");
  if(infile == NULL) {
    printf("error: can't open input file '%s'\n", argv[1]);
    return 1;
  }

  /* scan some image values */
  if (fread(&gb_header, sizeof(CARTR_HEADER), 1, infile) != 1) {
    printf("error: can't read input file '%s'\n", argv[1]);
    fclose(infile);
    return 1;
  }

  for (type = 0; known_types[type].id >= 0 &&
                 known_types[type].id != gb_header.ctype; type++);

  if (known_types[type].mcb < 0) {
    printf("error: unsupported cartridge type %d '%s'\n",
           gb_header.ctype, known_types[type].name);
    fclose(infile);
    return 1;
  }

  rom_banks = 2 << (gb_header.romsz & 0x0f);
  if ((gb_header.romsz & 0xf0) != 0)
    rom_banks += 2 << (gb_header.romsz >> 4);

  if(rom_banks > MAX_ROM_BANKS) {
    puts("error: too much ROM in this cartridge!");
    fclose(infile);
    return 1;
  }

  /* clean up cartridge name */
  if (argc!=4) {
    if (gb_header.name[15] == '\xc0') {
      puts("warning: color-only game");
      for (i = 0; i < 11; i++) {
        if (gb_header.name[i] == '\0')
          break;
        if (!isprint(gb_header.name[i]))
          gb_header.name[i] = ' ';
      }
    } else if (gb_header.name[15] == '\x80') {
      /* game with color and gray support */
      for (i = 0; i < 15; i++) {
        if (gb_header.name[i] == '\0')
          break;
        if (!isprint(gb_header.name[i]))
          gb_header.name[i] = ' ';
      }
    } else {
      /* old game, gray-only */
      for (i = 0; i < 16; i++) {
        if (gb_header.name[i] == '\0')
          break;
        if (!isprint(gb_header.name[i]))
          gb_header.name[i] = ' ';
      }
    }
  } else {
    if (gb_header.name[15] == '\xc0')
      printf("warning: color-only game\n");
    for (i = 0; i < 16; i++) {
      gb_header.name[i] = argv[3][i];
      if (gb_header.name[i] == '\0')
        break;
    }
  }
  gb_header.name[i] = '\0';

  printf("Name:     %s (%s)\n", gb_header.name, known_types[type].name);
  printf("ROM size: %dkB (%d banks)\n", (int)(rom_banks*(ROM_BANK/1024)), rom_banks);

  nrecs = rom_banks - 1;

  /* create destination file name */
  if (argc == 2) {
    outname = (char*)malloc(strlen(argv[1]) + 4 + 1);
    if (outname == NULL) {
      puts("error: failed allocating memory");
      fclose(infile);
      return 1;
    }
    strcpy(outname, argv[1]);
    if ((p = strrchr(outname, '.')) != NULL)
      strcpy(p, ".pdb");
    else
      strcat(outname, ".pdb");
  } else
    outname = argv[2];

  /* open destination file */
  outfile = fopen(outname, "wb");
  if (outfile == NULL) {
    printf("error: can't open output file '%s'\n", outname);
    fclose(infile);
    return 1;
  }
  printf("writing '%s'\n", outname);

  /* create/write pdb header */
  memset(&pdb_header,0,sizeof(PdbHeader));
  strcpy((char*)pdb_header.name, gb_header.name);
  WriteWord(&(pdb_header.version),0x0001);
  WriteDWord(&(pdb_header.creationDate), 0xb3d1b04dL);
  WriteDWord(&(pdb_header.modificationDate), 0xb3d1b04dL);
  memcpy(pdb_header.databaseType, DB_TYPE, 4);
  memcpy(pdb_header.creatorID, DB_CREATOR, 4);
  WriteWord(&(pdb_header.numberOfRecords), nrecs);
  fwrite(&pdb_header,sizeof(PdbHeader),1,outfile);

  /* offset to data inside db */
  offset = sizeof(PdbHeader) + nrecs * sizeof(RecHeader) + 2;

  /* write record headers */
  WriteDWord(&(rec_header.recordDataOffset), offset);
  rec_header.recordAttributes = 0x60;
  rec_header.uniqueID[0] = rec_header.uniqueID[1] = rec_header.uniqueID[2] = 0x00;
  fwrite(&rec_header,sizeof(RecHeader),1,outfile);
  offset += 2*ROM_BANK;

  for(i=2;i<rom_banks;i++) {
    WriteDWord(&(rec_header.recordDataOffset), offset);
    rec_header.recordAttributes = 0x60;
    rec_header.uniqueID[0] = rec_header.uniqueID[1] = rec_header.uniqueID[2] = 0x00;
    fwrite(&rec_header,sizeof(RecHeader),1,outfile);
    offset += ROM_BANK;
  }

  filler = 0;
  fwrite(&filler,sizeof(filler),1,outfile);

  fseek(infile, 0l, SEEK_SET);

  /* now copy rom content */
  for(i=0;i<rom_banks;i++) {
    if((j=fread(gb_buffer, 1, ROM_BANK, infile))!=ROM_BANK) {
      printf("error: can't read input file '%s' at (%d,%ld)\n", argv[1], i, (long)j);
      fclose(outfile);
      fclose(infile);
      return 1;
    }

    fwrite(gb_buffer, ROM_BANK, 1, outfile);
  }

  if (ferror(outfile)) {
    printf("error: can't write output file '%s'\n", outname);
    fclose(outfile);
    fclose(infile);
    return 1;
  }

  fclose(outfile);
  fclose(infile);

  return 0;
}
