/** VGB: portable GameBoy emulator ***************************/
/**                                                         **/
/**                           GB.c                          **/
/**                                                         **/
/** This file contains the portable part of the GameBoy     **/
/** hardware emulation. See GB.h for #defines related to    **/
/** drivers and GameBoy hardware.                           **/
/**                                                         **/
/** Copyright (C) Marat Fayzullin 1995                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/   
/**     changes to this file.                               **/
/*************************************************************/

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

#include "GB.h" 

byte Verbose    = 1;     /* Verboseness level                            */

byte *RAM=NULL;          /* Pointer to Z80 address space (64kB)          */

int  VPeriod    = 6000;  /* Number of Z80 cycles between VBlanks         */
int  UPeriod    = 1;     /* Number of VBlanks per screen update          */
int  TPeriod;            /* Number of Z80 cycles per screen scanline     */
byte LineDelay  = 1;     /* When 1, CMPLINE interrupts are delayed       */

char *SaveName  = NULL;  /* .sav file name                               */

struct                   /* Cheat list to be filled before StartGB()     */
{                        /* is called                                    */
  byte Value,Bank;
  word Address;
} Cheats[MAXCHEAT];
int CheatCount  = 0;     /* Number of cheats in the list                 */

FILE *SerialOut;         /* Output stream emulating the serial line      */
FILE *SerialIn;          /* Input stream emulating the serial line       */
byte SerialBuf;          /* Next byte to output via the serial line      */

byte MBCType;            /* MBC type: 1 for MBC2, 0 for MBC1             */
byte *ROMMap[256];       /* Addresses of ROM banks                       */
byte ROMBank;            /* Number of ROM bank currently used            */
byte ROMMask;            /* Mask for the ROM bank number                 */
int  ROMBanks;           /* Total number of ROM banks                    */
byte *RAMMap[256];       /* Addresses of RAM banks                       */ 
byte RAMBank;            /* Number of RAM bank currently used            */
byte RAMMask;            /* Mask for the RAM bank number                 */
int  RAMBanks;           /* Total number of RAM banks                    */

byte JoyState   = 0xFF;  /* Joystick state: START.SELECT.B.A.D.U.L.R     */

byte BPal[4];            /* Background palette                           */
byte SPal0[4],SPal1[4];  /* Sprite palettes                              */

byte *ChrGen;            /* Character generator                          */
byte *BgdTab,*WndTab;    /* Background and window character tables       */

byte SprFlag    = 0;     /* <>0: sprites were enabled during the frame   */

void DoWrite(word A,byte V);



void M_WRMEM(word A,byte V)
{
  switch(A&0xE000)
  {
    case 0x0000:
    case 0x6000:
      if(Verbose&0x02) printf("Wrote %Xh to ROM at %Xh\n",V,A);
      return;
    case 0x2000:
      if(MBCType&&((A&0xFF00)!=0x2100)) return;
      V&=ROMMask;
      if(ROMBank!=V)
      {
        ROMBank=V;
        if(ROMMap[V]) memcpy(RAM+0x4000,ROMMap[V],0x4000);
        else          memset(RAM+0x4000,NORAM,0x4000);
        if(Verbose&0x08) printf("ROM: Bank %d selected\n",V);
      }
      return;
    case 0x4000:
      V&=RAMMask;
      if(!MBCType&&(RAMBank!=V))
      {
        if(RAMMap[RAMBank]) memcpy(RAMMap[RAMBank],RAM+0xA000,0x2000);
        RAMBank=V;
        if(RAMMap[V]) memcpy(RAM+0xA000,RAMMap[V],0x2000);
        else          memset(RAM+0xA000,NORAM,0x2000);
        if(Verbose&0x08) printf("RAM: Bank %d selected\n",V);
      }
      return;
    case 0x8000:
    case 0xA000:
    case 0xC000:
      RAM[A]=V;return;
    case 0xE000:
      DoWrite(A,V);return;
  }
}

