/****************************************/
/*					*/
/*          N64 Rom Manager		*/
/*					*/
/*          By Salim Gasmi		*/
/*					*/
/*          salim@gasmi.net		*/
/*					*/
/****************************************/

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


#define VER	"V1.0"
#define MEGA	(1024*1024)
#define B1	0x80
#define B2	0x37

#define max2(a, b) ( (a)>(b) ? (a) : (b) )
#define min2(a, b) ( (a)<(b) ? (a) : (b) )

#define BUFSIZE 32768
#define SMALBUF	16384

#define CHECKSUM_START 0x1000
#define CHECKSUM_LENGTH 0x100000L
#define CHECKSUM_HEADERPOS 0x10
#define CHECKSUM_END (CHECKSUM_START + CHECKSUM_LENGTH)

#define CHECKSUM_STARTVALUE 0xf8ca4ddc

#define ROL(i, b) (((i)<<(b)) | ((i)>>(32-(b))))

#define BYTES2LONG(b, s) ( (((b)[0^(s)] & 0xffL) << 24) | \
                           (((b)[1^(s)] & 0xffL) << 16) | \
                           (((b)[2^(s)] & 0xffL) <<  8) | \
                           (((b)[3^(s)] & 0xffL)) )

#define LONG2BYTES(l, b, s)  (b)[0^(s)] = ((l)>>24)&0xff; \
                             (b)[1^(s)] = ((l)>>16)&0xff; \
                             (b)[2^(s)] = ((l)>> 8)&0xff; \
                             (b)[3^(s)] = ((l)    )&0xff;


int IsRom(unsigned char *);
void Swap(unsigned char *,int);
int ChkSum(FILE *,int,long,int);

int main(int argc,char **argv)
{
int f,i;
long l;
unsigned char v=0;
unsigned char name[64];
unsigned char mode[50];
int ty;
unsigned char *buf;
FILE *file;
unsigned char sbuf[SMALBUF];
int perc,curr;

if(argc!=3)
	{
	printf("\nUsage : %s -{hvsSc} rom_file\n\n",argv[0]);
	printf("-h : display info about the ROM\n");
	printf("-v : Verify checksum of the ROM\n");
	printf("-s : Swap little<->Big Endian format (fast but need mem)\n");
	printf("-S : Swap little<->Big Endian format (slow but need no mem)\n");
	printf("-c : Regenerate the checksum of the ROM\n\n");
	printf("---- n64rom %s by Salim Gasmi ---- \n\n",VER);
	return(0);
	}

if(strcmp(argv[1],"-h")==0) v=1;
if(strcmp(argv[1],"-v")==0) v=2;
if(strcmp(argv[1],"-s")==0) v=3;
if(strcmp(argv[1],"-c")==0) v=4;
if(strcmp(argv[1],"-S")==0) v=5;

if(v==0)
	{
	printf("Unknown switch option %s\n",argv[1]);
	return(0);
	}

f=open(argv[2],O_RDWR);
if(f<0)
	{
	printf("file %s not found or write protected\n",argv[2]);
	return(0);
	}

read(f,name,2);
lseek(f,0,0);
l=lseek(f,0,2);
lseek(f,0,0);
lseek(f,32,0);

ty=IsRom(name);

if(ty==-1) 
	{
	printf("%s is not a N64 ROM\n",argv[2]);
	close(f);
	return(0);
	}

read(f,name,63);

if(ty==1)
	{
	 Swap(name,63);
	 strcpy(mode,"Little Endian (V64)");
	}
	else strcpy(mode,"Big Endian (Z64)");

if(v==1)
	{
	printf("%s : %s %ld MB %s format\n",argv[2],name,l*8/MEGA,mode);
	close(f);
	return(0);
	}


if(v==3)
{
lseek(f,0,0);

/* we load the ROM in RAM */

buf=(unsigned char *)malloc(l);
if(buf==NULL)
        {
        close(f);
        printf("malloc failed\n");
        return(0);
        }


printf("reading && Swaping file %s (%ld bytes) (fast mode).... \n",argv[2],l);

read(f,buf,l);
lseek(f,0,0);
Swap(buf,l);
write(f,buf,l);
free(buf);
}

if(v==5)
{
ty=1;

printf("reading && Swaping file %s (%ld bytes) (slow mode).... \n",argv[2],l);


printf("%% done :");
fflush(stdout);
perc=l/10;
curr=0;

i=0;
while(i!=l)
{
lseek(f,i,0);
read(f,sbuf,SMALBUF);
Swap(sbuf,SMALBUF);
lseek(f,i,0);
write(f,sbuf,SMALBUF);

if(i/perc>curr)
	{
	curr=i/perc;
	printf("%d%% ",curr*10);
	fflush(stdout);
	}

i+=SMALBUF;
}
printf("100%%\n");
fflush(stdout);
}



if(v==2 || v==4)
{
file=fopen(argv[2],"r+b");
if(file==NULL)
	{
	printf("file %s not found or write protected\n",argv[1]);
	return(0);
	}
}

if(v==2)
{
if(ChkSum(file,ty,l,1)==0) printf("Checksum of %s is good\n",argv[2]);
else printf("Checksum of %s is wrong\n",argv[2]);
fclose(file);
}

if(v==4)
{
if(ChkSum(file,ty,l,0)==0) printf("Checksum of %s regenerated\n",argv[2]);
else printf("Error writing Checksum of %s\n",argv[2]);
fclose(file);
}


close(f);
return(0);
}


