cc68a136 |
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(0);\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 |