/*
  V64 Jr Flash Save Util by CrowTRobo
  Jr code based on Bung's source code and info.
  October 2, 2003.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*#include <conio.h>*/

void
_outp (unsigned short port, unsigned char byte)
{
#if     defined __BEOS__ || defined AMIGA
  st_ioport_t temp;

  temp.port = port;
  temp.data8 = byte;
  ioctl (ucon64_io_fd, 'w', &temp, 0);
#elif   defined __i386__
  __asm__ __volatile__
  ("outb %1, %0"
    :
    : "d" (port), "a" (byte)
  );
#endif
}

unsigned char
_inp (unsigned short port)
{
#if     defined __BEOS__ || defined AMIGA
  st_ioport_t temp;

  temp.port = port;
  ioctl (ucon64_io_fd, 'r', &temp, 0);

  return temp.data8;
#elif   defined __i386__
  unsigned char byte;

  __asm__ __volatile__
  ("inb %1, %0"
    : "=a" (byte)
    : "d" (port)
  );

  return byte;
#endif
}

#define MAX_PATH 1024
#define stricmp strcasecmp
#define strnicmp strncasecmp
#define getch getchar


#define LOAD_FLASH 1
#define SAVE_FLASH 2

#define set_ai_write()    _outp(port_ctrl,5);    // ninit=1, nwrite=0
#define set_data_write()  _outp(port_ctrl,1);    // ninit=0, nwrite=0
#define set_data_read()   _outp(port_ctrl,0);    // ninit=0, nwrite=1
#define set_normal()      _outp(port_ctrl,4);    // ninit=1, nwrite=1

unsigned short port = 0, port_stat, port_ctrl, port_addr, port_data;

void set_ai(unsigned char _ai) {

  set_ai_write()     // ninit=1, nwrite=0
  _outp(port_addr,_ai);
}

void set_ai_data(unsigned char _ai,unsigned char _data) {

  set_ai(_ai);
  set_data_write()   // ninit=0, nwrite=0
  _outp(port_data, _data);
}

void InitPort() {

  _outp(port_stat, 1);    // clear timeout flag
  set_ai_data(6, 0x0a);   // enable port control
  set_ai_data(7, 0x05);   //
  set_ai_data(5, 3);      // enable dram write & cart read
}

void ClosePort() {

  set_ai_data(5, 3);      // enable dram write & cart read
  set_ai_data(7, 0);      // quit using port
  set_ai_data(6, 0);      //
  set_normal();           //
}

int JrPresent(unsigned short p) {

  port = p;
  port_stat = port+1;
  port_ctrl = port_stat+1;
  port_addr = port_ctrl+1;
  port_data = port_addr+1;

  InitPort();
  set_ai_data(3, 0x12);   // send address of 0x12345678
  set_ai_data(2, 0x34);
  set_ai_data(1, 0x56);
  set_ai_data(0, 0x78);

  /* Read each address byte back to see if it matches */
  set_ai(3);
  set_data_read();

  if ((_inp(port_data) & 0x1f) != 0x12)
    return 0;

  set_ai(2);
  set_data_read();

  if (_inp(port_data) != 0x34)
    return 0;

  set_ai(1);
  set_data_read();

  if (_inp(port_data) != 0x56)
    return 0;

  set_ai(0);
  set_data_read();

  if (_inp(port_data) != 0x78)
    return 0;

  ClosePort();
  return 1;
}

void SendData(unsigned char *buf, unsigned long addr, int length) {

  int i;

  InitPort();
  set_ai_data(3, (unsigned char)(addr >> 24));           // write address
  set_ai_data(2, (unsigned char)((addr >> 16) & 0xff));  //
  set_ai_data(1, (unsigned char)((addr >> 8) & 0xff));   //
  set_ai_data(0, (unsigned char)(addr & 0xff));          //

  set_ai(4);          // start sending data
  set_data_write();   //

  for (i = 0; i < length; i++) {
    _outp(port_data, buf[i]);
  }

  ClosePort();
}

void GetData(unsigned char *buf, unsigned long addr, int length) {

  int i;

  InitPort();
  set_ai_data(3, (unsigned char)(addr >> 24));           // write address
  set_ai_data(2, (unsigned char)((addr >> 16) & 0xff));  //
  set_ai_data(1, (unsigned char)((addr >> 8) & 0xff));   //
  set_ai_data(0, (unsigned char)(addr & 0xff));          //

  set_ai(4);          // start sending data
  set_data_read();    //

  for (i = 0; i < length; i++)
    buf[i] = _inp(port_data);

  ClosePort();
}

