//////////////////////////////////////////////////////////////////////////////
// Radiant Silvergun EXTRA
//
// Miscellaneous ST-V ROM adjustment tool
//
// Copyright (C)2023-2024 Alex "trap15" Marshall <trap15@raidenii.net>
// All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//gcc -static -fno-strict-aliasing -o romtool ./romtool.c

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

static void usage(const char *app) {
  fprintf(stderr,
    "Usage:\n"
    "\t%s [mode] <outputs> <inputs>\n"
    "\n"
    "Modes:\n"
    "\t-m\tBuilds mask ROMs from single binary input.\n"
    "\t-p\tBuilds patch EPROM from binary input.\n"
    "\t-e\tBuilds a single binary output from mask ROMs.\n"
    "\t-i\tCreates a multigame-compatible ROM image from a patch EPROM.\n"
    , app);
}

static void build_mask(const char **out_fns, int out_count, const char *in_fn) {
  FILE *ifp = fopen(in_fn, "rb");
  if(ifp == NULL) {
    fprintf(stderr, "Can't open '%s' for reading\n", in_fn);
    exit(-2);
  }
  fseek(ifp, 0, SEEK_END);
  size_t size = ftell(ifp);
  fseek(ifp, 0, SEEK_SET);
  uint8_t *buf = malloc(size);
  fread(buf, size, 1, ifp);
  fclose(ifp);

  size_t pos = 0;
  for(int i = 0; i < out_count; i++) {
    FILE *ofp = fopen(out_fns[i], "wb");
    if(ofp == NULL) {
      fprintf(stderr, "Can't open '%s' for writing\n", out_fns[i]);
      exit(-3);
    }

    size_t outsize = (i == 0) ? 0x0200000 : 0x0400000;

    fwrite(buf+pos, outsize, 1, ofp);
    fclose(ofp);

    pos += outsize;
  }
}

static uint16_t r16(uint8_t *buf) {
  return (buf[0] << 8) | (buf[1]);
}

static uint32_t r32(uint8_t *buf) {
  return (r16(buf+0) << 16) | (r16(buf+2));
}

static void w16(uint8_t *buf, uint32_t val) {
  buf[0] = val >> 8;
  buf[1] = val >> 0;
}

static void w32(uint8_t *buf, uint32_t val) {
  w16(buf+0, val >> 16);
  w16(buf+2, val >> 0);
}

static void build_interleaved(const char *out_fn, const char *in_fn) {
  FILE *ifp = fopen(in_fn, "rb");
  if(ifp == NULL) {
    fprintf(stderr, "Can't open '%s' for reading\n", in_fn);
    exit(-2);
  }
  fseek(ifp, 0, SEEK_END);
  size_t insize = ftell(ifp);
  fseek(ifp, 0, SEEK_SET);
  uint8_t *inbuf = malloc(insize);
  fread(inbuf, insize, 1, ifp);
  fclose(ifp);

  FILE *ofp = fopen(out_fn, "wb");
  uint8_t null = 0;
  for(size_t i = 0; i < insize; i++) {
    fwrite(inbuf+i, 1, 1, ofp);
    fwrite(&null, 1, 1, ofp);
  }
  fclose(ofp);
}

static void build_patch(const char *out_fn, const char *in_fn) {
  FILE *ifp = fopen(in_fn, "rb");
  if(ifp == NULL) {
    fprintf(stderr, "Can't open '%s' for reading\n", in_fn);
    exit(-2);
  }
  fseek(ifp, 0, SEEK_END);
  size_t insize = ftell(ifp);
  fseek(ifp, 0, SEEK_SET);
  uint8_t *inbuf = malloc(insize);
  fread(inbuf, insize, 1, ifp);
  fclose(ifp);

  FILE *ofp = fopen(out_fn, "wb");
  if(ofp == NULL) {
    fprintf(stderr, "Can't open '%s' for writing\n", out_fn);
    exit(-3);
  }

  const int c_addr_eprom_base = 0x22000000;

  const int c_addr_iplbin = 0x00000;
  const int c_size_iplbin = 0x01100;
  const int c_addr_gamebin = c_addr_iplbin+c_size_iplbin;
  const int c_size_gamebin = 0x34000;
  const int c_addr_biosbin = 0x40000;
  const int c_size_biosbin = 0x40000;

  uint8_t *outbuf = calloc(0x80000, 1);

  // Copy regions
  memcpy(outbuf+c_addr_iplbin,  inbuf+0,        c_size_iplbin);  // IPL.BIN
  memcpy(outbuf+c_addr_gamebin, inbuf+0x100000, c_size_gamebin); // GAME.BIN
  memcpy(outbuf+c_addr_biosbin, inbuf+0x080000, c_size_biosbin); // 0BIOS.BIN

  for(int i = 0; i < 2; i++) {
    uint8_t *entry = outbuf+c_addr_iplbin+0xF10 + i*0x10;
    // Point to our new 0BIOS.BIN (addresses and sizes need *2 because EPROM)
    w32(entry+0, c_addr_eprom_base + (c_addr_biosbin * 2));
    w32(entry+8, r32(entry+8) * 2);
  }

  w32(outbuf+c_addr_biosbin+0x55E0, c_addr_eprom_base + (c_addr_gamebin * 2));

  fwrite(outbuf,0x80000,1,ofp);
  fclose(ofp);
}

static void build_binary(const char *out_fn, const char **in_fns, int in_count) {
  FILE *ofp = fopen(out_fn, "wb");
  if(ofp == NULL) {
    fprintf(stderr, "Can't open '%s' for writing\n", out_fn);
    exit(-3);
  }
  for(int i = 0; i < in_count; i++) {
    FILE *ifp = fopen(in_fns[i], "rb");
    if(ifp == NULL) {
      fprintf(stderr, "Can't open '%s' for reading\n", in_fns[i]);
      exit(-2);
    }

    fseek(ifp, 0, SEEK_END);
    size_t size = ftell(ifp);
    fseek(ifp, 0, SEEK_SET);
    uint8_t *buf = malloc(size);
    fread(buf, size, 1, ifp);
    fwrite(buf, size, 1, ofp);
    free(buf);
    fclose(ifp);
  }
  fclose(ofp);
}

int main(int argc, const char *argv[]) {
  if(argc < 3) {
    usage(argv[0]);
    return 1;
  }
  const char *arg_mode = argv[1];
  if(arg_mode[0] != '-') {
    usage(argv[0]);
    return -1;
  }
  char mode = arg_mode[1];
  switch(mode) {
    case 'm':
      build_mask(argv+2, argc-3, argv[argc-1]);
      break;
    case 'p':
      if(argc != 4) {
        usage(argv[0]);
        return 1;
      }
      build_patch(argv[2], argv[3]);
      break;
    case 'e':
      build_binary(argv[2], argv+3, argc-3);
      break;
    case 'i':
      build_interleaved(argv[2], argv[3]);
      break;
  }

  return 0;
}
