reset behavior changed, Puggsy detection added
[picodrive.git] / platform / uiq3 / Engine.cpp
1 /*******************************************************************\r
2  *\r
3  *      File:           Engine.cpp\r
4  *\r
5  *      Author:         Peter van Sebille (peter@yipton.net)\r
6  *\r
7  *  Modified/adapted for picodriveN by notaz, 2006\r
8  *\r
9  *  (c) Copyright 2006, notaz\r
10  *      (c) Copyright 2002, Peter van Sebille\r
11  *      All Rights Reserved\r
12  *\r
13  *******************************************************************/\r
14 \r
15 \r
16 #include "Engine.h"\r
17 #include <w32std.h>\r
18 #include <eikenv.h>\r
19 #include <e32svr.h>\r
20 #include <e32math.h>\r
21 #include <e32uid.h>\r
22 \r
23 #include <string.h>\r
24 \r
25 #include "version.h"\r
26 #include "../../pico/picoInt.h"\r
27 #include "engine/debug.h"\r
28 #include "app.h"\r
29 \r
30 // this is where we start to break a bunch of symbian rules\r
31 extern TInt machineUid;\r
32 extern int gamestate, gamestate_next;\r
33 extern TPicoConfig *currentConfig;\r
34 extern const char *actionNames[];\r
35 RSemaphore pauseSemaphore;\r
36 RSemaphore initSemaphore;\r
37 const char *RomFileName = 0;\r
38 int pico_was_reset = 0;\r
39 unsigned char *rom_data = 0;\r
40 static CPicolAppView *appView = 0;\r
41 \r
42 \r
43 TInt CPicoGameSession::Do(const TPicoServRqst what, TAny *param)\r
44 {\r
45         switch (what) {\r
46                 case PicoMsgLoadState: \r
47                         if(!rom_data) return -1; // no ROM\r
48                         return saveLoadGame(1);\r
49 \r
50                 case PicoMsgSaveState:\r
51                         if(!rom_data) return -1;\r
52                         return saveLoadGame(0);\r
53 \r
54                 case PicoMsgLoadROM:\r
55                         return loadROM((TPtrC16 *)param);\r
56                 \r
57                 case PicoMsgResume:\r
58                         DEBUGPRINT(_L("resume with rom %08x"), rom_data);\r
59                         if(rom_data) {\r
60                                 return ChangeRunState(PGS_Running);\r
61                         }\r
62                         return 1;\r
63 \r
64                 case PicoMsgReset: \r
65                         if(rom_data) {\r
66                                 PicoReset();\r
67                                 pico_was_reset = 1;\r
68                                 return ChangeRunState(PGS_Running);\r
69                         }\r
70                         return 1;\r
71 \r
72                 case PicoMsgKeys:\r
73                         return ChangeRunState(PGS_KeyConfig);\r
74 \r
75                 case PicoMsgPause:\r
76                         return ChangeRunState(PGS_Paused);\r
77 \r
78                 case PicoMsgQuit:\r
79                         DEBUGPRINT(_L("got quit msg."));\r
80                         return ChangeRunState(PGS_Quit);\r
81 \r
82                 // config change\r
83                 case PicoMsgConfigChange:\r
84                         return changeConfig((TPicoConfig *)param);\r
85 \r
86                 case PicoMsgSetAppView:\r
87                         appView = (CPicolAppView *)param;\r
88                         return 1;\r
89 \r
90                 default:\r
91                         return 1;\r
92         }\r
93 }\r
94 \r
95 TInt EmuThreadFunction(TAny* anArg);\r
96 \r
97 TInt CPicoGameSession::StartEmuThread()\r
98 {\r
99         TInt res=KErrNone;\r
100         iEmuRunning = EFalse;\r
101 \r
102         if (initSemaphore.Handle() > 0)\r
103                 initSemaphore.Close();\r
104         initSemaphore.CreateLocal(0);\r
105         if (pauseSemaphore.Handle() <= 0)\r
106                 pauseSemaphore.CreateLocal(0);\r
107 \r
108         RThread thread;\r
109         if(iThreadWatcher && (res = thread.Open(iThreadWatcher->iTid)) == KErrNone) {\r
110                 // should be a dead thread in some strange state.\r
111                 DEBUGPRINT(_L("found thread with the same id (id=%i, RequestCount=%i), killing.."),\r
112                                 (TInt32)thread.Id(), thread.RequestCount());\r
113                 // what can we do in this situation? Nothing seems to help, it just stays in this state.\r
114                 delete iThreadWatcher;\r
115                 iThreadWatcher = 0;\r
116                 thread.Kill(1);\r
117                 thread.Terminate(1);\r
118                 thread.Close();\r
119         }\r
120 \r
121         //semaphore.CreateLocal(0); // create a semaphore so we know when thread init is finished\r
122         res=thread.Create(_L("PicoEmuThread"),   // create new server thread\r
123                 EmuThreadFunction, // thread's main function\r
124                 KDefaultStackSize,\r
125                 KMinHeapSize,\r
126                 KPicoMaxHeapSize,\r
127                 0 // &semaphore // passed as TAny* argument to thread function\r
128                 );\r
129 \r
130         if(res == KErrNone) { // thread created ok - now start it going\r
131                 thread.SetPriority(EPriorityMore);\r
132                 iEmuRunning = ETrue;\r
133                 if (iThreadWatcher) delete iThreadWatcher;\r
134                 iThreadWatcher = CThreadWatcher::NewL(thread.Id());\r
135                 thread.Resume(); // start it going\r
136                 DEBUGPRINT(_L("initSemaphore.Wait()"));\r
137                 res = initSemaphore.Wait(1000*1000); // wait until it's initialized\r
138                 DEBUGPRINT(_L("initSemaphore resume, ExitReason() == %i"), thread.ExitReason());\r
139                 res |= thread.ExitReason();\r
140                 thread.Close(); // we're no longer interested in the other thread\r
141                 if(res != KErrNone) iEmuRunning = EFalse;\r
142                 return res;\r
143         }\r
144 \r
145         return res;\r
146 }\r
147 \r
148 TInt CPicoGameSession::ChangeRunState(TPicoGameState newstate, TPicoGameState newstate_next)\r
149 {\r
150         if (!iEmuRunning) {\r
151                 gamestate = PGS_Paused;\r
152                 TInt res = StartEmuThread();\r
153                 if(res != KErrNone) DEBUGPRINT(_L("StartEmuThread() returned %i"), res);\r
154                 if (!iEmuRunning) return PicoErrEmuThread;\r
155         }\r
156 \r
157         int oldstate = gamestate;\r
158         gamestate = newstate;\r
159         gamestate_next = newstate_next ? newstate_next : PGS_Paused;\r
160         if (oldstate == PGS_Paused) pauseSemaphore.Signal();\r
161         return 0;\r
162 }\r
163 \r
164 \r
165 TInt CPicoGameSession::loadROM(TPtrC16 *pptr)\r
166 {\r
167         TInt res, i;\r
168         char buff[0x31];\r
169 \r
170         if(rom_data) {\r
171                 // save SRAM for previous ROM\r
172                 if(currentConfig->iFlags & 1)\r
173                         saveLoadGame(0, 1);\r
174         }\r
175 \r
176         RomFileName = 0;\r
177         if(rom_data) {\r
178                 free(rom_data);\r
179                 rom_data = 0;\r
180         }\r
181 \r
182         // read the contents of the client pointer into a TPtr.\r
183         static TBuf8<KMaxFileName> writeBuf;\r
184         writeBuf.Copy(*pptr);\r
185 \r
186         // detect wrong extensions (.srm and .mds)\r
187         TBuf8<5> ext;\r
188         ext.Copy(writeBuf.Right(4));\r
189         ext.LowerCase();\r
190         if(!strcmp((char *)ext.PtrZ(), ".srm") || !strcmp((char *)ext.PtrZ(), "s.gz") || // .mds.gz\r
191            !strcmp((char *)ext.PtrZ(), ".mds")) {\r
192                 return PicoErrNotRom;\r
193         }\r
194 \r
195         FILE *rom = fopen((char *) writeBuf.PtrZ(), "rb");\r
196         if(!rom) {\r
197                 DEBUGPRINT(_L("failed to open rom."));\r
198                 return PicoErrRomOpenFailed;\r
199         }\r
200 \r
201         // make sure emu thread is ok\r
202         res = ChangeRunState(PGS_Paused);\r
203         if(res) {\r
204                 fclose(rom);\r
205                 return res;\r
206         }\r
207 \r
208         unsigned int rom_size = 0;\r
209         // zipfile support\r
210         if(!strcmp((char *)ext.PtrZ(), ".zip")) {\r
211                 fclose(rom);\r
212                 res = CartLoadZip((const char *) writeBuf.PtrZ(), &rom_data, &rom_size);\r
213                 if(res) {\r
214                         DEBUGPRINT(_L("CartLoadZip() failed (%i)"), res);\r
215                         return res;\r
216                 }\r
217         } else {\r
218                 if( (res = PicoCartLoad(rom, &rom_data, &rom_size)) ) {\r
219                         DEBUGPRINT(_L("PicoCartLoad() failed (%i)"), res);\r
220                         fclose(rom);\r
221                         return PicoErrOutOfMem;\r
222                 }\r
223                 fclose(rom);\r
224         }\r
225 \r
226         // detect wrong files (Pico crashes on very small files), also see if ROM EP is good\r
227         if(rom_size <= 0x200 || strncmp((char *)rom_data, "Pico", 4) == 0 ||\r
228           ((*(TUint16 *)(rom_data+4)<<16)|(*(TUint16 *)(rom_data+6))) >= (int)rom_size) {\r
229                 free(rom_data);\r
230                 rom_data = 0;\r
231                 return PicoErrNotRom;\r
232         }\r
233 \r
234         DEBUGPRINT(_L("PicoCartInsert(0x%08X, %d);"), rom_data, rom_size);\r
235         if(PicoCartInsert(rom_data, rom_size)) {\r
236                 return PicoErrOutOfMem;\r
237         }\r
238 \r
239         pico_was_reset = 1;\r
240 \r
241         // global ROM file name for later use\r
242         RomFileName = (const char *) writeBuf.PtrZ();\r
243 \r
244         // name from the ROM itself\r
245         for(i = 0; i < 0x30; i++)\r
246                 buff[i] = rom_data[0x150 + (i^1)]; // unbyteswap\r
247         for(buff[i] = 0, i--; i >= 0; i--) {\r
248                 if(buff[i] != ' ') break;\r
249                 buff[i] = 0;\r
250         }\r
251         TPtrC8 buff8((TUint8*) buff);\r
252         iRomInternalName.Copy(buff8);\r
253 \r
254         // load SRAM for this ROM\r
255         if(currentConfig->iFlags & 1)\r
256                 saveLoadGame(1, 1);\r
257 \r
258         // debug\r
259         #ifdef __DEBUG_PRINT\r
260         TInt cells = User::CountAllocCells();\r
261         TInt mem;\r
262         User::AllocSize(mem);\r
263         DEBUGPRINT(_L("comm:   cels=%d, size=%d KB"), cells, mem/1024);\r
264         ChangeRunState(PGS_DebugHeap, PGS_Running);\r
265         #else\r
266         ChangeRunState(PGS_Running);\r
267         #endif\r
268 \r
269         return 0;\r
270 }\r
271 \r
272 \r
273 TInt CPicoGameSession::changeConfig(TPicoConfig *aConfig)\r
274 {\r
275         DEBUGPRINT(_L("got new config."));\r
276 \r
277         currentConfig = aConfig;\r
278 \r
279         // set PicoOpt and rate\r
280         PicoRegionOverride = currentConfig->PicoRegion;\r
281         PicoOpt = currentConfig->iPicoOpt;\r
282         switch((currentConfig->iFlags>>3)&7) {\r
283                 case 1:  PsndRate=11025; break;\r
284                 case 2:  PsndRate=16000; break;\r
285                 case 3:  PsndRate=22050; break;\r
286                 case 4:  PsndRate=44100; break;\r
287                 default: PsndRate= 8000; break;\r
288         }\r
289 \r
290         // 6 button pad, enable XYZM config if needed\r
291         if(PicoOpt & 0x20) {\r
292                 actionNames[8]  = "Z";\r
293                 actionNames[9]  = "Y";\r
294                 actionNames[10] = "X";\r
295                 actionNames[11] = "MODE";\r
296         } else {\r
297                 actionNames[8] = actionNames[9] = actionNames[10] = actionNames[11] = 0;\r
298         }\r
299 \r
300         // if we are in center 90||270 modes, we can bind renderer switcher\r
301         if(currentConfig->iScreenMode == TPicoConfig::PMFit &&\r
302                 (currentConfig->iScreenRotation == TPicoConfig::PRot0 || currentConfig->iScreenRotation == TPicoConfig::PRot180))\r
303                                  actionNames[25] = 0;\r
304                         else actionNames[25] = "RENDERER";\r
305 \r
306         return 0;\r
307 }\r
308 \r
309 \r
310 void MainOldCleanup(); // from main.cpp\r
311 #ifdef __DEBUG_PRINT_FILE\r
312 extern RMutex logMutex;\r
313 #endif\r
314 \r
315 void CPicoGameSession::freeResources()\r
316 {\r
317         RThread thread;\r
318         TInt i;\r
319 \r
320         DEBUGPRINT(_L("CPicoGameSession::freeResources()"));\r
321 \r
322         if(iThreadWatcher && thread.Open(iThreadWatcher->iTid) == KErrNone)\r
323         {\r
324                 // try to stop our emu thread\r
325                 gamestate = PGS_Quit;\r
326                 if(pauseSemaphore.Handle() > 0)\r
327                         pauseSemaphore.Signal();\r
328 \r
329                 if(thread.Handle() > 0)\r
330                 {\r
331                         // tried reopening thread handle here over time intervals to detect if thread is alive,\r
332                         // but would run into handle panics.\r
333 \r
334                         for(i = 0; i < 8; i++) {\r
335                                 User::After(100 * 1000);\r
336                                 if(thread.ExitReason() != 0) break;\r
337                         }\r
338 \r
339                         if(thread.ExitReason() == 0) {\r
340                                 // too late, time to die\r
341                                 DEBUGPRINT(_L("thread %i not responding, killing.."), (TInt32) thread.Id());\r
342                                 thread.Terminate(1);\r
343                         }\r
344                         thread.Close();\r
345                 }\r
346 \r
347         }\r
348 \r
349         if(iThreadWatcher != NULL)\r
350         {\r
351                 DEBUGPRINT(_L("delete iThreadWatcher"));\r
352                 delete iThreadWatcher;\r
353                 DEBUGPRINT(_L("after delete iThreadWatcher"));\r
354                 iThreadWatcher = NULL;\r
355         }\r
356 \r
357         MainOldCleanup();\r
358 \r
359         if (initSemaphore.Handle() > 0)\r
360                 initSemaphore.Close();\r
361         if (pauseSemaphore.Handle() > 0)\r
362                 pauseSemaphore.Close();\r
363 #ifdef __DEBUG_PRINT_FILE\r
364         if (logMutex.Handle() > 0)\r
365                 logMutex.Close();\r
366 #endif\r
367 }\r
368 \r
369 TBool CPicoGameSession::iEmuRunning = EFalse;\r
370 CThreadWatcher *CPicoGameSession::iThreadWatcher = 0;\r
371 TBuf<0x30> CPicoGameSession::iRomInternalName;\r
372 \r
373 \r
374 void TPicoConfig::SetDefaults()\r
375 {\r
376         iLastROMFile.SetLength(0);\r
377         iScreenRotation = PRot270;\r
378         iScreenMode     = PMCenter;\r
379         iFlags          = 1; // use_sram\r
380         iPicoOpt        = 0; // all off\r
381         iFrameskip      = PFSkipAuto;\r
382 \r
383         Mem::FillZ(iKeyBinds,  sizeof(iKeyBinds));\r
384         Mem::FillZ(iAreaBinds, sizeof(iAreaBinds));\r
385         iKeyBinds[0xd5] = 1<<26; // bind back\r
386 }\r
387 \r
388 // load config\r
389 void TPicoConfig::InternalizeL(RReadStream &aStream)\r
390 {\r
391         TInt32 version, fname_len;\r
392         version = aStream.ReadInt32L();\r
393         fname_len       = aStream.ReadInt32L();\r
394 \r
395         // not sure if this is safe\r
396         iLastROMFile.SetMax();\r
397         aStream.ReadL((TUint8 *) iLastROMFile.Ptr(), KMaxFileName*2);\r
398         iLastROMFile.SetLength(fname_len);\r
399 \r
400         iScreenRotation = aStream.ReadInt32L();\r
401         iScreenMode     = aStream.ReadInt32L();\r
402         iFlags          = aStream.ReadUint32L();\r
403         iPicoOpt        = aStream.ReadInt32L();\r
404         iFrameskip      = aStream.ReadInt32L();\r
405 \r
406         aStream.ReadL((TUint8 *)iKeyBinds,  sizeof(iKeyBinds));\r
407         aStream.ReadL((TUint8 *)iAreaBinds, sizeof(iAreaBinds));\r
408 \r
409         PicoRegion      = aStream.ReadInt32L();\r
410 }\r
411 \r
412 // save config\r
413 void TPicoConfig::ExternalizeL(RWriteStream &aStream) const\r
414 {\r
415         TInt version = (KPicoMajorVersionNumber<<24)+(KPicoMinorVersionNumber<<16);\r
416 \r
417         aStream.WriteInt32L(version);\r
418         aStream.WriteInt32L(iLastROMFile.Length());\r
419         aStream.WriteL((const TUint8 *)iLastROMFile.Ptr(), KMaxFileName*2);\r
420 \r
421         aStream.WriteInt32L(iScreenRotation);\r
422         aStream.WriteInt32L(iScreenMode);\r
423         aStream.WriteUint32L(iFlags);\r
424         aStream.WriteInt32L(iPicoOpt);\r
425         aStream.WriteInt32L(iFrameskip);\r
426 \r
427         aStream.WriteL((const TUint8 *)iKeyBinds,  sizeof(iKeyBinds));\r
428         aStream.WriteL((const TUint8 *)iAreaBinds, sizeof(iAreaBinds));\r
429 \r
430         aStream.WriteInt32L(PicoRegion);\r
431 }\r
432 \r
433 \r
434 // CThreadWatcher\r
435 CThreadWatcher::~CThreadWatcher()\r
436 {\r
437         Cancel();\r
438         DEBUGPRINT(_L("after CThreadWatcher::Cancel();"));\r
439 }\r
440 \r
441 CThreadWatcher::CThreadWatcher(const TThreadId& aTid)\r
442 : CActive(CActive::EPriorityStandard), iTid(aTid)\r
443 {\r
444 }\r
445 \r
446 \r
447 CThreadWatcher* CThreadWatcher::NewL(const TThreadId& aTid)\r
448 {\r
449         CThreadWatcher* self = new(ELeave) CThreadWatcher(aTid);\r
450         CleanupStack::PushL(self);\r
451         self->ConstructL();\r
452         CleanupStack::Pop();                    // self\r
453         return self;\r
454 }\r
455 \r
456 void CThreadWatcher::ConstructL()\r
457 {\r
458         CActiveScheduler::Add(this);\r
459         RThread thread;\r
460         if(thread.Open(iTid) == KErrNone) {\r
461                 thread.Logon(iStatus);\r
462                 thread.Close();\r
463                 SetActive();\r
464         }\r
465 }\r
466 \r
467 void CThreadWatcher::RunL()\r
468 {\r
469         DEBUGPRINT(_L("CThreadWatcher::RunL()"));\r
470         CPicoGameSession::iEmuRunning = EFalse;\r
471         if(appView) appView->UpdateCommandList();\r
472         //initSemaphore.Signal(); // no point to do that here, AS can't get here if it is waiting\r
473 }\r
474 \r
475 void CThreadWatcher::DoCancel()\r
476 {\r
477         RThread thread;\r
478         DEBUGPRINT(_L("CThreadWatcher::DoCancel()"));\r
479         if(thread.Open(iTid) == KErrNone) {\r
480                 DEBUGPRINT(_L("thread.LogonCancel(iStatus);"));\r
481                 thread.LogonCancel(iStatus);\r
482                 thread.Close();\r
483         }\r
484 }\r