void ByteSwap(unsigned char *buf, int length) {

  unsigned char temp;
  int i;

  for (i = 0; i < length; i+=2) {
    temp = buf[i];
    buf[i] = buf[i+1];
    buf[i+1] = temp;
  }
}
    
int main(int argc, char *argv[]) {

  FILE *fp;
  unsigned char flash[131072], command[512], *rom, c = 0;
  unsigned char filename[MAX_PATH];
  int i, length, cmd;

  printf("\nV64 Jr Flash Save Util by CrowTRobo\n\n");
       
  if (argc < 3) {
    
    printf("Usage: ctr-jfsu <switches> <file>\n\n");
    printf("switches:\t-l    Restores flash\n");
    printf("         \t-s    Saves flash\n");
    printf("         \t-p#   Use port # (default = 0x378)\n");
    return 1;
  }

  for (i = 1; i < argc; i++) {

    if (argv[i][0] == '-') {
      argv[i]++;

      if (stricmp("l", argv[i]) == 0)
        cmd = LOAD_FLASH;
      else if (stricmp("s", argv[i]) == 0)
        cmd = SAVE_FLASH;
      else if (strnicmp("p", argv[i], 1) == 0)
        port = strtol(argv[i]+1, NULL, 16);
      else {
        printf("Invalid switch: %s\n", argv[i]);
        return 1;
      }
    }
    else
      break;
  }

  strcpy(filename, argv[i]);

  if (cmd != LOAD_FLASH && cmd != SAVE_FLASH) {
    printf("No command given!\n");
    return 1;
  }

  if (port == 0)
    port = 0x378;

  if (!JrPresent(port)) {
    printf("V64 Jr not found!\n");
    return 1;
  } 

  printf("Found V64 Jr at port 0x%X\n", port);

  if ((fp = fopen("ctr-jfsu.rom", "rb")) == NULL) {
    printf("Could not open ctr-jfsu.rom!\n");
    return 1;
  }

  fseek(fp, 0, SEEK_END);
  length = ftell(fp);
  fseek(fp, 0, SEEK_SET);

  if ((rom = (unsigned char *)malloc(length)) == NULL) {
    printf("Could not allocate memory!\n");
    fclose(fp);
    return 1;
  }

  fread(rom, 1, length, fp);
  fclose(fp);

  if (cmd == SAVE_FLASH) {

    if ((fp = fopen(filename, "wb")) == NULL) {
      printf("Could not open %s!\n", filename);
      free(rom);
      return 1;
    }

    // Write command to Jr at address where rom looks for command

    memset(command, 0, 512);
    command[0] = SAVE_FLASH;
    SendData(command, 0xb1020000, 512);

    // Send rom

    printf("Sending rom\n");

    for (i = 0; i < length/512; i++)
      SendData(&rom[i*512], 0xb0000000+(i*512), 512);

    // Wait till user hits enter, then read save data from Jr.

    printf("Turn on the N64 and press ENTER when prompted\n");

    while (c != 13)
      c = getch();

    printf("Reading save data\n");

    for (i = 0; i < 131072/512; i++)
      GetData(&flash[i*512], 0xb0000000+(i*512), 512);

    // Byte swap save data and write to file.

    ByteSwap(flash, 131072);
    fwrite(flash, 1, 131072, fp);
    fclose(fp);
    printf("%s saved\n", filename);
  }
  else if (cmd == LOAD_FLASH) {

    if ((fp = fopen(filename, "rb")) == NULL) {
      printf("Could not open %s!\n", filename);
      free(rom);
      return 1;
    }
    
    fread(flash, 1, 131072, fp);
    fclose(fp);

    // Write command to Jr at address where rom looks for command

    memset(command, 0, 512);
    command[0] = LOAD_FLASH;
    SendData(command, 0xb1020000, 512);

    // Send save data

    printf("Sending save data\n");

    for (i = 0; i < 131072/512; i++)
      SendData(&flash[i*512], 0xb1000000+(i*512), 512);

    // Send rom

    printf("Sending rom\n");

    for (i = 0; i < length/512; i++)
      SendData(&rom[i*512], 0xb0000000+(i*512), 512);

    printf("Turn on the N64 to write save data\n");
  }

  free(rom);
  return 0;
}