int IsRom(unsigned char *a)
{
if(a[0]==B1 && a[1]==B2) return(0);
if(a[0]==B2 && a[1]==B1) return(1);
return(-1);
}


void Swap(unsigned char *buffer,int l)
{
int i;
unsigned char p;

for(i=0;i<l-1;i=i+2)
        {
        p=buffer[i];
        buffer[i]=buffer[i+1];
        buffer[i+1]=p;
        }
}


int ChkSum(FILE *file,int swapped,long flen,int readonly)
{
    unsigned char *buffer;
    unsigned long sum1, sum2;
    unsigned long i;
    unsigned long c1, k1, k2;
    unsigned long t1, t2, t3, t4;
    unsigned long t5, t6;
    unsigned int n;
    long clen = CHECKSUM_LENGTH;
    long rlen = flen - CHECKSUM_START;
    int wrong,jj,ret;

    buffer=(unsigned char *)malloc(BUFSIZE);
    if(buffer==NULL) return(-99);


    t1 = CHECKSUM_STARTVALUE;
    t2 = CHECKSUM_STARTVALUE;
    t3 = CHECKSUM_STARTVALUE;
    t4 = CHECKSUM_STARTVALUE;
    t5 = CHECKSUM_STARTVALUE;
    t6 = CHECKSUM_STARTVALUE;

fseek(file, CHECKSUM_START, SEEK_SET);

    for( ;; ) {
      if( rlen > 0 ) {
        n = fread(buffer, 1, min2(BUFSIZE, clen), file);
        if( (n & 0x03) != 0 ) {
          n += fread(buffer+n, 1, 4-(n&3), file);
        }
      } else {
        n = min2(BUFSIZE, clen);
      }
      if( (n == 0) || ((n&3) != 0) ) {
        if( (clen != 0) || (n != 0) ) {
	  if(readonly==1) 
		{
		free(buffer);
		return(-1);
		}
        }
        break;
      }
      for( i=0; i<n; i+=4 ) {
        c1 = BYTES2LONG(&buffer[i], swapped);
        k1 = t6 + c1;
        if( k1 < t6 ) t4++;
        t6 = k1;
        t3 ^= c1;
        k2 = c1 & 0x1f;
        k1 = ROL(c1, k2);
        t5 += k1;
        if( c1 < t2 ) {
          t2 ^= k1;
        } else {
          t2 ^= t6 ^ c1;
        }
        t1 += c1 ^ t5;
      }
      if( rlen > 0 ) {
        rlen -= n;
        if( rlen <= 0 ) memset(buffer, 0, BUFSIZE);
      }
      clen -= n;
    }
    sum1 = t6 ^ t4 ^ t3;
    sum2 = t5 ^ t2 ^ t1;

  LONG2BYTES(sum1, &buffer[0], 0);
  LONG2BYTES(sum2, &buffer[4], 0);
  fseek(file, CHECKSUM_HEADERPOS, SEEK_SET);
  fread(buffer+8, 1, 8, file);

  wrong=0;

  for(jj=0;jj<=7;jj++)
	if(buffer[(jj+8)^swapped]!=buffer[jj]) wrong=-1;

	if(readonly==0) 
	{
    		fseek(file, CHECKSUM_HEADERPOS, SEEK_SET);
    		LONG2BYTES(sum1, &buffer[16], swapped);
    		LONG2BYTES(sum2, &buffer[20], swapped);
    		if( fwrite(buffer+16, 1, 8, file) != 8 ) ret=-1; else ret=0;
	}
	else ret=wrong;

free(buffer);
return(ret);
}