void DoWrite(word A,byte V)
{
  static byte TPShifts[] = { 6,0,2,4 };

  switch(A)
  {
    case 0xFF00: JOYPAD=0xCF|V;
                 if(!(V&0x20)) JOYPAD&=(JoyState>>4)|0xF0;
                 if(!(V&0x10)) JOYPAD&=JoyState|0xF0; 
                 return;
    case 0xFF01: SerialBuf=V;return;
    case 0xFF02: if(V&0x80)
                 { 
                   IFLAGS|=0x08;
                   if(SerialOut) fputc(SerialBuf,SerialOut);
                 }
                 V|=0x7E;break;
    case 0xFF05: V=0;break;
    case 0xFF07: TPeriod=1<<TPShifts[V&0x03];
                 V|=0xF8;break;
    case 0xFF0F: V|=0xE0;break;
    case 0xFFFF: V|=0xE0;break;
    case 0xFF46: memcpy(RAM+0xFE00,RAM+((word)V<<8),0xA0);
                 return;
    case 0xFF41: V=(V&0xF8)|(LCDSTAT&0x07);
                 break;
    case 0xFF40: ChrGen=RAM+(V&0x10? 0x8000:0x8800);
                 BgdTab=RAM+(V&0x08? 0x9C00:0x9800);
                 WndTab=RAM+(V&0x40? 0x9C00:0x9800);
                 break;
    case 0xFF44: V=0;break;
    case 0xFF47: BPal[0]=V&0x03;
                 BPal[1]=(V&0x0C)>>2;
                 BPal[2]=(V&0x30)>>4;
                 BPal[3]=(V&0xC0)>>6;
                 break;
    case 0xFF48: SPal0[0]=V&0x03;
                 SPal0[1]=(V&0x0C)>>2;
                 SPal0[2]=(V&0x30)>>4;
                 SPal0[3]=(V&0xC0)>>6;
                 break;
    case 0xFF49: SPal1[0]=V&0x03;
                 SPal1[1]=(V&0x0C)>>2;
                 SPal1[2]=(V&0x30)>>4;
                 SPal1[3]=(V&0xC0)>>6;
                 break;
  }
  RAM[A]=V;
}

