www.pudn.com > virtual_gameboy-0.8.8.zip > GB.C
/** 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,1996 **/ /** Marcel de Kogel 1996 **/ /** You are not allowed to distribute this software **/ /** commercially. Please, notify me, if you make any **/ /** changes to this file. **/ /*************************************************************/ #include "GB.h" #include#include #include #include byte Verbose = 1; /* Verboseness level */ byte *RAM,*Page[8]; /* RAM and pointers to Z80 address space 8x8kB) */ int VPeriod = 69905; /* Number of Z80 cycles between VBlanks */ byte UPeriod = 1; /* Number of VBlanks per screen update */ byte LineDelay = 1; /* When 1, screen refreshing is delayed */ byte CheckCRC = 1; /* When 1, VGB checks cartridge CRC on loading */ char *SaveName = NULL; /* .sav file name */ char *SndName = NULL; /* Soundtrack log file */ FILE *SndStream = NULL; struct /* Cheat list to be filled before StartGB() */ { /* is called */ byte Value,Orig; word Address; } Cheats[MAXCHEAT]; int CheatCount = 0; /* Number of cheats in the list */ byte SIOBuf; /* Next byte to output via the serial line */ byte SIOFlag; /* Bit0 -> SIO interrupt should be generated */ /* Bit1 -> Data should be sent out */ byte IMask; /* A mask to reset an event bit in IFLAGS */ 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 AutoA = 0; /* When 1, autofire emulation for button A */ byte AutoB = 0; /* When 1, autofire emulation for button B */ unsigned TCount,TStep; /* Timer counter and increment */ unsigned SIOCount; /* Serial I/O counter */ byte BPal[4]; /* Background palette */ byte SPal0[4],SPal1[4]; /* Sprite palettes */ byte *ChrGen; /* Character generator */ byte *BgdTab,*WndTab; /* Background and window character tables */ int IFreq=60; /* Interrupt Frequency in Hertz */ static unsigned NumInts=0; static void WriteIntStamp (void) { if (NumInts>65535) { fputc (0xFF,SndStream); fputc (NumInts,SndStream); fputc (NumInts>>8,SndStream); fputc (NumInts>>16,SndStream); fputc (NumInts>>24,SndStream); } else { if (NumInts>255) { fputc (0xFE,SndStream); fputc (NumInts,SndStream); fputc (NumInts>>8,SndStream); } else { fputc (0xFD,SndStream); fputc (NumInts,SndStream); } } NumInts=0; } static void _Sound (byte Reg, byte Value) { if(SndStream) { WriteIntStamp();fputc(Reg,SndStream);fputc(Value,SndStream); } Sound (Reg,Value); } static void WriteROM (unsigned A,byte V) __attribute__ ((regparm(3))); static void WriteCartRAM (unsigned A,byte V) __attribute__ ((regparm(3))); static void WriteRAM (unsigned A,byte V) __attribute__ ((regparm(3))); static void WROMBank (unsigned A,byte V) __attribute__ ((regparm(3))); static void WRAMBank (unsigned A,byte V) __attribute__ ((regparm(3))); static void WriteIO (unsigned A,byte V) __attribute__ ((regparm(3))); static void WriteCartRAM (unsigned A,byte V) { Page[A>>13][A&0x1FFF]=V; } static void WriteRAM (unsigned A,byte V) { RAM[A]=V; } static void WROMBank (unsigned A,byte V) { if(MBCType&&((A&0xFF00)!=0x2100)) return; V&=ROMMask; if(!V) V=1; if(ROMMask&&(V!=ROMBank)) { ROMBank=V; Page[2]=ROMMap[V]? ROMMap[V]:RAM+0x4000; Page[3]=Page[2]+0x2000; if(Verbose&0x08) printf("ROM: Bank %d selected\n",V); } } static void WriteROM (unsigned A,byte V) { if(Verbose&0x02) printf("Wrote %Xh to ROM at %Xh\n",V,A); } static void WRAMBank (unsigned A,byte V) { V&=RAMMask; if(RAMMask&&!MBCType&&(RAMBank!=V)) { RAMBank=V; Page[5]=RAMMap[V]? RAMMap[V]:RAM+0xA000; if(Verbose&0x08) printf("RAM: Bank %d selected\n",V); } } static void WriteIO(unsigned A,byte V) { static unsigned long TPFreqs[] = { 4096,262144,65536,16384 }; byte *P; switch(A) { case 0xFF00: JOYPAD=0xCF|V; if(!(V&0x20)) JOYPAD&=(JoyState>>4)|0xF0; if(!(V&0x10)) JOYPAD&=JoyState|0xF0; return; case 0xFF01: SIOBuf=V;SIOFlag|=0x02;return; case 0xFF02: SIOCount=0; if(V&0x80) { if ((V&1)==0) /* external clock */ { if(SIOFlag&0x02? SIOSend(SIOBuf):SIOReceive(&SIODATA)) SIOCount=8; } else /* internal clock */ SIOCount=8; SIOFlag=(SIOFlag&0xFD); } V|=0x7E; break; case 0xFF07: TStep=(TPFreqs[V&0x03]<<16)/(154*IFreq); V|=0xF8;break; case 0xFF0F: V&=0x1F;break; case 0xFFFF: V&=0x1F;break; case 0xFF46: P=RAM+0xFE00;A=(word)V<<8; for(V=0;V<0xA0;V++) *P++=M_RDMEM(A++); 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; default: if((A>=0xFF10)&&(A<=0xFF26)) _Sound(A-0xFF10,V); } RAM[A]=V; } WriteMemFn WriteMemFnTable[256]= { /* 0000-1FFF ROM */ WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, /* 2000-3FFF ROM Bank Select */ WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank, WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank, WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank, WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank,WROMBank, /* 4000-5FFF RAM Bank Select */ WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank, WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank, WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank, WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank,WRAMBank, /* 6000-7FFF ROM */ WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM,WriteROM, /* 8000-9FFF RAM */ WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, /* A000-BFFF Cartridge RAM */ WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM, WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM, WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM, WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM,WriteCartRAM, /* C000-FEFF RAM */ WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM,WriteRAM, /* FF00-FFFF I/O */ WriteIO }; void M_WRMEM (unsigned A,byte V) { (*WriteMemFnTable[A>>8])(A,V); } byte M_RDMEM(unsigned A) { return (Page[A>>13][A&0x1FFF]); } byte M_RDMEM_OPCODE (void) { byte V; V=M_RDMEM (R.PC.D); R.PC.W++; return V; } void ResetGB (void) { int I; if(RAMMap[0]) Page[5]=RAMMap[0]; if(ROMMap[1]) Page[2]=ROMMap[1]; Page[3]=Page[2]+0x2000; TStep=32768;TCount=0; ChrGen=RAM+0x8800;BgdTab=WndTab=RAM+0x9800; LCDCONT=0x81;LCDSTAT=0x00; CURLINE=0x00;CMPLINE=0xFF; IFLAGS=ISWITCH=0x00; TIMECNT=TIMEMOD=0x01; TIMEFRQ=0xF8; SIODATA=0x00; SIOCONT=0x7E; SIOBuf=SIOFlag=0x00; JoyState=0xFF; for(I=0;I<4;I++) SPal0[I]=SPal1[I]=BPal[I]=I; BGRDPAL=SPR0PAL=SPR1PAL=0xE4; /* Initialise the timer */ M_WRMEM (0xFF07,RAM[0xFF07]); ResetZ80(&R); } static int InitMachineDone=0; static word Exit_PC; int StartGB(char *CartName) { static char *CartTypes[] = { "ROM ONLY","ROM+MBC1","ROM+MBC1+RAM", "ROM+MBC1+RAM+BATTERY","UNKNOWN", "ROM+MBC2","ROM+MBC2+BATTERY" }; /*** Following are some known manufacturer codes *************************/ 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?" }, { 0x4901,"Irem" },{ 0x9C01,"Imagineer" },{ 0xA600,"Kawada?" }, { 0xB101,"Nexoft" },{ 0x5101,"Acclaim" },{ 0x6001,"Titus" }, { 0xB601,"HAL" },{ 0x3300,"Nintendo" },{ 0x0B00,"Coconuts?" }, { 0x5401,"Gametek" },{ 0x7F01,"Kemco?" },{ 0xC001,"Taito" }, { 0xEB01,"Atlus" },{ 0xE800,"Asmik?" },{ 0xDA00,"Tomy?" }, { 0xB100,"ASCII?" },{ 0xEB00,"Atlus" },{ 0xC000,"Taito" }, { 0x9C00,"Imagineer" },{ 0xC201,"Kemco?" },{ 0xD101,"Sofel?" }, { 0x6101,"Virgin" },{ 0xBB00,"SunSoft" },{ 0xCE01,"FCI?" }, { 0xB400,"Enix?" },{ 0xBD01,"Imagesoft" },{ 0x0A01,"Jaleco?" }, { 0xDF00,"Altron?" },{ 0xA700,"Takara?" },{ 0xEE00,"IGS?" }, { 0x8300,"Lozc?" },{ 0x5001,"Absolute?" },{ 0xDD00,"NCS?" }, { 0xE500,"Epoch?" },{ 0xCB00,"VAP?" },{ 0x8C00,"Vic Tokai" }, { 0xC200,"Kemco?" },{ 0xBF00,"Sammy?" }, { 0x1800,"Hudson Soft" },{ 0xCA01,"Palcom/Ultra" }, { 0xCA00,"Palcom/Ultra" },{ 0xC500,"Data East?" }, { 0xA900,"Technos Japan?" },{ 0xD900,"Banpresto?" }, { 0x7201,"Broderbund?" },{ 0x7A01,"Triffix Entertainment?" }, { 0xE100,"Towachiki?" },{ 0x9300,"Tsuburava?" }, { 0xC600,"Tonkin House?" },{ 0xCE00,"Pony Canyon" }, { 0x7001,"Infogrames?" },{ 0x8B01,"Bullet-Proof Software?" }, { 0x5501,"Park Place?" },{ 0xEA00,"King Records?" }, { 0x5D01,"Tradewest?" },{ 0x6F01,"ElectroBrain?" }, { 0xAA01,"Broderbund?" },{ 0xC301,"SquareSoft" }, { 0x5201,"Activision?" },{ 0x5A01,"Bitmap Brothers/Mindscape" }, { 0x5301,"American Sammy" },{ 0x4701,"Spectrum Holobyte" }, { 0x1801,"Hudson Soft"},{ 0x0000,NULL } }; int Checksum,I,J,*T; FILE *F; char *P,S[50]; word A; int rambanks[4]= { 0,1,1,4 }; /*** 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; if(Verbose) printf("Allocating 64kB for address space..."); if(!(RAM=malloc(0x10000))) { if(Verbose) puts("FAILED");return(0); } memset(RAM,NORAM,0x10000); for(I=0;I<8;I++) Page[I]=RAM+I*0x2000; 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< 3; P=NULL; if((RAM[0x0147]==4)||(RAM[0x0147]>6)) P="Unknown ROM type"; if(P) { printf("\nError loading cartridge: %s\n",P); fclose(F);return(0); } if(Verbose) { strncpy(S,RAM+0x0134,16);S[16]='\0'; printf("OK\n Name: %s\n",S); printf(" Type: %s\n",CartTypes[RAM[0x0147]]); printf(" ROM Size: %dx16kB\n",ROMBanks); J=(RAM[0x0149]&0x03)*2;J=J? (1<<(J-1)):0; printf(" RAM Size: %dkB\n",J); 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 0) { if(Verbose) puts("Patching cheats into the ROM code:"); for(J=0;J %Xh\n",A,Cheats[J].Orig,Cheats[J].Value); if(A<0x4000) { if(ROMMap[0][A]==Cheats[J].Orig) ROMMap[0][A]=Cheats[J].Value; } else for(I=0,A-=0x4000;I >8,SndStream); fputc (I>>16,SndStream); fputc (I>>24,SndStream); for (I=4;I<64;++I) fputc (0,SndStream); } } if(ROMBanks<3) ROMMask=0; else { for(I=1;I >16); TCount&=0x0000FFFF; if(L&0xFFFFFF00) { TIMECNT=TIMEMOD; if(ISWITCH&TIM_IFLAG) { IFLAGS=IMask=TIM_IFLAG;return 0x0050; } } else TIMECNT=L; } return 0xFFFF; } default: /* Only LCD state had to be changed */ return(0xFFFF); } /* Checking for line coincidence and refreshing screen */ J=0; if(LineDelay) { if(dorefresh&&(CURLINE<144)) RefreshLine(CURLINE); CURLINE=(CURLINE+1)%154; if(CURLINE!=CMPLINE) LCDSTAT&=0xFB; else { LCDSTAT|=0x04;J|=0x40; } } else { if(CURLINE!=CMPLINE) LCDSTAT&=0xFB; else { LCDSTAT|=0x04;J|=0x40; } CURLINE=(CURLINE+1)%154; if(dorefresh&&(CURLINE<144)) RefreshLine(CURLINE); } /* If end of frame reached... */ if(CURLINE==144) { /* Set VBlank state */ LCDSTAT=(LCDSTAT&0xFC)|0x01;J|=0x10; /* Update joystick */ JoyState=Joystick(); /* Autofire emulation */ ACount=(ACount+1)&0x07; if(ACount>3) { if(AutoA) JoyState|=0x10; if(AutoB) JoyState|=0x20; } /* Assign value to JOYPAD */ I=JOYPAD|0xCF; if(!(I&0x10)) JOYPAD=I&(JoyState|0xF0); if(!(I&0x20)) JOYPAD=I&((JoyState>>4)|0xF0); /* Refresh screen if needed */ if (dorefresh) RefreshScreen(); /* Sync the GB emulation */ switch (MachineInterrupt ()) { case 0: if (UPeriod==0) { dorefresh=0; break; } case 1: if (UPeriod==0) { dorefresh=1; break; } default: if (!--UCount) { UCount=UPeriod; dorefresh=1; } else dorefresh=0; break; } } /* Generating LCD controller interrupt */ if((J&LCDSTAT)&&(ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80)) IFLAGS|=LCD_IFLAG; /* Determining interrupt address */ if(IFLAGS&EXT_IFLAG) { IMask=EXT_IFLAG;return(0x0060); } if(IFLAGS&SIO_IFLAG) { IMask=SIO_IFLAG;return(0x0058); } if(IFLAGS&TIM_IFLAG) { IMask=TIM_IFLAG;return(0x0050); } if(IFLAGS&LCD_IFLAG) { IMask=LCD_IFLAG;return(0x0048); } if(IFLAGS&VBL_IFLAG) { IMask=VBL_IFLAG;return(0x0040); } /* No interrupt */ return(0xFFFF); } int AddCheat(char *Cheat) { static char Digits[]="0123456789ABCDEF"; int X1,X2; 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[4]))-Digits; X2=strchr(Digits,toupper(Cheat[5]))-Digits; if((X1<0)||(X2<0)) return(0); else Cheats[CheatCount].Address=X1*16+X2; X1=strchr(Digits,toupper(Cheat[6]))-Digits; X2=strchr(Digits,toupper(Cheat[2]))-Digits; if((X1<0)||(X2<0)) return(0); else Cheats[CheatCount].Address+=256*((15-X1)*16+X2); X1=strchr(Digits,toupper(Cheat[10]))-Digits; X2=strchr(Digits,toupper(Cheat[8]))-Digits; if((X1<0)||(X2<0)) return(0); X1=~(16*X2+X1); X1=((X1>>2)&0x3F)|((X1<<6)&0xC0); Cheats[CheatCount].Orig=X1^0x45; if(Cheats[CheatCount].Address>=0x8000) return(0); CheatCount++;return(1); } }