cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

makebin.c (28342B)


      1/*
      2  makebin - turn a .ihx file into a binary image or GameBoy format binaryimage
      3
      4  Copyright (c) 2000 Michael Hope
      5  Copyright (c) 2010 Borut Razem
      6  Copyright (c) 2012 Noel Lemouel
      7  Copyright (c) 2020-2021 Sebastian 'basxto' Riedel
      8  Copyright (c) 2020 'bbbbbr'
      9
     10  This software is provided 'as-is', without any express or implied
     11  warranty.  In no event will the authors be held liable for any damages
     12  arising from the use of this software.
     13
     14  Permission is granted to anyone to use this software for any purpose,
     15  including commercial applications, and to alter it and redistribute it
     16  freely, subject to the following restrictions:
     17
     18  1. The origin of this software must not be misrepresented; you must not
     19     claim that you wrote the original software. If you use this software
     20     in a product, an acknowledgment in the product documentation would be
     21     appreciated but is not required.
     22  2. Altered source versions must be plainly marked as such, and must not be
     23     misrepresented as being the original software.
     24  3. This notice may not be removed or altered from any source distribution.
     25*/
     26
     27
     28#include <stdio.h>
     29#include <stdlib.h>
     30#include <stdbool.h>
     31#include <string.h>
     32#include <ctype.h>
     33
     34#if defined(_WIN32)
     35#include <fcntl.h>
     36#include <io.h>
     37#else
     38#include <unistd.h>
     39#endif
     40
     41
     42typedef unsigned char BYTE;
     43
     44#define FILL_BYTE 0xff
     45
     46int
     47getnibble (FILE *fin)
     48{
     49  int ret;
     50  int c = getc (fin);
     51
     52  if (feof (fin) || ferror (fin))
     53    {
     54      fprintf (stderr, "error: unexpected end of file.\n");
     55      exit (6);
     56    }
     57
     58  ret = c - '0';
     59  if (ret > 9)
     60    {
     61      ret -= 'A' - '9' - 1;
     62    }
     63
     64  if (ret > 0xf)
     65    {
     66       ret -= 'a' - 'A';
     67    }
     68
     69  if (ret < 0 || ret > 0xf)
     70    {
     71      fprintf (stderr, "error: character %02x.\n", ret);
     72      exit (7);
     73    }
     74  return ret;
     75}
     76
     77int
     78getbyte (FILE *fin, int *sum)
     79{
     80  int b = (getnibble (fin) << 4) | getnibble (fin);
     81  *sum += b;
     82  return b;
     83}
     84
     85void
     86usage (void)
     87{
     88  fprintf (stderr,
     89           "makebin: convert a Intel IHX file to binary or GameBoy format binary.\n"
     90           "Usage: makebin [options] [<in_file> [<out_file>]]\n"
     91           "Options:\n"
     92           "  -p             pack mode: the binary file size will be truncated to the last occupied byte\n"
     93           "  -s romsize     size of the binary file (default: rom banks * 16384)\n"
     94           "  -Z             generate GameBoy format binary file\n"
     95           "  -S             generate Sega Master System format binary file\n"
     96           "  -t size        skip size bytes from the beginning of the rom"
     97
     98           "SMS format options (applicable only with -S option):\n"
     99           "  -xo n          rom size (0xa-0x2)\n"
    100           "  -xj n          set region code (3-7)\n"
    101           //"  -xc n          product code (0-159999)\n"
    102           "  -xv n          version number (0-15)\n"
    103           //"  -xV n          SDSC version number\n"
    104           //"  -xd n          SDSC date\n"
    105           //"  -xA n          SDSC author pointer\n"
    106           //"  -xn n          SDSC program name pointer\n"
    107           //"  -xD n          SDSC description pointer\n"
    108
    109           "GameBoy format options (applicable only with -Z option):\n"
    110           "  -yo n          number of rom banks (default: 2) (autosize: A)\n"
    111           "  -ya n          number of ram banks (default: 0)\n"
    112           "  -yt n          MBC type (default: no MBC)\n"
    113           "  -yl n          old licensee code (default: 0x33)\n"
    114           "  -yk cc         new licensee string (default: 00)\n"
    115           "  -yn name       cartridge name (default: none)\n"
    116           "  -yc            GameBoy Color compatible\n"
    117           "  -yC            GameBoy Color only\n"
    118           "  -ys            Super GameBoy\n"
    119           "  -yS            Convert .noi file named like input file to .sym\n"
    120           "  -yj            set non-Japanese region flag\n"
    121           "  -yN            do not copy big N validation logo into ROM header\n"
    122           "  -yp addr=value Set address in ROM to given value (address 0x100-0x1FE)\n"
    123           "Arguments:\n"
    124           "  <in_file>      optional IHX input file, '-' means stdin. (default: stdin)\n"
    125           "  <out_file>     optional output file, '-' means stdout. (default: stdout)\n");
    126}
    127
    128#define CART_NAME_LEN 16
    129
    130struct gb_opt_s
    131{
    132  char cart_name[CART_NAME_LEN];  /* cartridge name buffer */
    133  char licensee_str[2];           /* new licensee string */
    134  BYTE mbc_type;                  /* MBC type (default: no MBC) */
    135  short nb_rom_banks;             /* Number of rom banks (default: 2) */
    136  BYTE nb_ram_banks;              /* Number of ram banks (default: 0) */
    137  BYTE licensee_id;               /* old licensee code */
    138  BYTE is_gbc;                    /* 1 if GBC compatible, 2 if GBC only, false for all other*/
    139  BYTE is_sgb;                    /* True if SGB, false for all other*/
    140  BYTE sym_conversion;            /* True if .noi file should be converted to .sym (default false)*/
    141  BYTE non_jp;                    /* True if non-Japanese region, false for all other*/
    142  BYTE rom_banks_autosize;        /* True if rom banks should be auto-sized (default false)*/
    143  bool do_logo_copy;              /* True if the nintendo logo should be copied into the ROM (default true) */
    144  BYTE address_overwrite[16];     /* For limited compatibility with very old versions */
    145};
    146
    147struct sms_opt_s
    148{
    149  BYTE rom_size;                  /* Doesn't have to be the real size, needed for checksum */
    150  BYTE region_code;               /* Region code Japan/Export/International and SMS/GG */
    151  BYTE version;                   /* Game version */
    152};
    153
    154void
    155gb_postproc (BYTE * rom, int size, int *real_size, struct gb_opt_s *o)
    156{
    157  int i, chk;
    158  static const BYTE gb_logo[] =
    159    {
    160      0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b,
    161      0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d,
    162      0x00, 0x08, 0x11, 0x1f, 0x88, 0x89, 0x00, 0x0e,
    163      0xdc, 0xcc, 0x6e, 0xe6, 0xdd, 0xdd, 0xd9, 0x99,
    164      0xbb, 0xbb, 0x67, 0x63, 0x6e, 0x0e, 0xec, 0xcc,
    165      0xdd, 0xdc, 0x99, 0x9f, 0xbb, 0xb9, 0x33, 0x3e
    166    };
    167
    168  /* $0104-$0133: Nintendo logo
    169   * If missing, an actual Game Boy won't run the ROM.
    170   */
    171
    172  if (o->do_logo_copy)
    173    {
    174      memcpy (&rom[0x104], gb_logo, sizeof (gb_logo));
    175    }
    176
    177  rom[0x144] = o->licensee_str[0];
    178  rom[0x145] = o->licensee_str[1];
    179
    180  /*
    181   * 0134-0142: Title of the game in UPPER CASE ASCII. If it
    182   * is less than 16 characters then the
    183   * remaining bytes are filled with 00's.
    184   */
    185
    186  /* capitalize cartridge name */
    187  for (i = 0; i < CART_NAME_LEN; ++i)
    188    {
    189      rom[0x134 + i] = toupper (o->cart_name[i]);
    190    }
    191
    192  if (o->is_gbc == 1)
    193    {
    194      rom[0x143] = 0x80;
    195    }
    196
    197  if (o->is_gbc == 2)
    198    {
    199      rom[0x143] = 0xC0;
    200    }
    201
    202  if (o->is_sgb)
    203    {
    204      rom[0x146] = 0x03;
    205    }
    206
    207  /*
    208   * 0147: Cartridge type:
    209   * 0-ROM ONLY            12-ROM+MBC3+RAM
    210   * 1-ROM+MBC1            13-ROM+MBC3+RAM+BATT
    211   * 2-ROM+MBC1+RAM        19-ROM+MBC5
    212   * 3-ROM+MBC1+RAM+BATT   1A-ROM+MBC5+RAM
    213   * 5-ROM+MBC2            1B-ROM+MBC5+RAM+BATT
    214   * 6-ROM+MBC2+BATTERY    1C-ROM+MBC5+RUMBLE
    215   * 8-ROM+RAM             1D-ROM+MBC5+RUMBLE+SRAM
    216   * 9-ROM+RAM+BATTERY     1E-ROM+MBC5+RUMBLE+SRAM+BATT
    217   * B-ROM+MMM01           1F-Pocket Camera
    218   * C-ROM+MMM01+SRAM      FD-Bandai TAMA5
    219   * D-ROM+MMM01+SRAM+BATT FE - Hudson HuC-3
    220   * F-ROM+MBC3+TIMER+BATT FF - Hudson HuC-1
    221   * 10-ROM+MBC3+TIMER+RAM+BATT
    222   * 11-ROM+MBC3
    223   */
    224  rom[0x147] = o->mbc_type;
    225
    226  /*
    227   * 0148 ROM size:
    228   * 0 - 256Kbit = 32KByte = 2 banks
    229   * 1 - 512Kbit = 64KByte = 4 banks
    230   * 2 - 1Mbit = 128KByte = 8 banks
    231   * 3 - 2Mbit = 256KByte = 16 banks
    232   * 4 - 4Mbit = 512KByte = 32 banks
    233   * 5 - 8Mbit = 1MByte = 64 banks
    234   * 6 - 16Mbit = 2MByte = 128 banks
    235   * $52 - 9Mbit = 1.1MByte = 72 banks
    236   * $53 - 10Mbit = 1.2MByte = 80 banks
    237   * $54 - 12Mbit = 1.5MByte = 96 banks
    238   */
    239  switch (o->nb_rom_banks)
    240    {
    241    case 2:
    242      rom[0x148] = 0;
    243      break;
    244
    245    case 4:
    246      rom[0x148] = 1;
    247      break;
    248
    249    case 8:
    250      rom[0x148] = 2;
    251      break;
    252
    253    case 16:
    254      rom[0x148] = 3;
    255      break;
    256
    257    case 32:
    258      rom[0x148] = 4;
    259      break;
    260
    261    case 64:
    262      rom[0x148] = 5;
    263      break;
    264
    265    case 128:
    266      rom[0x148] = 6;
    267      break;
    268
    269    case 256:
    270      rom[0x148] = 7;
    271      break;
    272
    273    case 512:
    274      rom[0x148] = 8;
    275      break;
    276
    277    default:
    278      fprintf (stderr, "warning: unsupported number of ROM banks (%d)\n", o->nb_rom_banks);
    279      rom[0x148] = 0;
    280      break;
    281    }
    282
    283  /*
    284   * 0149 RAM size:
    285   * 0 - None
    286   * 1 - 16kBit = 2kB = 1 bank
    287   * 2 - 64kBit = 8kB = 1 bank
    288   * 3 - 256kBit = 32kB = 4 banks
    289   * 4 - 1MBit =128kB =16 banks
    290   */
    291  switch (o->nb_ram_banks)
    292    {
    293    case 0:
    294      rom[0x149] = 0;
    295      break;
    296
    297    case 1:
    298      rom[0x149] = 2;
    299      break;
    300
    301    case 4:
    302      rom[0x149] = 3;
    303      break;
    304
    305    case 16:
    306      rom[0x149] = 4;
    307      break;
    308
    309    default:
    310      fprintf (stderr, "warning: unsupported number of RAM banks (%d)\n", o->nb_ram_banks);
    311      rom[0x149] = 0;
    312      break;
    313    }
    314
    315  rom[0x14A] = o->non_jp;
    316
    317  rom[0x14B] = o->licensee_id;
    318
    319  for (i = 0; i < 16; i+=2)
    320    {
    321      if(o->address_overwrite[i] != 0xFF)
    322        {
    323          rom[0x0100 & o->address_overwrite[i]] = o->address_overwrite[i+1];
    324          // warnings for builds ported from ancient GBDK
    325          fprintf (stderr, "caution: -yp0x01%02x=0x%02x is outdated", o->address_overwrite[i], o->address_overwrite[i+1]);
    326          if(o->address_overwrite[i] == 0x43)
    327            switch(o->address_overwrite[i+1]&0xC0)
    328              {
    329              case 0x80:
    330                fprintf (stderr, ", please use -yc instead");
    331                break;
    332              case 0xC0:
    333                fprintf (stderr, ", please use -yC instead");
    334                break;
    335              default:
    336                o->address_overwrite[i] = 0xFF;
    337              }
    338          if(o->address_overwrite[i] == 0x44 || o->address_overwrite[i] == 0x45)
    339              fprintf (stderr, ", please use -yk cc instead");
    340          if(o->address_overwrite[i] == 0x46)
    341            if(o->address_overwrite[i+1] == 0x03)
    342              fprintf (stderr, ", please use -ys instead");
    343            else
    344              o->address_overwrite[i] = 0xFF;
    345          if(o->address_overwrite[i] == 0x47)
    346            fprintf (stderr, ", please use -yt 0x%02x instead", o->address_overwrite[i+1]);
    347          if(o->address_overwrite[i] == 0x4A)
    348            fprintf (stderr, ", please use -yl 0x%02x instead", o->address_overwrite[i+1]);
    349          if(o->address_overwrite[i] == 0x4B && o->address_overwrite[i+1] == 1)
    350            fprintf (stderr, ", please use -yj instead");
    351          if(o->address_overwrite[i] == 0xFF)
    352            fprintf (stderr, ", this setting is the default");
    353          fprintf (stderr, ".\n");
    354        }
    355    }
    356
    357  /* Update complement checksum */
    358  chk = 0;
    359  for (i = 0x134; i < 0x14d; ++i)
    360    chk += rom[i];
    361  rom[0x014d] = (unsigned char) (0xe7 - (chk & 0xff));
    362
    363  /* Update checksum */
    364  chk = 0;
    365  rom[0x14e] = 0;
    366  rom[0x14f] = 0;
    367  for (i = 0; i < size; ++i)
    368    chk += rom[i];
    369  rom[0x14e] = (unsigned char) ((chk >> 8) & 0xff);
    370  rom[0x14f] = (unsigned char) (chk & 0xff);
    371
    372  if (*real_size < 0x150)
    373    *real_size = 0x150;
    374}
    375
    376void
    377sms_postproc (BYTE * rom, int size, int *real_size, struct sms_opt_s *o)
    378{
    379  // based on https://www.smspower.org/Development/ROMHeader
    380  // 0x1ff0 and 0x3ff0 are also possible, but never used
    381  static const char tmr_sega[] = "TMR SEGA  ";
    382  short header_base = 0x7ff0;
    383  int chk = 0;
    384  unsigned long i;
    385  // choose earlier positions for smaller roms
    386  if (header_base > size)
    387    header_base = 0x3ff0;
    388  if (header_base > size)
    389    header_base = 0x1ff0;
    390
    391  memcpy (&rom[header_base], tmr_sega, sizeof (tmr_sega) - 1);
    392  // configure amounts of bytes to check
    393  switch(o->rom_size)
    394    {
    395      case 0xa:
    396      default:
    397        i = 0x1FEF;
    398        break;
    399      case 0xb:
    400        i = 0x3FEF;
    401        break;
    402      case 0xc:
    403        i = 0x7FEF;
    404        break;
    405      case 0xd:
    406        i = 0xBFEF;
    407        break;
    408      case 0xe:
    409        i = 0xFFFF;
    410        break;
    411      case 0xf:
    412        i = 0x1FFFF;
    413        break;
    414      case 0x0:
    415        i = 0x3FFFF;
    416        break;
    417      case 0x1:
    418        i = 0x7FFFF;
    419        break;
    420      case 0x2:
    421        i = 0xFFFFF;
    422        break;
    423    }
    424  // calculate checksum
    425  for(;i > 0; --i)
    426    {
    427      chk += rom[i];
    428      // 0x7FF0 - 0x7FFF is skipped
    429      if(i == 0x8000)
    430        i = 0x7FF0;
    431    }
    432  // we  skipped index 0
    433  chk += rom[0];
    434  // little endian
    435  rom[header_base + 0xa] = chk & 0xff;
    436  rom[header_base + 0xb] = (chk>>8) & 0xff;
    437  // game version
    438  rom[header_base + 0xe] &= 0xF0;
    439  rom[header_base + 0xe] |= o->version;
    440  // rom size
    441  rom[header_base + 0xf] = (o->region_code << 4) | o->rom_size;
    442}
    443
    444int
    445rom_autosize_grow(BYTE **rom, int test_size, int *size, struct gb_opt_s *o)
    446{
    447  int last_size = *size;
    448
    449  while ((test_size > *size) && (o->nb_rom_banks <= 512))
    450    {
    451      o->nb_rom_banks *= 2;
    452      // banks work differently for mbc6, they have half the size
    453      // but this in general ignored by -yo
    454      *size = o->nb_rom_banks * 0x4000;
    455    }
    456
    457  if (o->nb_rom_banks > 512)
    458    {
    459      fprintf (stderr, "error: auto-size banks exceeded max of 512 banks.\n");
    460      return 0;
    461    }
    462  else
    463    {
    464      BYTE * t_rom = *rom;
    465      *rom = realloc (*rom, *size);
    466      if (*rom == NULL)
    467        {
    468          free(t_rom);
    469          fprintf (stderr, "error: couldn't re-allocate size for larger rom image.\n");
    470          return 0;
    471        }
    472      memset (*rom + last_size, FILL_BYTE, *size - last_size);
    473    }
    474
    475  return 1;
    476}
    477
    478int
    479noi2sym (char *filename)
    480{
    481  FILE *noi, *sym;
    482  char *nname, *sname;
    483  //ssize_t read;
    484  char read = ' ';
    485  // no$gmb's implementation is limited to 32 character labels
    486  // we can safely throw away the rest
    487  char label[33];
    488  // 0x + 6 digit hex number
    489  // -> 65536 rom banks is the maximum homebrew cartrideges support (TPP1)
    490  char value[9];
    491  int name_len = strlen(filename);
    492  int i = 0;
    493  // copy filename's value to nname and sname
    494  nname = malloc((name_len+1) * sizeof(char));
    495  strcpy (nname, filename);
    496  sname = malloc((name_len+1) * sizeof(char));
    497  strcpy (sname, filename);
    498  // change the extensions
    499  nname[name_len-1]='i';
    500  nname[name_len-2]='o';
    501  nname[name_len-3]='n';
    502  sname[name_len-1]='m';
    503  sname[name_len-2]='y';
    504  sname[name_len-3]='s';
    505
    506  if (NULL == (noi = fopen (nname, "r")))
    507    {
    508      fprintf (stderr, "error: can't open %s: ", nname);
    509      perror(NULL);
    510      return 1;
    511    }
    512  if (NULL == (sym = fopen (sname, "w")))
    513    {
    514      fprintf (stderr, "error: can't create %s: ", sname);
    515      perror(NULL);
    516      return 1;
    517    }
    518  // write header
    519  fprintf (sym, "; no$gmb compatible .sym file\n; Generated automagically by makebin\n");
    520  // iterate through .noi file
    521  while (read != EOF && (read = fgetc(noi)) != EOF)
    522    {
    523      // just skip line breaks
    524      if (read == '\r' || read == '\n')
    525        continue;
    526      // read first 4 chars
    527      for (i = 0; i < 4; ++i)
    528        {
    529          value[i] = read;
    530          if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n')
    531            {
    532              // leave for-loop
    533              break;
    534            }
    535        }
    536      // we left loop early
    537      if (i != 4)
    538        continue;
    539      // only accept if line starts with this
    540      if (strncmp(value, "DEF ", 4) == 0)
    541        {
    542          // read label
    543          for (i = 0; i < 32; ++i)
    544            {
    545              label[i] = read;
    546              if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n' || read == ' ')
    547                {
    548                  // leave for-loop
    549                  break;
    550                }
    551            }
    552          // skip rest of the label
    553          while (read != EOF && read != '\r' && read != '\n' && read != ' ')
    554            read = fgetc(noi);
    555          // it has to be end of file or line if it's not space
    556          if (read != ' ')
    557            continue;
    558          // strings have to end with \0
    559          label[i+1] = '\0';
    560          // read value
    561          for (i = 0; i < 8; ++i)
    562            {
    563              value[i] = read;
    564              if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n')
    565                {
    566                  // leave for-loop
    567                  break;
    568                }
    569            }
    570          // number is too long; ignore
    571          if (read != EOF && read != '\r' && read != '\n')
    572            continue;
    573          value[i+1] = '\0';
    574          // we successfully read label and value
    575
    576          // but filter out some invalid symbols
    577          if (strcmp(label, ".__.ABS.") != 0 && strncmp(label, "l__", 3) != 0)
    578            fprintf (sym, "%02X:%04X %s\n", (unsigned int)(strtoul(value, NULL, 0)>>16), (unsigned int)strtoul(value, NULL, 0)&0xFFFF, label);
    579        }
    580      else
    581        // skip until file/line end
    582        while ((read = fgetc(noi))!= EOF && read != '\r' && read != '\n');
    583    }
    584
    585  // free close files
    586  fclose (noi);
    587  fclose (sym);
    588
    589  fprintf (stderr, "Converted %s to %s.\n", nname, sname);
    590  return 0;
    591}
    592
    593int
    594read_ihx (FILE *fin, BYTE **rom, int *size, int *real_size, struct gb_opt_s *o)
    595{
    596  int record_type;
    597
    598  int extaddr = 0;
    599  do
    600    {
    601      int nbytes;
    602      int addr;
    603      int checksum, sum = 0;
    604
    605      if (getc (fin) != ':')
    606        {
    607          fprintf (stderr, "error: invalid IHX line.\n");
    608          return 0;
    609        }
    610      nbytes = getbyte (fin, &sum);
    611      addr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum);
    612      record_type = getbyte (fin, &sum);
    613      if(record_type == 4)
    614        {
    615          extaddr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum);
    616          extaddr <<= 16; // those are the upper 16 bits
    617          checksum = getbyte (fin, &sum);
    618          // move to the next record
    619          if (0 != (sum & 0xff))
    620            {
    621              fprintf (stderr, "error: bad checksum: %02x.\n", checksum);
    622              return 0;
    623            }
    624          while (isspace (sum = getc (fin)))  /* skip all kind of spaces */
    625            ;
    626          ungetc (sum, fin);
    627          if (getc (fin) != ':')
    628            {
    629              fprintf (stderr, "error: invalid IHX line.\n");
    630              return 0;
    631            }
    632          // parse real data part
    633          checksum = sum = 0;
    634          nbytes = getbyte (fin, &sum);
    635          // lower 16 bits
    636          addr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum);
    637          record_type = getbyte (fin, &sum);
    638        }
    639      // add linear address extension
    640      addr |= extaddr;
    641      // TODO: warn for unreachable banks according to chosen MBC
    642      if (record_type > 1)
    643        {
    644          fprintf (stderr, "error: unsupported record type: %02x.\n", record_type);
    645          return 0;
    646        }
    647
    648      if (addr + nbytes > *size)
    649        {
    650          // If auto-size is enabled, grow rom bank size by power of 2 when needed
    651          if (o->rom_banks_autosize)
    652            {
    653              if (rom_autosize_grow(rom, addr + nbytes, size, o) == 0)
    654                return 0;
    655            }
    656          else
    657            {
    658              fprintf (stderr, "error: size of the buffer is too small.\n");
    659              return 0;
    660            }
    661        }
    662
    663      while (nbytes--)
    664        {
    665          if (addr < *size)
    666            (*rom)[addr++] = getbyte (fin, &sum);
    667        }
    668
    669      if (addr > *real_size)
    670        *real_size = addr;
    671
    672      checksum = getbyte (fin, &sum);
    673      if (0 != (sum & 0xff))
    674        {
    675          fprintf (stderr, "error: bad checksum: %02x.\n", checksum);
    676          return 0;
    677        }
    678
    679      while (isspace (sum = getc (fin)))  /* skip all kind of spaces */
    680        ;
    681      ungetc (sum, fin);
    682    }
    683  while (1 != record_type); /* EOF record */
    684
    685  return 1;
    686}
    687
    688int
    689main (int argc, char **argv)
    690{
    691  int size = 32768, skipsize = 0, pack = 0, real_size = 0, i = 0;
    692  char *token;
    693  BYTE *rom;
    694  FILE *fin, *fout;
    695  char *filename = NULL;
    696  int ret;
    697  int gb = 0;
    698  int sms = 0;
    699
    700  struct gb_opt_s gb_opt = {.cart_name="",
    701                            .licensee_str={'0', '0'},
    702                            .mbc_type=0,
    703                            .nb_rom_banks=2,
    704                            .nb_ram_banks=0,
    705                            .licensee_id=0x33,
    706                            .is_gbc=0,
    707                            .is_sgb=0,
    708                            .sym_conversion=0,
    709                            .non_jp=0,
    710                            .rom_banks_autosize=0,
    711                            .do_logo_copy=true,
    712                            .address_overwrite={0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0} };
    713
    714  struct sms_opt_s sms_opt = {.rom_size=0xa,
    715                              .region_code=7,
    716                              .version=0 };
    717
    718#if defined(_WIN32)
    719  setmode (fileno (stdout), O_BINARY);
    720#endif
    721
    722  while (*++argv && '-' == argv[0][0] && '\0' != argv[0][1])
    723    {
    724      switch (argv[0][1])
    725        {
    726        case 's':
    727          if (!*++argv)
    728            {
    729              usage ();
    730              return 1;
    731            }
    732          size = strtoul (*argv, NULL, 0);
    733          break;
    734
    735        case 't':
    736          if (!*++argv)
    737            {
    738              usage ();
    739              return 1;
    740            }
    741          skipsize = strtoul (*argv, NULL, 0);
    742          break;
    743
    744        case 'h':
    745          usage ();
    746          return 0;
    747
    748        case 'p':
    749          pack = 1;
    750          break;
    751
    752        case 'Z':
    753          /* generate GameBoy binary file */
    754          gb = 1;
    755          break;
    756
    757        case 'y':
    758          /* GameBoy options:
    759           * -yo  Number of rom banks (default: 2)
    760           * -ya  Number of ram banks (default: 0)
    761           * -yt  MBC type (default: no MBC)
    762           * -yn  Name of program (default: name of output file)
    763           */
    764          switch (argv[0][2])
    765            {
    766            case 'o':
    767              if (!*++argv)
    768                {
    769                  usage ();
    770                  return 1;
    771                }
    772              // Use auto-size for rom banks if -yto size param is 'A'
    773              if ((*argv)[0] == 'A' || (*argv)[0] == 'a')
    774                  gb_opt.rom_banks_autosize = 1;
    775              else
    776                {
    777                  gb_opt.nb_rom_banks = strtoul (*argv, NULL, 0);
    778                  size = gb_opt.nb_rom_banks * 0x4000;
    779                }
    780              break;
    781
    782            case 'a':
    783              if (!++argv)
    784                {
    785                  usage ();
    786                  return 1;
    787                }
    788              gb_opt.nb_ram_banks = strtoul (*argv, NULL, 0);
    789              break;
    790
    791            case 't':
    792              if (!*++argv)
    793                {
    794                  usage ();
    795                  return 1;
    796                }
    797              gb_opt.mbc_type = strtoul (*argv, NULL, 0);
    798              break;
    799
    800            case 'n':
    801              if (!*++argv)
    802                {
    803                  usage ();
    804                  return 1;
    805                }
    806              strncpy (gb_opt.cart_name, *argv, CART_NAME_LEN-1);
    807              gb_opt.cart_name[CART_NAME_LEN-1] = '\0';
    808              break;
    809
    810            case 'k':
    811              if (!*++argv)
    812                {
    813                  usage ();
    814                  return 1;
    815                }
    816              strncpy (gb_opt.licensee_str, *argv, 2);
    817              break;
    818
    819            case 'l':
    820              if (!*++argv)
    821                {
    822                  usage ();
    823                  return 1;
    824                }
    825              gb_opt.licensee_id = strtoul (*argv, NULL, 0);
    826              break;
    827
    828            case 'c':
    829              gb_opt.is_gbc = 1;
    830              break;
    831
    832            case 'C':
    833              gb_opt.is_gbc = 2;
    834              break;
    835
    836            case 'N':
    837              gb_opt.do_logo_copy = false; // when switch is present, turn off logo copy
    838              break;
    839
    840            case 's':
    841              gb_opt.is_sgb = 1;
    842              break;
    843
    844            case 'S':
    845              gb_opt.sym_conversion = 1;
    846              break;
    847
    848            case 'j':
    849              gb_opt.non_jp = 1;
    850              break;
    851
    852            // like -yp0x143=0x80
    853            case 'p':
    854              // remove "-yp"
    855              *argv += 3;
    856              // effectively split string into argv and token
    857              strtok(*argv, "=");
    858              token = strtok(NULL, "=");
    859              for (i = 0; i < 16; i+=2)
    860                {
    861                  if(gb_opt.address_overwrite[i] == 0xFF)
    862                    {
    863                      gb_opt.address_overwrite[i] = strtoul (*argv, NULL, 0);
    864                      gb_opt.address_overwrite[i+1] = strtoul (token, NULL, 0);
    865                      break;
    866                    }
    867                }
    868              break;
    869
    870            default:
    871              usage ();
    872              return 1;
    873            }
    874          break;
    875
    876        case 'S':
    877          /* generate SMS binary file */
    878          sms = 1;
    879          break;
    880
    881        case 'x':
    882
    883          switch (argv[0][2])
    884            {
    885            case 'o':
    886              if (!*++argv)
    887                {
    888                  usage ();
    889                  return 1;
    890                }
    891              sms_opt.rom_size = strtoul (*argv, NULL, 0);
    892              if ( sms_opt.rom_size > 2 && (sms_opt.rom_size < 0xa || sms_opt.rom_size > 0xf ) )
    893                {
    894                  fprintf (stderr, "error: invalid rom size (0x%X)", sms_opt.rom_size);
    895                  perror(NULL);
    896                  return 1;
    897                }
    898              if ( sms_opt.rom_size == 0xd || sms_opt.rom_size == 0x2 )
    899                {
    900                  fprintf (stderr, "warning: this rom size (0x%X) is bugged in some BIOSes\n", sms_opt.rom_size);
    901                }
    902              break;
    903
    904            case 'j':
    905              if (!*++argv)
    906                {
    907                  usage ();
    908                  return 1;
    909                }
    910              sms_opt.region_code = strtoul (*argv, NULL, 0);
    911              if ( sms_opt.region_code < 3 && sms_opt.region_code > 7 )
    912                {
    913                  fprintf (stderr, "error: invalid region code (0x%X)", sms_opt.region_code);
    914                  perror(NULL);
    915                  return 1;
    916                }
    917              break;
    918
    919            case 'v':
    920              if (!*++argv)
    921                {
    922                  usage ();
    923                  return 1;
    924                }
    925              sms_opt.version = strtoul (*argv, NULL, 0);
    926              if ( sms_opt.version > 0xf )
    927                {
    928                  fprintf (stderr, "error: invalid version (0x%X)", sms_opt.version);
    929                  perror(NULL);
    930                  return 1;
    931                }
    932              break;
    933
    934            default:
    935              usage ();
    936              return 1;
    937            }
    938          break;
    939
    940        default:
    941          usage ();
    942          return 1;
    943        }
    944    }
    945
    946  fin = stdin;
    947  fout = stdout;
    948  if (*argv)
    949    {
    950      if ('-' != argv[0][0] || '\0' != argv[0][1])
    951        {
    952          if (NULL == (fin = fopen (*argv, "r")))
    953            {
    954              fprintf (stderr, "error: can't open %s: ", *argv);
    955              perror(NULL);
    956              return 1;
    957            }
    958          filename = *argv;
    959        }
    960      ++argv;
    961    }
    962
    963  if (NULL != argv[0] && NULL != argv[1])
    964    {
    965      usage ();
    966      return 1;
    967    }
    968
    969  rom = malloc (size);
    970  if (rom == NULL)
    971    {
    972      fclose (fin);
    973      fprintf (stderr, "error: couldn't allocate room for the image.\n");
    974      return 1;
    975    }
    976  memset (rom, FILL_BYTE, size);
    977
    978  if (gb_opt.sym_conversion == 1)
    979    {
    980      if (filename)
    981        noi2sym(filename);
    982      else
    983        {
    984          fprintf (stderr, "error: .noi to .sym conversion needs an input file.\n");
    985        }
    986    }
    987
    988  ret = read_ihx (fin, &rom, &size, &real_size, &gb_opt);
    989
    990  fclose (fin);
    991
    992  if (ret)
    993    {
    994      if (gb)
    995        gb_postproc (rom, size, &real_size, &gb_opt);
    996      else if (sms)
    997        sms_postproc (rom, size, &real_size, &sms_opt);
    998
    999      if (*argv)
   1000        {
   1001          if ('-' != argv[0][0] || '\0' != argv[0][1])
   1002            {
   1003              if (NULL == (fout = fopen (*argv, "wb")))
   1004                {
   1005                  fprintf (stderr, "error: can't create %s: ", *argv);
   1006                  perror(NULL);
   1007                  return 1;
   1008                }
   1009            }
   1010        }
   1011
   1012      int writesize = (pack ? real_size : size) - skipsize;
   1013      if (writesize > 0) fwrite (rom + skipsize, 1, writesize, fout);
   1014
   1015      fclose (fout);
   1016
   1017      return 0;
   1018    }
   1019  else
   1020    return 1;
   1021}
   1022