/** 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 **/ /** You are not allowed to distribute this software **/ /** commercially. Please, notify me, if you make any **/ /** changes to this file. **/ /*************************************************************/ #include "GB.h" #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> byte Verbose = 1; /* Verboseness level */ byte *RAM,*Page[8]; /* RAM and pointers to Z80 address space 8x8kB) */ int VPeriod = 6000; /* Number of Z80 cycles between VBlanks */ byte UPeriod = 1; /* Number of VBlanks per screen update */ byte LineDelay = 0; /* When 1, CMPLINE interrupts are delayed */ byte CheckCRC = 1; /* When 1, VGB checks cartridge CRC on loading */ byte AutoA = 0; /* When 1, autofire emulation for button A */ byte AutoB = 0; /* When 1, autofire emulation for button B */ 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 JoyState; /* Joystick state: START.SELECT.B.A.D.U.L.R */ 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 */ unsigned long TCount,TStep; /* Timer counter and increment */ 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: sprites were enabled during the frame */ /*** Following are some known manufacturer codes *************************/ 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 } }; void DoWrite(word A,byte V); byte M_RDMEM(register word A) { return(Page[A>>13][A&0x1FFF]); } void M_WRMEM(register word A,register byte V) { if(A>0xFEFF) DoWrite(A,V); else if(A>0x7FFF) Page[A>>13][A&0x1FFF]=V; else switch(A&0xE000) { case 0xC000: case 0x8000: RAM[A]=V;return; case 0xA000: Page[5][A&0x1FFF]=V;return; case 0xE000: DoWrite(A,V);return; case 0x2000: if(MBCType&&((A&0xFF00)!=0x2100)) return; V&=ROMMask; if(!V) V++; 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); } return; case 0x4000: 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); } return; case 0x0000: case 0x6000: if(Verbose&0x02) printf("Wrote %Xh to ROM at %Xh\n",V,A); return; } } void DoWrite(register word A,register byte V) { static unsigned long TPFreqs[] = { 4096,262144,65536,16384 }; register 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: if(V&0x80) { if(SIOFlag&0x02? SIOSend(SIOBuf):SIOReceive(&SIODATA)) SIOFlag|=0x01; SIOFlag=(SIOFlag&0xFD); } V|=0x7E; break; case 0xFF07: TStep=(TPFreqs[V&0x03]<<16)/(154*60); 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)) { if(SndStream) { fputc(A-0xFF10,SndStream);fputc(V,SndStream); } Sound(A-0xFF10,V); } } 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" }; int Checksum,I,J,*T; FILE *F; char *P,S[50]; 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 RAM=NULL;SaveName=NULL;SndStream=NULL; 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<<RAM[0x0148]; RAMBanks=(RAM[0x0149]&0x03)==3? 4:0; 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(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<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(CheckCRC&&(Checksum&0xFFFF)) { puts("\nError loading cartridge: Checksum is wrong");return(0); } if(Verbose) puts("OK"); if(RAMBanks&&!MBCType) { if(Verbose) printf("Allocating %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); } if(Verbose) puts("OK"); } if((RAM[0x0147]!=3)&&(RAM[0x0147]!=6)) SaveName=NULL; else 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=RAM[0x0147]==3? 0x2000:0x0200; J=(fread(RAMMap[0]? RAMMap[0]:Page[5],1,J,F)==J); 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++) { A=Cheats[J].Address; if(Verbose) printf(" at %Xh: %Xh -> %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<ROMBanks;I++) if(ROMMap[I][A]==Cheats[J].Orig) ROMMap[I][A]=Cheats[J].Value; } } if(SndName) { if(Verbose) printf("Logging soundtrack to %s...",SndName); SndStream=fopen(SndName,"wb"); if(Verbose) puts(SndStream? "OK":"FAILED"); if(SndStream) fprintf(SndStream,"GameBoy Soundtrack File 1.0\032"); } if(ROMBanks<3) ROMMask=0; else { for(I=1;I<ROMBanks;I<<=1);ROMMask=I-1;ROMBank=1; } if(!RAMMap[0]) RAMMask=0; else { for(I=1;I<RAMBanks;I<<=1);RAMMask=I-1;RAMBank=0; } if(RAMMap[0]) Page[5]=RAMMap[0]; if(ROMMap[1]) Page[2]=ROMMap[1]; Page[3]=Page[2]+0x2000; IPeriod=VPeriod/154/11; 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; SprFlag=0; JoyState=0xFF; for(I=0;I<4;I++) SPal0[I]=SPal1[I]=BPal[I]=I; BGRDPAL=SPR0PAL=SPR1PAL=0xE4; if(Verbose) printf("RUNNING ROM CODE...\n"); ResetZ80(&R);A=Z80(R); if(Verbose) printf("EXITED at PC = %04Xh.\n",A); return(1); } void TrashGB(void) { FILE *F; int I; if(SaveName) { if(Verbose) printf("\nOpening %s...",SaveName); if(F=fopen(SaveName,"wb")) { if(Verbose) printf("writing..."); I=RAM[0x0147]==3? 0x2000:0x0200; I=(fwrite(RAMMap[0]? RAMMap[0]:Page[5],1,I,F)==I); if(Verbose) puts(I? "OK":"FAILED"); fclose(F); } else if(Verbose) puts("FAILED"); free(SaveName); } if(RAM) free(RAM); for(I=1;ROMMap[I];I++) free(ROMMap[I]); for(I=0;RAMMap[I];I++) free(RAMMap[I]); if(SndName&&SndStream) fclose(SndStream); } word Interrupt(void) { static byte LCDStates[11] = { 2,2,3,3,3,3,0,0,0,0,0 }; static byte LCount=0; static byte UCount=1; static byte ACount=0; register byte J,I; DIVREG++;LCount=(LCount+1)%11; if(CURLINE<144) LCDSTAT=(LCDSTAT&0xFC)|LCDStates[LCount]; switch(LCount) { case 0: /* Proceeding with line refresh */ break; case 2: /* Generating VBlank interrupt */ if(CURLINE==144) if((ISWITCH&VBL_IFLAG)&&(LCDCONT&0x80)) { IFLAGS=IMask=VBL_IFLAG;return(0x0040); } return(0xFFFF); case 7: /* Generating HBlank interrupt */ if((LCDSTAT&0x08)&&(CURLINE<144)) if((ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80)) { IFLAGS=IMask=LCD_IFLAG;return(0x0048); } return(0xFFFF); default: /* Only LCD state had to be changed */ return(0xFFFF); } CURLINE=(CURLINE+1)%154; if(!UCount&&(CURLINE<144)) RefreshLine(CURLINE); SprFlag|=LCDCONT&0x02; /* Checking for line coincidence */ J=LineDelay? (CURLINE+1)%154:CURLINE; if(J!=CMPLINE) { LCDSTAT&=0xFB;J=0x00; } else { LCDSTAT|=0x04;J=0x40; } /* If end of frame reached... */ if(CURLINE==144) { /* Set VBlank state */ LCDSTAT=(LCDSTAT&0xFC)|0x01;J|=0x10; /* Write interrupt timestamp into sound log */ if(SndStream) fputc(0xFF,SndStream); /* Refresh screen if needed */ if(UCount) UCount--; else { if(SprFlag&&(LCDCONT&0x80)) RefreshSprites(); RefreshScreen();SprFlag=0;UCount=UPeriod; } /* Generate serial IO interrupt */ if(SIOFlag&0x01) { SIOFlag&=0xFE;SIOCONT&=0x7F; if(ISWITCH&SIO_IFLAG) IFLAGS|=SIO_IFLAG; } /* 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); } /* Generating LCD controller interrupt */ if((J&LCDSTAT)&&(ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80)) IFLAGS|=LCD_IFLAG; /* Generating timer interrupt */ if(TIMEFRQ&0x04) { TCount+=TStep; if(TCount&0xFFFF0000) { unsigned long L; L=TIMECNT+(TCount>>16); TCount&=0x0000FFFF; if(L&0xFFFFFF00) { TIMECNT=TIMEMOD; if(ISWITCH&TIM_IFLAG) IFLAGS|=TIM_IFLAG; } else TIMECNT=L; } } /* Determining interrupt address */ if(J=IFLAGS) { if(J&EXT_IFLAG) { IMask=EXT_IFLAG;return(0x0060); } if(J&SIO_IFLAG) { IMask=SIO_IFLAG;return(0x0058); } if(J&TIM_IFLAG) { IMask=TIM_IFLAG;return(0x0050); } if(J&LCD_IFLAG) { IMask=LCD_IFLAG;return(0x0048); } if(J&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); } }