/* * Copyright (C) 2017 FIX94 * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ #include #include #include #include #include #include #include #include #include "cpu.h" #include "input.h" #include "ppu.h" #include "mem.h" #include "apu.h" #include "audio.h" #if ZIPSUPPORT #include "unzip/unzip.h" #endif #define DEBUG_HZ 0 #define DEBUG_MAIN_CALLS 0 #define DEBUG_KEY 0 #define DEBUG_LOAD_INFO 1 const char *VERSION_STRING = "fixGB Alpha v0.8.2"; static char window_title[256]; static char window_title_pause[256]; enum { FTYPE_UNK = 0, FTYPE_GB, FTYPE_GBC, FTYPE_GBS, #if ZIPSUPPORT FTYPE_ZIP, #endif }; static void gbEmuFileOpen(const char *name); static bool gbEmuFileRead(); static void gbEmuFileClose(); static void gbEmuResetRegs(); static void gbEmuDisplayFrame(void); void gbEmuMainLoop(void); void gbEmuDeinit(void); static void gbEmuHandleKeyDown(unsigned char key, int x, int y); static void gbEmuHandleKeyUp(unsigned char key, int x, int y); static void gbEmuHandleSpecialDown(int key, int x, int y); static void gbEmuHandleSpecialUp(int key, int x, int y); volatile bool emuRenderFrame; static int emuFileType; static char emuFileName[1024]; uint8_t *emuGBROM = NULL; uint32_t emuGBROMsize; char emuSaveName[1024]; bool emuSaveEnabled; //used externally uint32_t textureImage[0x5A00]; bool gbPause; bool gbEmuGBSPlayback; bool gbsTimerMode; uint16_t gbsLoadAddr; uint16_t gbsInitAddr; uint16_t gbsPlayAddr; uint32_t gbsRomSize; uint16_t gbsSP; uint8_t gbsTracksTotal, gbsTMA, gbsTAC; uint8_t cpuTimer; bool gbCgbGame; bool gbCgbMode; bool gbCgbBootrom; bool gbAllowInvVRAM; bool gbIsMulticart; static bool inPause; static bool inResize; //used externally bool emuSkipVsync; bool emuSkipFrame; static uint8_t mainClock; static uint8_t memClock; #if WINDOWS_BUILD #include typedef bool (APIENTRY *PFNWGLSWAPINTERVALEXTPROC) (int interval); PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #if DEBUG_HZ static DWORD emuFrameStart = 0; static DWORD emuTimesCalled = 0; static DWORD emuTotalElapsed = 0; #endif #if DEBUG_MAIN_CALLS static DWORD emuMainFrameStart = 0; static DWORD emuMainTimesCalled = 0; static DWORD emuMainTimesSkipped = 0; static DWORD emuMainTotalElapsed = 0; #endif #endif #define VISIBLE_DOTS 160 #define VISIBLE_LINES 144 static uint32_t linesToDraw; static const uint32_t visibleImg = VISIBLE_DOTS*VISIBLE_LINES*4; static uint8_t scaleFactor; static FILE *gbEmuFilePointer = NULL; #if ZIPSUPPORT static bool gbEmuFileIsZip; static uint8_t *gbEmuZipBuf = NULL; static uint32_t gbEmuZipLen; static unzFile gbEmuZipObj; static unz_file_info gbEmuZipObjInfo; #endif //from input.c extern uint8_t inValReads[8]; //from mbc.c extern bool rtcUsed; extern size_t extTotalSize; int gbEmuLoadGame(const char* filename) { int argc = 2; const char* argv[] = {"fixGB", filename}; puts(VERSION_STRING); gbEmuResetRegs(); if(argc >= 2) gbEmuFileOpen(argv[1]); if(emuFileType == FTYPE_GB || emuFileType == FTYPE_GBC) { if(!gbEmuFileRead()) { gbEmuFileClose(); printf("Main: Could not read %s!\n", emuFileName); puts("Press enter to exit"); getc(stdin); return EXIT_SUCCESS; } gbEmuFileClose(); memcpy(emuSaveName, emuFileName, 1024); if(emuFileType == FTYPE_GBC) memcpy(emuSaveName+strlen(emuSaveName)-3,"sav",3); else //.gb has one less character memcpy(emuSaveName+strlen(emuSaveName)-2,"sav",3); printf("Save Path: %s\n",emuSaveName); //Set Invalid VRAM allowed gbAllowInvVRAM = (strstr(emuFileName,"InvVRAM") != NULL); printf("Main: Invalid VRAM Access is %sallowed\n", gbAllowInvVRAM?"":"dis"); gbCgbBootrom = memInitCGBBootrom(); //Verify Header CRC uint8_t hdrcrc = 0; uint16_t hdrpos; for(hdrpos = 0x134; hdrpos < 0x14D; hdrpos++) hdrcrc = hdrcrc-emuGBROM[hdrpos]-1; if(hdrcrc != emuGBROM[0x14D]) { printf("Main: WARNING: Invalid ROM Header CRC, ROM may not work\n"); if(gbCgbBootrom) { //Fix Header CRC for Bootrom emuGBROM[0x14D] = hdrcrc; } } //Set CGB Regs allowed gbCgbGame = (emuGBROM[0x143] == 0x80 || emuGBROM[0x143] == 0xC0); gbCgbMode = (gbCgbGame || gbCgbBootrom); printf("Main: CGB Regs are %sallowed\n", gbCgbMode?"":"dis"); //Quick multicart check gbIsMulticart = (emuGBROMsize == 0x200000 && strcmp((char*)(emuGBROM+0x134), "QBILLION") == 0); if(!memInit(true,false)) { free(emuGBROM); puts("Press enter to exit"); getc(stdin); return EXIT_SUCCESS; } //CPU DMG Mode cpuSetSpeed(false); apuInitBufs(); cpuInit(); ppuInit(); apuInit(); inputInit(); inputClear(); if(emuGBROM[0x134] != 0) { if(gbCgbMode) { printf("Game: %.11s\n", (char*)(emuGBROM+0x134)); sprintf(window_title, "%.11s (CGB) - %s\n", (char*)(emuGBROM+0x134), VERSION_STRING); } else { printf("Game: %.16s\n", (char*)(emuGBROM+0x134)); sprintf(window_title, "%.16s (DMG) - %s\n", (char*)(emuGBROM+0x134), VERSION_STRING); } } } else if(emuFileType == FTYPE_GBS) { if(!gbEmuFileRead()) { gbEmuFileClose(); printf("Main: Could not read %s!\n", emuFileName); puts("Press enter to exit"); getc(stdin); return EXIT_SUCCESS; } gbEmuFileClose(); uint8_t *tmpROM = emuGBROM; uint32_t tmpROMsize = emuGBROMsize; gbsTracksTotal = tmpROM[4]; gbsLoadAddr = (tmpROM[6])|(tmpROM[7]<<8); gbsInitAddr = (tmpROM[8])|(tmpROM[9]<<8); gbsPlayAddr = (tmpROM[0xA])|(tmpROM[0xB]<<8); gbsSP = (tmpROM[0xC])|(tmpROM[0xD]<<8); //should give more than enough room for everything gbsRomSize = emuGBROMsize = (tmpROMsize-0x70+gbsLoadAddr+0x7FFF)&(~0x7FFF); //printf("Main: gbsLoadAddr %04x gbsInitAddr %04x gbsPlayAddr %04x gbsSP %04x\n", // gbsLoadAddr, gbsInitAddr, gbsPlayAddr, gbsSP); emuGBROM = malloc(emuGBROMsize); memset(emuGBROM,0xFF,emuGBROMsize); memcpy(emuGBROM+gbsLoadAddr,tmpROM+0x70,tmpROMsize-0x70); gbsTMA = tmpROM[0xE]; gbsTAC = tmpROM[0xF]; if(gbsTAC&0x80) { cpuSetSpeed(true); gbCgbGame = gbCgbMode = true; } else { cpuSetSpeed(false); gbCgbGame = gbCgbMode = false; } printf("Main: CGB Regs are %sallowed\n", gbCgbMode?"":"dis"); if(gbsTAC&4) { printf("Main: GBS Play Timing: Timer\n"); gbsTimerMode = true; } else { printf("Main: GBS Play Timing: VSync\n"); gbsTimerMode = false; } memInit(true,true); if(tmpROM[0x10] != 0) { printf("Game: %.32s\n",(char*)(tmpROM+0x10)); sprintf(window_title, "%.32s (GBS) - %s\n", (char*)(tmpROM+0x10), VERSION_STRING); } free(tmpROM); apuInitBufs(); inputClear(); //does all inits for us memStartGBS(); gbEmuGBSPlayback = true; linesToDraw = 20; scaleFactor = 4; } if(emuGBROM == NULL) { #if ZIPSUPPORT printf("Main: No File to Open! Make sure to call fixGB with a .gb/.gbc/.gbs/.zip File as Argument.\n"); #else printf("Main: No File to Open! Make sure to call fixGB with a .gb/.gbc/.gbs File as Argument.\n"); #endif puts("Press enter to exit"); getc(stdin); return EXIT_SUCCESS; } sprintf(window_title_pause, "%s (Pause)", window_title); #if WINDOWS_BUILD #if DEBUG_HZ emuFrameStart = GetTickCount(); #endif #if DEBUG_MAIN_CALLS emuMainFrameStart = GetTickCount(); #endif #endif return EXIT_SUCCESS; } static void gbEmuResetRegs() { strcpy(window_title, VERSION_STRING); emuRenderFrame = false; linesToDraw = VISIBLE_LINES; scaleFactor = 3; memset(textureImage,0,visibleImg); emuFileType = FTYPE_UNK; memset(emuFileName,0,1024); memset(emuSaveName,0,1024); emuSaveEnabled = false; if(emuGBROM) free(emuGBROM); emuGBROM = NULL; emuGBROMsize = 0; gbPause = false; gbEmuGBSPlayback = false; gbsTimerMode = false; gbsLoadAddr = 0, gbsInitAddr = 0; gbsPlayAddr = 0, gbsRomSize = 0; gbsSP = 0; gbsTracksTotal = 0, gbsTMA = 0, gbsTAC = 0; cpuTimer = 3; gbCgbGame = false; gbCgbMode = false; gbCgbBootrom = false; gbAllowInvVRAM = false; gbIsMulticart = false; inPause = false; inResize = false; emuSkipVsync = false; emuSkipFrame = false; mainClock = 0; memClock = 0; if(gbEmuFilePointer) fclose(gbEmuFilePointer); gbEmuFilePointer = NULL; #if ZIPSUPPORT gbEmuFileIsZip = false; if(gbEmuZipBuf) free(gbEmuZipBuf); gbEmuZipBuf = NULL; gbEmuZipLen = 0; #endif } static int gbEmuGetFileType(const char *name) { int nLen = strlen(name); if(nLen > 4 && name[nLen-4] == '.') { if(tolower(name[nLen-3]) == 'g' && tolower(name[nLen-2]) == 'b' && tolower(name[nLen-1]) == 'c') return FTYPE_GBC; else if(tolower(name[nLen-3]) == 'g' && tolower(name[nLen-2]) == 'b' && tolower(name[nLen-1]) == 's') return FTYPE_GBS; #if ZIPSUPPORT else if(tolower(name[nLen-3]) == 'z' && tolower(name[nLen-2]) == 'i' && tolower(name[nLen-1]) == 'p') return FTYPE_ZIP; #endif } else if(nLen > 3 && name[nLen-3] == '.') { if(tolower(name[nLen-2]) == 'g' && tolower(name[nLen-1]) == 'b') return FTYPE_GB; } return FTYPE_UNK; } static void gbEmuFileOpen(const char *name) { emuFileType = FTYPE_UNK; memset(emuFileName,0,1024); memset(emuSaveName,0,1024); int baseType = gbEmuGetFileType(name); #if ZIPSUPPORT if(baseType == FTYPE_ZIP) { printf("Base ZIP File: %s\n", name); FILE *tmp = fopen(name,"rb"); if(!tmp) { printf("Main: Could not open %s!\n", name); return; } fseek(tmp,0,SEEK_END); gbEmuZipLen = ftell(tmp); rewind(tmp); gbEmuZipBuf = malloc(gbEmuZipLen); if(!gbEmuZipBuf) { printf("Main: Could not allocate ZIP buffer!\n"); fclose(tmp); return; } fread(gbEmuZipBuf,1,gbEmuZipLen,tmp); fclose(tmp); char filepath[20]; snprintf(filepath,20,"%x+%x",(unsigned int)gbEmuZipBuf,gbEmuZipLen); gbEmuZipObj = unzOpen(filepath); int err = unzGoToFirstFile(gbEmuZipObj); while (err == UNZ_OK) { char tmpName[256]; err = unzGetCurrentFileInfo(gbEmuZipObj,&gbEmuZipObjInfo,tmpName,256,NULL,0,NULL,0); if(err == UNZ_OK) { int curInZipType = gbEmuGetFileType(tmpName); if(curInZipType != FTYPE_ZIP && curInZipType != FTYPE_UNK) { emuFileType = curInZipType; gbEmuFileIsZip = true; if(strchr(name,'/') != NULL || strchr(name,'\\') != NULL) { const char *nPath = name; if(strchr(nPath,'/') != NULL) nPath = (strrchr(nPath,'/')+1); if(strchr(nPath,'\\') != NULL) nPath = (strrchr(nPath,'\\')+1); strncpy(emuFileName, name, nPath-name); } char *zName = tmpName; if(strchr(zName,'/') != NULL) zName = (strrchr(zName,'/')+1); if(strchr(zName,'\\') != NULL) zName = (strrchr(zName,'\\')+1); strcat(emuFileName, zName); printf("File in ZIP Type: %s\n", emuFileType == FTYPE_GB ? "GB" : (emuFileType == FTYPE_GBC ? "GBC" : "GBS")); printf("Full Path from ZIP: %s\n", emuFileName); break; } else err = unzGoToNextFile(gbEmuZipObj); } } if(emuFileType == FTYPE_UNK) { printf("Found no usable file in ZIP\n"); unzClose(gbEmuZipObj); if(gbEmuZipBuf) free(gbEmuZipBuf); gbEmuZipBuf = NULL; gbEmuZipLen = 0; } } else if(baseType != FTYPE_UNK) #else if(baseType != FTYPE_UNK) #endif { gbEmuFilePointer = fopen(name,"rb"); if(!gbEmuFilePointer) printf("Main: Could not open %s!\n", name); else { emuFileType = baseType; strncpy(emuFileName, name, 1024); printf("File Type: %s\n", baseType == FTYPE_GB ? "GB" : (baseType == FTYPE_GBC ? "GBC" : "GBS")); printf("Full Path: %s\n", emuFileName); } } } static bool gbEmuFileRead() { #if ZIPSUPPORT if(gbEmuFileIsZip) { unzOpenCurrentFile(gbEmuZipObj); emuGBROMsize = gbEmuZipObjInfo.uncompressed_size; emuGBROM = malloc(emuGBROMsize); if(emuGBROM) unzReadCurrentFile(gbEmuZipObj,emuGBROM,emuGBROMsize); unzCloseCurrentFile(gbEmuZipObj); } else #endif { fseek(gbEmuFilePointer,0,SEEK_END); emuGBROMsize = ftell(gbEmuFilePointer); rewind(gbEmuFilePointer); emuGBROM = malloc(emuGBROMsize); if(emuGBROM) fread(emuGBROM,1,emuGBROMsize,gbEmuFilePointer); } if(emuGBROM) return true; //else printf("Main: Could not allocate ROM buffer!\n"); return false; } //cleans up vars from read static void gbEmuFileClose() { #if ZIPSUPPORT if(gbEmuFileIsZip) unzClose(gbEmuZipObj); gbEmuFileIsZip = false; if(gbEmuZipBuf) free(gbEmuZipBuf); gbEmuZipBuf = NULL; gbEmuZipLen = 0; #endif if(gbEmuFilePointer) fclose(gbEmuFilePointer); gbEmuFilePointer = NULL; } void gbEmuDeinit(void) { //printf("\n"); emuRenderFrame = false; audioDeinit(); apuDeinitBufs(); if(emuGBROM != NULL) free(emuGBROM); emuGBROM = NULL; emuFileType = FTYPE_UNK; memset(emuFileName,0,1024); memset(emuSaveName,0,1024); extTotalSize = 0; rtcUsed = false; memDeinit(); gbIsMulticart = false; //printf("Bye!\n"); } void gbEmuMainLoop(void) { //do one scanline loop do { //run APU first to make sure its synced if(!(mainClock&15)) apuCycle(); //channel timer updates apuClockTimers(); //run possible DMA next memDmaClockTimers(); //run CPU (and mem clocks) next if(!(mainClock&cpuTimer)) { //main CPU clock cpuCycle(); //mem clock tied to CPU clock, so //double speed in CGB mode! if(!(memClock&3)) memClockTimers(); memClock++; } //run PPU last ppuCycle(); if(ppuDrawDone()) { emuRenderFrame = true; //update console stats if requested #if (WINDOWS_BUILD && DEBUG_HZ) emuTimesCalled++; DWORD end = GetTickCount(); emuTotalElapsed += end - emuFrameStart; if(emuTotalElapsed >= 1000) { printf("\r%iHz ", emuTimesCalled); emuTimesCalled = 0; emuTotalElapsed = 0; } emuFrameStart = end; #endif //send VSync to GBS Player if required if(gbEmuGBSPlayback && !gbsTimerMode) cpuPlayGBS(); } mainClock++; } while(emuRenderFrame == false) ; //update console stats if requested #if (WINDOWS_BUILD && DEBUG_MAIN_CALLS) emuMainTimesCalled++; DWORD end = GetTickCount(); emuMainTotalElapsed += end - emuMainFrameStart; if(emuMainTotalElapsed >= 1000) { printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped); emuMainTimesCalled = 0; emuMainTimesSkipped = 0; emuMainTotalElapsed = 0; } emuMainFrameStart = end; #endif }