int StartGB(char *CartName)
{
  static char *CartTypes[] =
  {
    "ROM ONLY","ROM+MBC1","ROM+MBC1+RAM",
    "ROM+MBC1+RAM+BATTERY","UNKNOWN",
    "ROM+MBC2","ROM+MBC2+BATTERY"
  };

  static struct { word Code;char *Name; } Companies[] =
  {
    { 0x3301,"Nintendo"  },{ 0x7901,"Accolade"  },{ 0xA400,"Konami"    },
    { 0x6701,"Ocean"     },{ 0x5601,"LJN"       },{ 0x9900,"ARC?"      },
    { 0x0101,"Nintendo"  },{ 0x0801,"Capcom"    },{ 0x0100,"Nintendo"  },
    { 0xBB01,"SunSoft"   },{ 0xA401,"Konami"    },{ 0xAF01,"Namcot?"   },
    { 0xCA01,"Palcom?"   },{ 0xB601,"HAL?"      },{ 0x9C01,"Imagineer" },
    { 0xB101,"NexSoft?"  },{ 0x5101,"Acclaim?"  },{ 0x6001,"Titus?"    },
    { 0xB601,"HAL?"      },{ 0xCA01,"Palcom?"   },{ 0x3300,"Nintendo?" },
    { 0x5401,"Gametek?"  },{ 0x7F01,"Kemco?"    },{ 0x4901,"Irem?"     },
    { 0x7001,"Infogrames?"    },{ 0x8B01,"Bullet-Proof Software?" },
    { 0x6101,"Virgin Games"   },{ 0x5501,"Park Place?" },
    { 0x5D01,"Tradewest?"     },{ 0x6F01,"ElectroBrain?" },
    { 0xAA01,"Broderbund?"    },{ 0xC301,"SquareSoft?" },
    { 0x5201,"Activision?"    },{ 0x5A01,"Bitmap Brothers/Mindscape" },
    { 0x5301,"American Sammy" },{ 0x1801,"Hudson Soft"},{ 0x0000,NULL }
  };

  int Checksum,I,J,*T;
  FILE *F;
  char *P;
  word A;
  reg R;

  /*** STARTUP CODE starts here: ***/
  T=(int *)"\01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
#ifdef LSB_FIRST
  if(*T!=1)
  {
    puts("********** This machine is high-endian. *********");
    puts("Take #define LSB_FIRST out and compile VGB again.");
    return(0);
  }
#else
  if(*T==1)
  {
    puts("********* This machine is low-endian. *********");
    puts("Insert #define LSB_FIRST and compile VGB again.");
    return(0);
  }
#endif

  for(I=0;I<256;I++) RAMMap[I]=ROMMap[I]=NULL;

  SerialIn=stdin;SerialOut=NULL;

  if(Verbose) printf("Allocating 64kB for address space...");
  if(!(RAM=malloc(0x10000)))
  { if(Verbose) puts("FAILED");return(0); }
  memset(RAM,0,0x10000);

  if(Verbose) printf("OK\nOpening %s...",CartName);
  if(!(F=fopen(CartName,"rb")))
  { if(Verbose) puts("FAILED");return(0); }

  if(Verbose) printf("reading...");
  if(fread(RAM,1,0x4000,F)!=0x4000)
  { if(Verbose) puts("FAILED");return(0); }

  ROMMap[0]=RAM;
  ROMBanks=2<<RAM[0x0148];
  RAMBanks=(1<<RAM[0x0149]*2)>>1;
  Checksum=((word)RAM[0x014E]<<8)+RAM[0x014F];
  MBCType=RAM[0x0147]>3;

  P=NULL;
  if((RAM[0x0147]==4)||(RAM[0x0147]>6)) P="Unknown ROM type";
  if(RAM[0x0100]||(RAM[0x0101]!=0xC3))  P="Invalid cartridge ROM";
  if(P)
  {
    printf("\nError loading cartridge: %s\n",P);
    fclose(F);return(0);
  }

  if(Verbose)
  {
    printf("OK\n  Name: %s\n",RAM+0x0134);
    printf("  Type: %s\n",CartTypes[RAM[0x0147]]);
    printf("  ROM Size: %dx16kB\n",ROMBanks);
    printf("  RAM Size: %dx8kB\n",RAMBanks);

    J=((word)RAM[0x014B]<<8)+RAM[0x014A];
    for(I=0,P=NULL;!P&&Companies[I].Name;I++)
      if(J==Companies[I].Code) P=Companies[I].Name;
    printf("  Manufacturer ID: %Xh",J);
    printf(" [%s]\n",P? P:"?");

    printf("  Version Number: %Xh\n",RAM[0x014C]);
    printf("  Complement Check: %Xh\n",RAM[0x014D]);
    printf("  Checksum: %Xh\n",Checksum);
    J=((word)RAM[0x0103]<<8)+RAM[0x0102];
    printf("  Start Address: %Xh\n",J);
  }

  Checksum+=RAM[0x014E]+RAM[0x014F];
  for(I=0;I<0x4000;I++) Checksum-=RAM[I];

  if(Verbose) printf("Loading %dx16kB ROM banks:\n.",ROMBanks);
  for(I=1;I<ROMBanks;I++)
    if(ROMMap[I]=malloc(0x4000))
      if(fread(ROMMap[I],1,0x4000,F)==0x4000)
      {
        for(J=0;J<0x4000;J++) Checksum-=ROMMap[I][J];
        if(Verbose) putchar('.');
      }
      else { if(Verbose) puts("READ FAILED");break; }
    else { if(Verbose) puts("MALLOC FAILED");break; }

  fclose(F);if(I<ROMBanks) return(0);

  if(Checksum&0xFFFF)
	{
		printf("\nError loading cartridge: Checksum is wrong\n\
(Should be $0000 - is $%lx !)\nStart anyway (y/n)?",(Checksum&0xffff));
	if(getchar() != 'y')	return(0);
	}

  if(RAMBanks&&Verbose)
    printf("OK\nAllocating %dx8kB RAM banks...",RAMBanks);
  for(I=0;I<RAMBanks;I++)
    if(RAMMap[I]=malloc(0x2000))
      memset(RAMMap[I],0,0x2000);
    else
    { if(Verbose) puts("FAILED");return(0); }

  puts("OK");

  if(((RAM[0x0147]==3)||(RAM[0x0147]==6))&&RAMMap[0])
    if(SaveName=malloc(strlen(CartName)+10))
    {
      strcpy(SaveName,CartName);
      if(P=strrchr(SaveName,'.')) strcpy(P,".sav");
      else strcat(SaveName,".sav");
      if(Verbose) printf("Opening %s...",SaveName);
      if(F=fopen(SaveName,"rb"))
      {
        if(Verbose) printf("reading...");
        J=(fread(RAMMap[0],1,0x2000,F)==0x2000);
        if(Verbose) puts(J? "OK":"FAILED");
        fclose(F);
      }
      else if(Verbose) puts("FAILED");
    }

  if(CheatCount>0)
  {
    if(Verbose) puts("Patching cheats into the ROM code:");
    for(J=0;J<CheatCount;J++)
    {
      I=Cheats[J].Bank;A=Cheats[J].Address;
      printf("  %d:%Xh = %Xh - ",I,A,Cheats[J].Value);
      if(I<ROMBanks)
        if(ROMMap[I])
        { *(ROMMap[I]+A)=Cheats[J].Value;P="OK"; }
        else P="FAILED. No ROM in this bank.";
      else P="FAILED. Invalid bank number.";
      puts(P);
    }
  }

  for(ROMMask=0x01,I=ROMBanks;I;ROMMask<<=1,I>>=1);ROMMask--;ROMBank=1;
  for(RAMMask=0x01,I=RAMBanks;I;RAMMask<<=1,I>>=1);RAMMask--;RAMBank=0;
    
  if(RAMMap[0]) memcpy(RAM+0xA000,RAMMap[0],0x2000);
  if(ROMMap[1]) memcpy(RAM+0x4000,ROMMap[1],0x4000);

  IPeriod=VPeriod/154;TPeriod=64;

  ChrGen=RAM+0x8800;BgdTab=WndTab=RAM+0x9800;
  LCDCONT=0x81;LCDSTAT=0x00;
  CURLINE=0x00;CMPLINE=0xFF;
  IFLAGS=ISWITCH=0xE0;
  TIMECNT=TIMEMOD=0x00;
  TIMEFRQ=0xF8;
  SIOCONT=0x00;

  for(I=0;I<4;I++) SPal0[I]=SPal1[I]=BPal[I]=I;
  BGRDPAL=SPR0PAL=SPR1PAL=0xE4;

  R.PC.W=0x0100;R.SP.W=0xF000;R.IFF=0;
  if(Verbose) printf("RUNNING ROM CODE...\n");
  A=Z80(RAM,R);

  if(Verbose) printf("EXITED at PC = %Xh.\n",A); 
  return(1);
}

