/* Pcsx2 - Pc Ps2 Emulator * Copyright (C) 2002-2009 Pcsx2 Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "PrecompiledHeader.h" #ifdef _WIN32 #include "RDebug/deci2.h" #else #include #endif #include #include "Common.h" #include "PsxCommon.h" #include "SaveState.h" #include "CDVDisodrv.h" #include "VUmicro.h" #include "VU.h" #include "iCore.h" #include "iVUzerorec.h" #include "BaseblockEx.h" // included for devbuild block dumping (which may or may not work anymore?) #include "GS.h" #include "COP0.h" #include "Cache.h" #include "Paths.h" using namespace std; using namespace R5900; PcsxConfig Config; u32 BiosVersion; char CdromId[12]; static int g_Pcsx2Recording = 0; // true 1 if recording video and sound const char *LabelAuthors = { N_( "PCSX2, a PS2 emulator\n\n" "Active Devs: Arcum42, Refraction,\n" "drk||raziel, cottonvibes, gigaherz,\n" "rama, Jake.Stine, saqib, Tmkk\n" "\n" "Inactive devs: Alexey silinov, Aumatt,\n" "Florin, goldfinger, Linuzappz, loser,\n" "Nachbrenner, shadow, Zerofrog\n" "\n" "Betatesting: Bositman, ChaosCode,\n" "CKemu, crushtest, GeneralPlot,\n" "Krakatos, Paorotaku, Rudy_X\n" "\n" "Webmasters: CKemu, Falcon4ever" ) }; const char *LabelGreets = { N_( "Contributors: Hiryu and Sjeep for libcvd (the iso parsing and\n" "filesystem driver code), nneeve, pseudonym\n" "\n" "Plugin Specialists: ChickenLiver (Lilypad), Efp (efp),\n" "Gabest (Gsdx, Cdvdolio, Xpad)\n" "\n" "Special thanks to: black_wd, Belmont, BGome, _Demo_, Dreamtime,\n" "F|RES, MrBrown, razorblade, Seta-san, Skarmeth" ) }; static struct { const char *name; u32 size; } ioprps[]={ {"IOPRP14", 43845}, {"IOPRP142", 48109}, {"IOPRP143", 58317}, {"IOPRP144", 58525}, {"IOPRP15", 82741}, {"IOPRP151", 82917}, {"IOPRP153", 82949}, {"IOPRP16", 91909}, {"IOPRP165", 98901}, {"IOPRP20", 109809}, {"IOPRP202", 110993}, {"IOPRP205", 119797}, {"IOPRP21", 126857}, {"IOPRP211", 129577}, {"IOPRP213", 129577}, {"IOPRP214", 140945}, {"IOPRP22", 199257}, {"IOPRP221", 196937}, {"IOPRP222", 198233}, {"IOPRP224", 201065}, {"IOPRP23", 230329}, {"IOPRP234", 247641}, {"IOPRP24", 251065}, {"IOPRP241", 251049}, {"IOPRP242", 252409}, {"IOPRP243", 253201}, {"IOPRP250", 264897}, {"IOPRP252", 265233}, {"IOPRP253", 267217}, {"IOPRP254", 264449}, {"IOPRP255", 264449}, {"IOPRP260", 248945}, {"IOPRP270", 249121}, {"IOPRP271", 266817}, {"IOPRP280", 269889}, {"IOPRP300", 275345}, {"DNAS280", 272753}, {"DNAS270", 251729}, {"DNAS271", 268977}, {"DNAS300", 278641}, {"DNAS280", 272705}, {"DNAS255", 264945}, {NULL, 0} }; void GetRPCVersion(char *ioprp, char *rpcver){ char *p=ioprp; int i; struct TocEntry te; if (p && (CDVD_findfile(p+strlen("cdromN:"), &te) != -1)){ for (i=0; ioprps[i].size>0; i++) if (te.fileSize==ioprps[i].size) break; if (ioprps[i].size>0) p=(char *)ioprps[i].name; } // fixme - Is p really supposed to be set in the middle of an if statement? if (p && (p=strstr(p, "IOPRP")+strlen("IOPRP"))){ for (i=0;(i<4) && p && (*p>='0') && (*p<='9');i++, p++) rpcver[i]=*p; for ( ; i<4 ;i++ ) rpcver[i]='0'; } } u32 GetBiosVersion() { unsigned int fileOffset=0; s8 *ROMVER; char vermaj[8]; char vermin[8]; struct romdir *rd; u32 version; int i; for (i=0; i<512*1024; i++) { rd = (struct romdir*)&psRu8(i); if (strncmp(rd->fileName, "RESET", 5) == 0) break; /* found romdir */ } if (i == 512*1024) return -1; while(strlen(rd->fileName) > 0){ if (strcmp(rd->fileName, "ROMVER") == 0){ // found romver ROMVER = &psRs8(fileOffset); strncpy(vermaj, (char *)(ROMVER+ 0), 2); vermaj[2] = 0; strncpy(vermin, (char *)(ROMVER+ 2), 2); vermin[2] = 0; version = strtol(vermaj, (char**)NULL, 0) << 8; version|= strtol(vermin, (char**)NULL, 0); return version; } if ((rd->fileSize % 0x10)==0) fileOffset += rd->fileSize; else fileOffset += (rd->fileSize + 0x10) & 0xfffffff0; rd++; } return -1; } //2002-09-22 (Florin) int IsBIOS(char *filename, char *description) { string Bios; char ROMVER[14+1], zone[12+1]; FILE *fp; unsigned int fileOffset=0, found=FALSE; struct romdir rd; Path::Combine( Bios, Config.BiosDir, filename ); int biosFileSize = Path::getFileSize( Bios ); if( biosFileSize <= 0) return FALSE; fp = fopen(Bios.c_str(), "rb"); if (fp == NULL) return FALSE; while ((ftell(fp)<512*1024) && (fread(&rd, DIRENTRY_SIZE, 1, fp)==1)) if (strcmp(rd.fileName, "RESET") == 0) break; /* found romdir */ if ((strcmp(rd.fileName, "RESET") != 0) || (rd.fileSize == 0)) { fclose(fp); return FALSE; //Unable to locate ROMDIR structure in file or a ioprpXXX.img } while(strlen(rd.fileName) > 0){ if (strcmp(rd.fileName, "ROMVER") == 0){ // found romver unsigned int filepos=ftell(fp); fseek(fp, fileOffset, SEEK_SET); if (fread(&ROMVER, 14, 1, fp) == 0) break; fseek(fp, filepos, SEEK_SET);//go back switch(ROMVER[4]){ case 'T':sprintf(zone, "T10K "); break; case 'X':sprintf(zone, "Test ");break; case 'J':sprintf(zone, "Japan "); break; case 'A':sprintf(zone, "USA "); break; case 'E':sprintf(zone, "Europe"); break; case 'H':sprintf(zone, "HK "); break; case 'P':sprintf(zone, "Free "); break; case 'C':sprintf(zone, "China "); break; default: sprintf(zone, "%c ",ROMVER[4]); break;//shoudn't show } sprintf(description, "%s vXX.XX(XX/XX/XXXX) %s", zone, ROMVER[5]=='C'?"Console":ROMVER[5]=='D'?"Devel":""); strncpy(description+ 8, ROMVER+ 0, 2);//ver major strncpy(description+11, ROMVER+ 2, 2);//ver minor strncpy(description+14, ROMVER+12, 2);//day strncpy(description+17, ROMVER+10, 2);//month strncpy(description+20, ROMVER+ 6, 4);//year found = TRUE; } if ((rd.fileSize % 0x10)==0) fileOffset += rd.fileSize; else fileOffset += (rd.fileSize + 0x10) & 0xfffffff0; if (fread(&rd, DIRENTRY_SIZE, 1, fp)==0) break; } fileOffset-=((rd.fileSize + 0x10) & 0xfffffff0) - rd.fileSize; fclose(fp); if (found) { char percent[6]; if ( biosFileSize < (int)fileOffset) { sprintf(percent, " %d%%", biosFileSize*100/(int)fileOffset); strcat(description, percent);//we force users to have correct bioses, //not that lame scph10000 of 513KB ;-) } return TRUE; } return FALSE; //fail quietly } // LOAD STUFF // fixme - Is there any reason why we shouldn't delete this define, and replace the array lengths // with the actual numbers? #define ISODCL(from, to) (to - from + 1) struct iso_directory_record { char length [ISODCL (1, 1)]; /* length[1]; 711 */ char ext_attr_length [ISODCL (2, 2)]; /* ext_attr_length[1]; 711 */ char extent [ISODCL (3, 10)]; /* extent[8]; 733 */ char size [ISODCL (11, 18)]; /* size[8]; 733 */ char date [ISODCL (19, 25)]; /* date[7]; 7 by 711 */ char flags [ISODCL (26, 26)]; /* flags[1]; */ char file_unit_size [ISODCL (27, 27)]; /* file_unit_size[1]; 711 */ char interleave [ISODCL (28, 28)]; /* interleave[1]; 711 */ char volume_sequence_number [ISODCL (29, 32)]; /* volume_sequence_number[3]; 723 */ unsigned char name_len [ISODCL (33, 33)]; /* name_len[1]; 711 */ char name [1]; }; int LoadCdrom() { return 0; } int CheckCdrom() { u8 *buf; if (CDVDreadTrack(16, CDVD_MODE_2352) == -1) return -1; buf = CDVDgetBuffer(); if (buf == NULL) return -1; strncpy(CdromId, (char*)buf+52, 10); return 0; } int GetPS2ElfName(char *name){ int f; char buffer[g_MaxPath];//if a file is longer...it should be shorter :D char *pos; TocEntry tocEntry; CDVDFS_init(); // check if the file exists if (CDVD_findfile("SYSTEM.CNF;1", &tocEntry) != TRUE){ Console::Error("Boot Error > SYSTEM.CNF not found"); return 0;//could not find; not a PS/PS2 cdvd } f=CDVDFS_open("SYSTEM.CNF;1", 1); CDVDFS_read(f, buffer, g_MaxPath); CDVDFS_close(f); buffer[tocEntry.fileSize]='\0'; pos=strstr(buffer, "BOOT2"); if (pos==NULL){ pos=strstr(buffer, "BOOT"); if (pos==NULL) { Console::Error("Boot Error > This is not a PS2 game!"); return 0; } return 1; } pos+=strlen("BOOT2"); while (pos && *pos && pos<=&buffer[255] && (*pos<'A' || (*pos>'Z' && *pos<'a') || *pos>'z')) pos++; if (!pos || *pos==0) return 0; sscanf(pos, "%s", name); if (strncmp("cdrom0:\\", name, 8) == 0) { strncpy(CdromId, name+8, 11); CdromId[11] = 0; } #ifdef PCSX2_DEVBUILD FILE *fp; int i; // inifile_read(CdromId); fp = fopen("System.map", "r"); if( fp == NULL ) return 2; u32 addr; Console::WriteLn("Loading System.map..."); while (!feof(fp)) { fseek(fp, 8, SEEK_CUR); buffer[0] = '0'; buffer[1] = 'x'; for (i=2; i<10; i++) buffer[i] = fgetc(fp); buffer[i] = 0; addr = strtoul(buffer, (char**)NULL, 0); fseek(fp, 3, SEEK_CUR); for (i=0; iFreeze( g_nLeftGSFrames ); } extern uptr pDsp; void LoadGSState(const string& file) { int ret; gzLoadingState* f; Console::Status( "Loading GS State..." ); try { f = new gzLoadingState( file ); } catch( Exception::FileNotFound& ) { // file not found? try prefixing with sstates folder: if( !Path::isRooted( file ) ) { string strfile; Path::Combine( strfile, SSTATES_DIR, file ); f = new gzLoadingState( strfile.c_str() ); // If this load attempt fails, then let the exception bubble up to // the caller to deal with... } } // Always set gsIrq callback -- GS States are always exclusionary of MTGS mode GSirqCallback( gsIrq ); ret = GSopen(&pDsp, "PCSX2", 0); if (ret != 0) { delete f; throw Exception::PluginFailure( "GS" ); } ret = PAD1open((void *)&pDsp); f->Freeze(g_nLeftGSFrames); f->gsFreeze(); f->FreezePlugin( "GS", gsSafeFreeze ); RunGSState( *f ); delete( f ); GSclose(); PAD1close(); } #endif struct LangDef { char id[8]; char name[64]; }; LangDef sLangs[] = { { "ar_AR", N_("Arabic") }, { "bg_BG", N_("Bulgarian") }, { "ca_CA", N_("Catalan") }, { "cz_CZ", N_("Czech") }, { "du_DU", N_("Dutch") }, { "de_DE", N_("German") }, { "el_EL", N_("Greek") }, { "en_US", N_("English") }, { "fr_FR", N_("French") }, { "hb_HB" , N_("Hebrew") }, { "hu_HU", N_("Hungarian") }, { "it_IT", N_("Italian") }, { "ja_JA", N_("Japanese") }, { "pe_PE", N_("Persian") }, { "po_PO", N_("Portuguese") }, { "po_BR", N_("Portuguese BR") }, { "pl_PL" , N_("Polish") }, { "ro_RO", N_("Romanian") }, { "ru_RU", N_("Russian") }, { "es_ES", N_("Spanish") }, { "sh_SH" , N_("S-Chinese") }, { "sw_SW", N_("Swedish") }, { "tc_TC", N_("T-Chinese") }, { "tr_TR", N_("Turkish") }, { "", "" }, }; char *ParseLang(char *id) { int i=0; while (sLangs[i].id[0] != 0) { if (!strcmp(id, sLangs[i].id)) return _(sLangs[i].name); i++; } return id; } #define NUM_STATES 10 int StatesC = 0; extern char strgametitle[256]; char* mystrlwr( char* string ) { assert( string != NULL ); while ( 0 != ( *string++ = (char)tolower( *string ) ) ); return string; } static void GetGSStateFilename( string& dest ) { string gsText; ssprintf( gsText, "/%8.8X.%d.gs", ElfCRC, StatesC); Path::Combine( dest, SSTATES_DIR, gsText ); } void CycleFrameLimit(int dir) { const char* limitMsg; u32 newOptions; u32 curFrameLimit = Config.Options & PCSX2_FRAMELIMIT_MASK; u32 newFrameLimit; static u32 oldFrameLimit = PCSX2_FRAMELIMIT_LIMIT; if( dir == 0 ) { // turn off limit or restore previous limit mode if (curFrameLimit) { oldFrameLimit = curFrameLimit; newFrameLimit = 0; } else newFrameLimit = oldFrameLimit; } else if (dir > 0) { // next newFrameLimit = (curFrameLimit + PCSX2_FRAMELIMIT_LIMIT) & PCSX2_FRAMELIMIT_MASK; } else { // previous newFrameLimit = (curFrameLimit + PCSX2_FRAMELIMIT_VUSKIP) & PCSX2_FRAMELIMIT_MASK; } newOptions = (Config.Options & ~PCSX2_FRAMELIMIT_MASK) | newFrameLimit; gsResetFrameSkip(); switch(newFrameLimit) { case PCSX2_FRAMELIMIT_NORMAL: limitMsg = "None/Normal"; break; case PCSX2_FRAMELIMIT_LIMIT: limitMsg = "Limit"; break; case PCSX2_FRAMELIMIT_SKIP: case PCSX2_FRAMELIMIT_VUSKIP: if( GSsetFrameSkip == NULL ) { newOptions &= ~PCSX2_FRAMELIMIT_MASK; Console::Notice("Notice: GS Plugin does not support frameskipping."); limitMsg = "None/Normal"; } else { // When enabling Skipping we have to make sure Skipper (GS) and Limiter (EE) // are properly synchronized. gsDynamicSkipEnable(); limitMsg = ((newOptions & PCSX2_FRAMELIMIT_MASK) == PCSX2_FRAMELIMIT_SKIP) ? "Skip" : "VUSkip"; } break; } Threading::AtomicExchange( Config.Options, newOptions ); Console::Notice("Frame Limit Mode Changed: %s", params limitMsg ); // [Air]: Do we really want to save runtime changes to frameskipping? //SaveConfig(); } void ProcessFKeys(int fkey, int shift) { string Text; assert(fkey >= 1 && fkey <= 12 ); switch(fkey) { case 1: try { SaveState::GetFilename( Text, StatesC ); gzSavingState( Text ).FreezeAll(); } catch( Exception::BaseException& ex ) { // 99% of the time this is a file permission error and the // cpu state is intact so just display a passive msg to console. Console::Error( _( "Error > Could not save state to slot %d" ), params StatesC ); Console::Error( ex.cMessage() ); } break; case 2: if( shift ) StatesC = (StatesC+NUM_STATES-1) % NUM_STATES; else StatesC = (StatesC+1) % NUM_STATES; Console::Notice( _( " > Selected savestate slot %d" ), params StatesC); if( GSchangeSaveState != NULL ) { SaveState::GetFilename(Text, StatesC); GSchangeSaveState(StatesC, Text.c_str()); } break; case 3: try { SaveState::GetFilename( Text, StatesC ); gzLoadingState joe( Text ); // throws exception on version mismatch cpuReset(); SysResetExecutionState(); joe.FreezeAll(); } catch( Exception::StateLoadError_Recoverable& ) { // At this point the cpu hasn't been reset, so we can return // control to the user safely... (and silently) } catch( Exception::FileNotFound& ) { Console::Notice( _("Saveslot %d cannot be loaded; slot does not exist (file not found)"), params StatesC ); } catch( Exception::RuntimeError& ex ) { // This is the bad one. Chances are the cpu has been reset, so emulation has // to be aborted. Sorry user! We'll give you some info for your trouble: Console::Error( _("An error occured while trying to load saveslot %d"), params StatesC ); Console::Error( ex.cMessage() ); Msgbox::Alert( "Pcsx2 encountered an error while trying to load the savestate\n" "and emulation had to be aborted." ); ClosePlugins( true ); throw Exception::CpuStateShutdown( "Saveslot load failed; PS2 emulated state had to be shut down." ); // let the GUI handle the error "gracefully" } break; case 4: CycleFrameLimit(shift ? -1 : 1); break; // note: VK_F5-VK_F7 are reserved for GS case 8: GSmakeSnapshot("snaps/"); break; #ifdef PCSX2_DEVBUILD case 10: { int num; FILE* f; BASEBLOCKEX** ppblocks = GetAllBaseBlocks(&num, 0); f = fopen("perflog.txt", "w"); while(num-- > 0 ) { if( ppblocks[0]->visited > 0 ) { fprintf(f, "%u %u %u %u\n", ppblocks[0]->startpc, (u32)(ppblocks[0]->ltime.QuadPart / ppblocks[0]->visited), ppblocks[0]->visited, ppblocks[0]->size); } ppblocks[0]->visited = 0; ppblocks[0]->ltime.QuadPart = 0; ppblocks++; } fclose(f); Console::Status( "perflog.txt written" ); break; } case 11: if( mtgsThread != NULL ) { Console::Notice( "Cannot make gsstates in MTGS mode" ); } else { if( strgametitle[0] != 0 ) { // only take the first two words char name[256], *tok; string gsText; tok = strtok(strgametitle, " "); sprintf(name, "%s_", mystrlwr(tok)); tok = strtok(NULL, " "); if( tok != NULL ) strcat(name, tok); ssprintf( gsText, "%s.%d.gs", name, StatesC); Path::Combine( Text, SSTATES_DIR, gsText ); } else GetGSStateFilename( Text ); SaveGSState(Text); } break; #endif case 12: if( shift ) { #ifdef PCSX2_DEVBUILD iDumpRegisters(cpuRegs.pc, 0); Console::Notice("hardware registers dumped EE:%x, IOP:%x\n", params cpuRegs.pc, psxRegs.pc); #endif } else { g_Pcsx2Recording ^= 1; if( mtgsThread != NULL ) { mtgsThread->SendSimplePacket(GS_RINGTYPE_RECORD, g_Pcsx2Recording, 0, 0); } else { if( GSsetupRecording != NULL ) GSsetupRecording(g_Pcsx2Recording, NULL); } if( SPU2setupRecording != NULL ) SPU2setupRecording(g_Pcsx2Recording, NULL); } break; } } void injectIRX(const char *filename) { string path; char name[260], *p, *q; struct romdir *rd; int iROMDIR=-1, iIOPBTCONF=-1, iBLANK=-1, i, filesize; FILE *fp; strcpy(name, filename); for (i=0; name[i] && name[i]!='.' && i<10; i++) name[i]=toupper(name[i]);name[i]=0; //phase 1: find ROMDIR in bios for (p=(char*)PS2MEM_ROM; p<(char*)PS2MEM_ROM+0x80000; p++) if (strncmp(p, "RESET", 5)==0) break; rd=(struct romdir*)p; for (i=0; rd[i].fileName[0]; i++)if (strncmp(rd[i].fileName, name, strlen(name))==0)break; if (rd[i].fileName[0])return;//already in;) //phase 2: make room in IOPBTCONF & ROMDIR for (i=0; rd[i].fileName[0]; i++)if (strncmp(rd[i].fileName, "ROMDIR", 6)==0)iROMDIR=i; for (i=0; rd[i].fileName[0]; i++)if (strncmp(rd[i].fileName, "IOPBTCONF", 9)==0)iIOPBTCONF=i; for (i=0; rd[i].fileName[0]; i++)if (rd[i].fileName[0]=='-')break; iBLANK=i; rd[iBLANK].fileSize-=DIRENTRY_SIZE+DIRENTRY_SIZE; p=(char*)PS2MEM_ROM;for (i=0; iq){*((u64*)p)=*((u64*)p-4);*((u64*)p+1)=*((u64*)p-3);p-=DIRENTRY_SIZE;} *((u64*)p)=*((u64*)p+1)=0;p-=DIRENTRY_SIZE;rd[iIOPBTCONF].fileSize+=DIRENTRY_SIZE; q=(char*)PS2MEM_ROM;for (i=0; i<=iROMDIR; i++) q+=(rd[i].fileSize+0xF)&(~0xF); while (p >q){*((u64*)p)=*((u64*)p-2);*((u64*)p+1)=*((u64*)p-1);p-=DIRENTRY_SIZE;} *((u64*)p)=*((u64*)p+1)=0;p-=DIRENTRY_SIZE;rd[iROMDIR].fileSize+=DIRENTRY_SIZE; //phase 3: add the name to the end of IOPBTCONF p=(char*)PS2MEM_ROM;for (i=0; i