/******************************************************************* * * File: Engine.cpp * * Author: Peter van Sebille (peter@yipton.net) * * Modified/adapted for picodriveN by notaz, 2006 * * (c) Copyright 2006, notaz * (c) Copyright 2002, Peter van Sebille * All Rights Reserved * *******************************************************************/ #include "Engine.h" #include #include #include #include #include #include #include "version.h" #include "../../pico/picoInt.h" #include "engine/debug.h" #include "app.h" // this is where we start to break a bunch of symbian rules extern TInt machineUid; extern int gamestate, gamestate_next; extern TPicoConfig *currentConfig; extern const char *actionNames[]; RSemaphore pauseSemaphore; RSemaphore initSemaphore; const char *RomFileName = 0; int pico_was_reset = 0; unsigned char *rom_data = 0; static CPicolAppView *appView = 0; TInt CPicoGameSession::Do(const TPicoServRqst what, TAny *param) { switch (what) { case PicoMsgLoadState: if(!rom_data) return -1; // no ROM return saveLoadGame(1); case PicoMsgSaveState: if(!rom_data) return -1; return saveLoadGame(0); case PicoMsgLoadROM: return loadROM((TPtrC16 *)param); case PicoMsgResume: DEBUGPRINT(_L("resume with rom %08x"), rom_data); if(rom_data) { return ChangeRunState(PGS_Running); } return 1; case PicoMsgReset: if(rom_data) { PicoReset(0); pico_was_reset = 1; return ChangeRunState(PGS_Running); } return 1; case PicoMsgKeys: return ChangeRunState(PGS_KeyConfig); case PicoMsgPause: return ChangeRunState(PGS_Paused); case PicoMsgQuit: DEBUGPRINT(_L("got quit msg.")); return ChangeRunState(PGS_Quit); // config change case PicoMsgConfigChange: return changeConfig((TPicoConfig *)param); case PicoMsgSetAppView: appView = (CPicolAppView *)param; return 1; default: return 1; } } TInt EmuThreadFunction(TAny* anArg); TInt CPicoGameSession::StartEmuThread() { TInt res=KErrNone; iEmuRunning = EFalse; if (initSemaphore.Handle() > 0) initSemaphore.Close(); initSemaphore.CreateLocal(0); if (pauseSemaphore.Handle() <= 0) pauseSemaphore.CreateLocal(0); RThread thread; if(iThreadWatcher && (res = thread.Open(iThreadWatcher->iTid)) == KErrNone) { // should be a dead thread in some strange state. DEBUGPRINT(_L("found thread with the same id (id=%i, RequestCount=%i), killing.."), (TInt32)thread.Id(), thread.RequestCount()); // what can we do in this situation? Nothing seems to help, it just stays in this state. delete iThreadWatcher; iThreadWatcher = 0; thread.Kill(1); thread.Terminate(1); thread.Close(); } //semaphore.CreateLocal(0); // create a semaphore so we know when thread init is finished res=thread.Create(_L("PicoEmuThread"), // create new server thread EmuThreadFunction, // thread's main function KDefaultStackSize, KMinHeapSize, KPicoMaxHeapSize, 0 // &semaphore // passed as TAny* argument to thread function ); if(res == KErrNone) { // thread created ok - now start it going thread.SetPriority(EPriorityMore); iEmuRunning = ETrue; if (iThreadWatcher) delete iThreadWatcher; iThreadWatcher = CThreadWatcher::NewL(thread.Id()); thread.Resume(); // start it going DEBUGPRINT(_L("initSemaphore.Wait()")); res = initSemaphore.Wait(1000*1000); // wait until it's initialized DEBUGPRINT(_L("initSemaphore resume, ExitReason() == %i"), thread.ExitReason()); res |= thread.ExitReason(); thread.Close(); // we're no longer interested in the other thread if(res != KErrNone) iEmuRunning = EFalse; return res; } return res; } TInt CPicoGameSession::ChangeRunState(TPicoGameState newstate, TPicoGameState newstate_next) { if (!iEmuRunning) { gamestate = PGS_Paused; TInt res = StartEmuThread(); if(res != KErrNone) DEBUGPRINT(_L("StartEmuThread() returned %i"), res); if (!iEmuRunning) return PicoErrEmuThread; } int oldstate = gamestate; gamestate = newstate; gamestate_next = newstate_next ? newstate_next : PGS_Paused; if (oldstate == PGS_Paused) pauseSemaphore.Signal(); return 0; } TInt CPicoGameSession::loadROM(TPtrC16 *pptr) { TInt res, i; char buff[0x31]; if(rom_data) { // save SRAM for previous ROM if(currentConfig->iFlags & 1) saveLoadGame(0, 1); } RomFileName = 0; if(rom_data) { free(rom_data); rom_data = 0; } // read the contents of the client pointer into a TPtr. static TBuf8 writeBuf; writeBuf.Copy(*pptr); // detect wrong extensions (.srm and .mds) TBuf8<5> ext; ext.Copy(writeBuf.Right(4)); ext.LowerCase(); if(!strcmp((char *)ext.PtrZ(), ".srm") || !strcmp((char *)ext.PtrZ(), "s.gz") || // .mds.gz !strcmp((char *)ext.PtrZ(), ".mds")) { return PicoErrNotRom; } FILE *rom = fopen((char *) writeBuf.PtrZ(), "rb"); if(!rom) { DEBUGPRINT(_L("failed to open rom.")); return PicoErrRomOpenFailed; } // make sure emu thread is ok res = ChangeRunState(PGS_Paused); if(res) { fclose(rom); return res; } unsigned int rom_size = 0; // zipfile support if(!strcmp((char *)ext.PtrZ(), ".zip")) { fclose(rom); res = CartLoadZip((const char *) writeBuf.PtrZ(), &rom_data, &rom_size); if(res) { DEBUGPRINT(_L("CartLoadZip() failed (%i)"), res); return res; } } else { if( (res = PicoCartLoad(rom, &rom_data, &rom_size)) ) { DEBUGPRINT(_L("PicoCartLoad() failed (%i)"), res); fclose(rom); return PicoErrOutOfMem; } fclose(rom); } // detect wrong files (Pico crashes on very small files), also see if ROM EP is good if(rom_size <= 0x200 || strncmp((char *)rom_data, "Pico", 4) == 0 || ((*(TUint16 *)(rom_data+4)<<16)|(*(TUint16 *)(rom_data+6))) >= (int)rom_size) { free(rom_data); rom_data = 0; return PicoErrNotRom; } DEBUGPRINT(_L("PicoCartInsert(0x%08X, %d);"), rom_data, rom_size); if(PicoCartInsert(rom_data, rom_size)) { return PicoErrOutOfMem; } pico_was_reset = 1; // global ROM file name for later use RomFileName = (const char *) writeBuf.PtrZ(); // name from the ROM itself for(i = 0; i < 0x30; i++) buff[i] = rom_data[0x150 + (i^1)]; // unbyteswap for(buff[i] = 0, i--; i >= 0; i--) { if(buff[i] != ' ') break; buff[i] = 0; } TPtrC8 buff8((TUint8*) buff); iRomInternalName.Copy(buff8); // load SRAM for this ROM if(currentConfig->iFlags & 1) saveLoadGame(1, 1); // debug #ifdef __DEBUG_PRINT TInt cells = User::CountAllocCells(); TInt mem; User::AllocSize(mem); DEBUGPRINT(_L("comm: cels=%d, size=%d KB"), cells, mem/1024); ChangeRunState(PGS_DebugHeap, PGS_Running); #else ChangeRunState(PGS_Running); #endif return 0; } TInt CPicoGameSession::changeConfig(TPicoConfig *aConfig) { DEBUGPRINT(_L("got new config.")); currentConfig = aConfig; // set PicoOpt and rate PicoRegionOverride = currentConfig->PicoRegion; PicoOpt = currentConfig->iPicoOpt; switch((currentConfig->iFlags>>3)&7) { case 1: PsndRate=11025; break; case 2: PsndRate=16000; break; case 3: PsndRate=22050; break; case 4: PsndRate=44100; break; default: PsndRate= 8000; break; } // 6 button pad, enable XYZM config if needed if(PicoOpt & 0x20) { actionNames[8] = "Z"; actionNames[9] = "Y"; actionNames[10] = "X"; actionNames[11] = "MODE"; } else { actionNames[8] = actionNames[9] = actionNames[10] = actionNames[11] = 0; } // if we are in center 90||270 modes, we can bind renderer switcher if(currentConfig->iScreenMode == TPicoConfig::PMFit && (currentConfig->iScreenRotation == TPicoConfig::PRot0 || currentConfig->iScreenRotation == TPicoConfig::PRot180)) actionNames[25] = 0; else actionNames[25] = "RENDERER"; return 0; } void MainOldCleanup(); // from main.cpp #ifdef __DEBUG_PRINT_FILE extern RMutex logMutex; #endif void CPicoGameSession::freeResources() { RThread thread; TInt i; DEBUGPRINT(_L("CPicoGameSession::freeResources()")); if(iThreadWatcher && thread.Open(iThreadWatcher->iTid) == KErrNone) { // try to stop our emu thread gamestate = PGS_Quit; if(pauseSemaphore.Handle() > 0) pauseSemaphore.Signal(); if(thread.Handle() > 0) { // tried reopening thread handle here over time intervals to detect if thread is alive, // but would run into handle panics. for(i = 0; i < 8; i++) { User::After(100 * 1000); if(thread.ExitReason() != 0) break; } if(thread.ExitReason() == 0) { // too late, time to die DEBUGPRINT(_L("thread %i not responding, killing.."), (TInt32) thread.Id()); thread.Terminate(1); } thread.Close(); } } if(iThreadWatcher != NULL) { DEBUGPRINT(_L("delete iThreadWatcher")); delete iThreadWatcher; DEBUGPRINT(_L("after delete iThreadWatcher")); iThreadWatcher = NULL; } MainOldCleanup(); if (initSemaphore.Handle() > 0) initSemaphore.Close(); if (pauseSemaphore.Handle() > 0) pauseSemaphore.Close(); #ifdef __DEBUG_PRINT_FILE if (logMutex.Handle() > 0) logMutex.Close(); #endif } TBool CPicoGameSession::iEmuRunning = EFalse; CThreadWatcher *CPicoGameSession::iThreadWatcher = 0; TBuf<0x30> CPicoGameSession::iRomInternalName; void TPicoConfig::SetDefaults() { iLastROMFile.SetLength(0); iScreenRotation = PRot270; iScreenMode = PMCenter; iFlags = 1; // use_sram iPicoOpt = 0; // all off iFrameskip = PFSkipAuto; Mem::FillZ(iKeyBinds, sizeof(iKeyBinds)); Mem::FillZ(iAreaBinds, sizeof(iAreaBinds)); iKeyBinds[0xd5] = 1<<26; // bind back } // load config void TPicoConfig::InternalizeL(RReadStream &aStream) { TInt32 version, fname_len; version = aStream.ReadInt32L(); fname_len = aStream.ReadInt32L(); // not sure if this is safe iLastROMFile.SetMax(); aStream.ReadL((TUint8 *) iLastROMFile.Ptr(), KMaxFileName*2); iLastROMFile.SetLength(fname_len); iScreenRotation = aStream.ReadInt32L(); iScreenMode = aStream.ReadInt32L(); iFlags = aStream.ReadUint32L(); iPicoOpt = aStream.ReadInt32L(); iFrameskip = aStream.ReadInt32L(); aStream.ReadL((TUint8 *)iKeyBinds, sizeof(iKeyBinds)); aStream.ReadL((TUint8 *)iAreaBinds, sizeof(iAreaBinds)); PicoRegion = aStream.ReadInt32L(); } // save config void TPicoConfig::ExternalizeL(RWriteStream &aStream) const { TInt version = (KPicoMajorVersionNumber<<24)+(KPicoMinorVersionNumber<<16); aStream.WriteInt32L(version); aStream.WriteInt32L(iLastROMFile.Length()); aStream.WriteL((const TUint8 *)iLastROMFile.Ptr(), KMaxFileName*2); aStream.WriteInt32L(iScreenRotation); aStream.WriteInt32L(iScreenMode); aStream.WriteUint32L(iFlags); aStream.WriteInt32L(iPicoOpt); aStream.WriteInt32L(iFrameskip); aStream.WriteL((const TUint8 *)iKeyBinds, sizeof(iKeyBinds)); aStream.WriteL((const TUint8 *)iAreaBinds, sizeof(iAreaBinds)); aStream.WriteInt32L(PicoRegion); } // CThreadWatcher CThreadWatcher::~CThreadWatcher() { Cancel(); DEBUGPRINT(_L("after CThreadWatcher::Cancel();")); } CThreadWatcher::CThreadWatcher(const TThreadId& aTid) : CActive(CActive::EPriorityStandard), iTid(aTid) { } CThreadWatcher* CThreadWatcher::NewL(const TThreadId& aTid) { CThreadWatcher* self = new(ELeave) CThreadWatcher(aTid); CleanupStack::PushL(self); self->ConstructL(); CleanupStack::Pop(); // self return self; } void CThreadWatcher::ConstructL() { CActiveScheduler::Add(this); RThread thread; if(thread.Open(iTid) == KErrNone) { thread.Logon(iStatus); thread.Close(); SetActive(); } } void CThreadWatcher::RunL() { DEBUGPRINT(_L("CThreadWatcher::RunL()")); CPicoGameSession::iEmuRunning = EFalse; if(appView) appView->UpdateCommandList(); //initSemaphore.Signal(); // no point to do that here, AS can't get here if it is waiting } void CThreadWatcher::DoCancel() { RThread thread; DEBUGPRINT(_L("CThreadWatcher::DoCancel()")); if(thread.Open(iTid) == KErrNone) { DEBUGPRINT(_L("thread.LogonCancel(iStatus);")); thread.LogonCancel(iStatus); thread.Close(); } }