void TrashGB(void)
{
  FILE *F;
  int I;

  if(SaveName&&RAMMap[0])   
  {
    if(Verbose) printf("\nOpening %s...",SaveName);   
    if(F=fopen(SaveName,"wb"))
    { 
      if(Verbose) printf("writing...");
      I=(fwrite(RAMBank? RAMMap[0]:(RAM+0xA000),1,0x2000,F)==0x2000);
      if(Verbose) puts(I? "OK":"FAILED");
      fclose(F);
    }
    else if(Verbose) puts("FAILED");
  }

  if(SaveName) free(SaveName);
  if(RAM) free(RAM);
  for(I=1;ROMMap[I];I++) free(ROMMap[I]);
  for(I=0;RAMMap[I];I++) free(RAMMap[I]);
}

word Interrupt(void)
{
  static int TCount=1;
  static int UCount=1;
  register word A;
  register byte J;

  A=0xFFFF;DIVREG++;
  SprFlag|=LCDCONT&0x02;
  J=(0x08<<(LCDSTAT&0x03))&0x3F;

  if(LineDelay)
  {
    if(!UCount&&(CURLINE<144)) RefreshLine(CURLINE);
    CURLINE=(CURLINE>=153)? 0:CURLINE+1;
    if(CURLINE==CMPLINE) { LCDSTAT|=0x04;J|=0x40; }
  }
  else
  {
    if(CURLINE==CMPLINE) { LCDSTAT|=0x04;J|=0x40; }
    CURLINE=(CURLINE>=153)? 0:CURLINE+1;
    if(!UCount&&(CURLINE<144)) RefreshLine(CURLINE);
  }

  /* If end of frame reached... */
  if(CURLINE==145)
  {
    if(!UCount)
    {
      if((LCDCONT&0x80)&&SprFlag) RefreshSprites();
      SprFlag=0;UCount=UPeriod;RefreshScreen();
    }
    else UCount--;

    /* Generating VBlank interrupt */
    if((ISWITCH&VBL_IFLAG)&&(LCDCONT&0x80))
    { IFLAGS|=VBL_IFLAG;A=0x0040; }
  }

  /* Generating LCD controller interrupt */
  if((J&LCDSTAT)&&(ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80))
  { IFLAGS|=LCD_IFLAG;A=0x0048; }

  /* Generating timer interrupt */
  if(TIMEFRQ&0x04)
    if(!--TCount)
    {
      TCount=TPeriod;
      if(!++TIMECNT)
      {
        if(ISWITCH&TIM_IFLAG)
        { IFLAGS|=TIM_IFLAG;A=0x0050; }
        TIMECNT=TIMEMOD;
      }
    }

  /* Generating serial IO interrupt */
  if(0/*SIOCONT&0x80*/)
  {
    SIOCONT&=0x7F;
    if(ISWITCH&SIO_IFLAG)
    { IFLAGS|=SIO_IFLAG;A=0x0058; }
  }

  if(CURLINE==144)
  {
    IFLAGS=0x00;

    /* Updating joystick */
    J=0xCF|JOYPAD;JoyState=Joystick();
    if(!(J&0x10)) J&=JoyState|0xF0;
    if(!(J&0x20)) J&=(JoyState>>4)|0xF0;

    /* Generating joystick interrupt */
    if((J^JOYPAD)&JOYPAD)
      if(ISWITCH&JOY_IFLAG)
      { IFLAGS|=JOY_IFLAG;A=0x0060; }

    JOYPAD=J;
  }

  return(A); 
} 

int AddCheat(char *Cheat)
{
  static char Digits[]="0123456789ABCDEF";
  int X1,X2;

  if(Verbose){ puts("Cheats are not supported yet!"); return(1);}

  if(CheatCount>=MAXCHEAT) return(0);
  else
  {
    if((Cheat[3]!='-')||(Cheat[7]!='-')) return(0);
    X1=strchr(Digits,toupper(Cheat[0]))-Digits;
    X2=strchr(Digits,toupper(Cheat[1]))-Digits;
    if((X1<0)||(X2<0)) return(0);
    else Cheats[CheatCount].Value=X1*16+X2;
    X1=strchr(Digits,toupper(Cheat[5]))-Digits;
    X2=strchr(Digits,toupper(Cheat[4]))-Digits;
    if((X1<0)||(X2<0)) return(0);
    else Cheats[CheatCount].Address=X1*16+X2;
    X1=strchr(Digits,toupper(Cheat[2]))-Digits;
    X2=strchr(Digits,toupper(Cheat[6]))-Digits;
    if((X1<0)||(X2<0)) return(0);
    else Cheats[CheatCount].Address+=256*(X1*16+X2);
    X1=strchr(Digits,toupper(Cheat[2]))-Digits;
    if(X1<0) return(0); else Cheats[CheatCount].Bank=X1;

    if(Cheats[CheatCount].Address<0x4000)
      Cheats[CheatCount].Bank=0; 
    else
      if(Cheats[CheatCount].Address<0x8000)
        Cheats[CheatCount].Address-=0x4000;
      else
        return(0);

    CheatCount++;return(1);
  }
}
