1 #include <QString>
2 #include <QVector>
3 #include <QDir>
4 #include <QFile>
5 #include <QFileInfo>
6 #include <QByteArray>
7 #include <QDateTime>
8 #include <QDate>
9 #include <QTime>
10
11 #include <new>
12 #include <chrono>
13 #include <thread>
14 #include <string>
15 #include <stdint.h>
16 #include <string.h>
17 #include <time.h>
18
19 #include "emuwrapper.h"
20 #include "../../src/emulator.h"
21 #include "../../src/fileLauncher/launcher.h"
22
23 extern "C"{
24 #include "../../src/flx68000.h"
25 #include "../../src/m68k/m68k.h"
26 #include "../../src/pxa260/pxa260.h"
27 #include "../../src/armv5te/disasm.h"
28 }
29
30
31 #define MAX_LOG_ENTRYS 2000
32 #define MAX_LOG_ENTRY_LENGTH 200
33
34
35 static bool alreadyExists = false;//there can only be one of this class since it wrappers C code
36
37 static QVector<QString> debugStrings;
38 static uint64_t debugDeletedStrings;
39 static QVector<uint64_t> debugDuplicateCallCount;
40 static bool debugAbort = false;
41 static QString debugAbortString = "";
42 uint32_t frontendDebugStringSize;
43 char* frontendDebugString;
44
45
frontendHandleDebugPrint()46 void frontendHandleDebugPrint(){
47 QString newDebugString = frontendDebugString;
48
49 //lock out logs to capture a specific section
50 if(debugAbort)
51 return;
52
53 if(!debugAbortString.isEmpty() && newDebugString.contains(debugAbortString))
54 debugAbort = true;
55
56 //this debug handler doesnt need the \n
57 if(newDebugString.back() == '\n')
58 newDebugString.remove(newDebugString.length() - 1, 1);
59 else
60 newDebugString.append("MISSING \"\\n\"");
61
62 if(!debugStrings.empty() && newDebugString == debugStrings.back()){
63 debugDuplicateCallCount.back()++;
64 }
65 else{
66 debugStrings.push_back(newDebugString);
67 debugDuplicateCallCount.push_back(1);
68 }
69
70 while(debugStrings.size() > MAX_LOG_ENTRYS){
71 debugStrings.remove(0);
72 debugDuplicateCallCount.remove(0);
73 debugDeletedStrings++;
74 }
75 }
76
frontendGetCurrentTime(uint8_t * writeBack)77 static void frontendGetCurrentTime(uint8_t* writeBack){
78 time_t rawTime;
79 struct tm* timeInfo;
80
81 time(&rawTime);
82 timeInfo = localtime(&rawTime);
83
84 writeBack[0] = timeInfo->tm_hour;
85 writeBack[1] = timeInfo->tm_min;
86 writeBack[2] = timeInfo->tm_sec;
87 }
88
89
EmuWrapper()90 EmuWrapper::EmuWrapper(){
91 if(alreadyExists == true)
92 throw std::bad_alloc();
93 alreadyExists = true;
94
95 emuInited = false;
96 emuThreadJoin = false;
97 emuRunning = false;
98 emuPaused = false;
99 emuNewFrameReady = false;
100
101 frontendDebugString = new char[MAX_LOG_ENTRY_LENGTH];
102 frontendDebugStringSize = MAX_LOG_ENTRY_LENGTH;
103 }
104
~EmuWrapper()105 EmuWrapper::~EmuWrapper(){
106 if(emuInited)
107 exit();
108
109 delete[] frontendDebugString;
110 frontendDebugStringSize = 0;
111 debugStrings.clear();
112 debugDuplicateCallCount.clear();
113
114 //allow creating a new emu class after the old one is closed
115 alreadyExists = false;
116 }
117
emuThreadRun()118 void EmuWrapper::emuThreadRun(){
119 while(!emuThreadJoin){
120 if(emuRunning){
121 emuPaused = false;
122 if(!emuNewFrameReady){
123 palmInput = emuInput;
124 emulatorRunFrame();
125 emuNewFrameReady = true;
126 }
127 }
128 else{
129 emuPaused = true;
130 }
131
132 std::this_thread::sleep_for(std::chrono::milliseconds(5));
133 }
134 }
135
writeOutSaves()136 void EmuWrapper::writeOutSaves(){
137 if(emuRamFilePath != ""){
138 QFile ramFile(emuRamFilePath);
139 uint32_t emuRamSize = emulatorGetRamSize();
140 uint8_t* emuRamData = new uint8_t[emuRamSize];
141
142 emulatorSaveRam(emuRamData, emuRamSize);
143
144 //save out RAM before exit
145 if(ramFile.open(QFile::WriteOnly | QFile::Truncate)){
146 ramFile.write((const char*)emuRamData, emuRamSize);
147 ramFile.close();
148 }
149
150 delete[] emuRamData;
151 }
152 if(emuSdCardFilePath != ""){
153 uint32_t emuSdCardSize = emulatorGetSdCardSize();
154 uint8_t* emuSdCardData = new uint8_t[emuSdCardSize];
155
156 if(emulatorGetSdCardData(emuSdCardData, emuSdCardSize) == EMU_ERROR_NONE){
157 QFile sdCardFile(emuSdCardFilePath);
158
159 //save out SD card before exit
160 if(sdCardFile.open(QFile::WriteOnly | QFile::Truncate)){
161 sdCardFile.write((const char*)emuSdCardData, emuSdCardSize);
162 sdCardFile.close();
163 }
164 }
165 }
166 }
167
init(const QString & assetPath,const QString & osVersion,bool syncRtc,bool allowInvalidBehavior,bool fastBoot)168 uint32_t EmuWrapper::init(const QString& assetPath, const QString& osVersion, bool syncRtc, bool allowInvalidBehavior, bool fastBoot){
169 if(!emuRunning && !emuInited){
170 //start emu
171 uint32_t error;
172 uint8_t deviceModel;
173 bool hasBootloader = true;
174
175 if(osVersion == "Palm m500/Palm OS 4.0")
176 emuOsName = "palmos40-en-m500";
177 else if(osVersion == "Palm m515/Palm OS 4.1")
178 emuOsName = "palmos41-en-m515";
179 else if(osVersion == "Tungsten T3/Palm OS 5.2.1")
180 emuOsName = "palmos52-en-t3";
181 else if(osVersion == "Tungsten T3/Palm OS 5.2.1")
182 emuOsName = "palmos60-en-t3";
183 else
184 return EMU_ERROR_INVALID_PARAMETER;
185
186 if(osVersion.contains("Palm m500"))
187 deviceModel = EMU_DEVICE_PALM_M500;
188 else if(osVersion.contains("Palm m515"))
189 deviceModel = EMU_DEVICE_PALM_M515;
190 else if(osVersion.contains("Tungsten T3"))
191 deviceModel = EMU_DEVICE_TUNGSTEN_T3;
192 else
193 return EMU_ERROR_INVALID_PARAMETER;
194
195 QFile romFile(assetPath + "/" + emuOsName + ".rom");
196 QFile bootloaderFile(assetPath + "/bootloader-dbvz.rom");
197 QFile ramFile(assetPath + "/userdata-" + emuOsName + ".ram");
198 QFile sdCardFile(assetPath + "/sd-" + emuOsName + ".img");
199
200 if(!romFile.open(QFile::ReadOnly | QFile::ExistingOnly))
201 return EMU_ERROR_INVALID_PARAMETER;
202
203 if(deviceModel == EMU_DEVICE_TUNGSTEN_T3 || !bootloaderFile.open(QFile::ReadOnly | QFile::ExistingOnly))
204 hasBootloader = false;
205
206 error = emulatorInit(deviceModel, (uint8_t*)romFile.readAll().data(), romFile.size(), hasBootloader ? (uint8_t*)bootloaderFile.readAll().data() : NULL, hasBootloader ? bootloaderFile.size() : 0, syncRtc, allowInvalidBehavior);
207 if(error == EMU_ERROR_NONE){
208 QTime now = QTime::currentTime();
209
210 palmGetRtcFromHost = frontendGetCurrentTime;
211 emulatorSetRtc(QDate::currentDate().day(), now.hour(), now.minute(), now.second());
212
213 if(ramFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
214 emulatorLoadRam((uint8_t*)ramFile.readAll().data(), ramFile.size());
215 ramFile.close();
216 }
217
218 if(sdCardFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
219 emulatorInsertSdCard((uint8_t*)sdCardFile.readAll().data(), sdCardFile.size(), NULL);
220 sdCardFile.close();
221 }
222
223 emuInput = palmInput;
224 emuRamFilePath = assetPath + "/userdata-" + emuOsName + ".ram";
225 emuSdCardFilePath = assetPath + "/sd-" + emuOsName + ".img";
226 emuSaveStatePath = assetPath + "/states-" + emuOsName + ".states";
227
228 //make the place to store the saves
229 QDir(emuSaveStatePath).mkdir(".");
230
231 //skip the boot screen
232 if(fastBoot)
233 launcherBootInstantly(ramFile.exists());
234
235 //start the thread
236 emuThreadJoin = false;
237 emuInited = true;
238 emuRunning = true;
239 emuPaused = false;
240 emuNewFrameReady = false;
241 emuThread = std::thread(&EmuWrapper::emuThreadRun, this);
242 }
243 else{
244 return error;
245 }
246
247 romFile.close();
248 bootloaderFile.close();
249 }
250
251 return EMU_ERROR_NONE;
252 }
253
exit()254 void EmuWrapper::exit(){
255 emuThreadJoin = true;
256 emuRunning = false;
257 if(emuThread.joinable())
258 emuThread.join();
259 if(emuInited){
260 writeOutSaves();
261 emulatorDeinit();
262 }
263 }
264
pause()265 void EmuWrapper::pause(){
266 if(emuInited){
267 emuRunning = false;
268 while(!emuPaused)
269 std::this_thread::sleep_for(std::chrono::milliseconds(1));
270 }
271 }
272
resume()273 void EmuWrapper::resume(){
274 if(emuInited){
275 emuRunning = true;
276 while(emuPaused)
277 std::this_thread::sleep_for(std::chrono::milliseconds(1));
278 }
279 }
280
reset(bool hard)281 void EmuWrapper::reset(bool hard){
282 if(emuInited){
283 bool wasPaused = isPaused();
284
285 if(!wasPaused)
286 pause();
287
288 if(hard)
289 emulatorHardReset();
290 else
291 emulatorSoftReset();
292
293 if(!wasPaused)
294 resume();
295 }
296 }
297
setCpuSpeed(double speed)298 void EmuWrapper::setCpuSpeed(double speed){
299 if(emuInited){
300 bool wasPaused = isPaused();
301
302 if(!wasPaused)
303 pause();
304
305 emulatorSetCpuSpeed(speed);
306
307 if(!wasPaused)
308 resume();
309 }
310 }
311
bootFromFile(const QString & mainPath)312 uint32_t EmuWrapper::bootFromFile(const QString& mainPath){
313 bool wasPaused = isPaused();
314 uint32_t error = EMU_ERROR_NONE;
315 QFileInfo pathInfo(mainPath);
316 QFile appFile(mainPath);
317 QFile ramFile(mainPath + "." + emuOsName + ".ram");
318 QFile sdCardFile(mainPath + "." + emuOsName + ".sd.img");
319 QString suffix = QFileInfo(mainPath).suffix().toLower();
320 bool hasSaveRam;
321 bool hasSaveSdCard;
322
323 if(!wasPaused)
324 pause();
325
326 //save the current data for the last program launched, or the standard device image if none where launched
327 writeOutSaves();
328
329 //its OK if these fail, the buffer will just be NULL, 0 if they do
330 hasSaveRam = ramFile.open(QFile::ReadOnly | QFile::ExistingOnly);
331 hasSaveSdCard = suffix != "img" ? sdCardFile.open(QFile::ReadOnly | QFile::ExistingOnly) : false;
332
333 //fully clear the emu
334 emulatorEjectSdCard();
335 emulatorHardReset();
336
337 if(hasSaveRam)
338 emulatorLoadRam((uint8_t*)ramFile.readAll().data(), ramFile.size());
339 if(hasSaveSdCard)
340 emulatorInsertSdCard((uint8_t*)sdCardFile.readAll().data(), sdCardFile.size(), NULL);
341
342 //its OK if these fail
343 if(hasSaveRam)
344 ramFile.close();
345 if(hasSaveSdCard)
346 sdCardFile.close();
347
348 launcherBootInstantly(hasSaveRam);
349
350 if(appFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
351 QByteArray fileBuffer = appFile.readAll();
352
353 appFile.close();
354
355 if(suffix == "img"){
356 QFile infoFile(mainPath.mid(0, mainPath.length() - 3) + "info");//swap "img" for "info"
357 sd_card_info_t sdInfo;
358
359 memset(&sdInfo, 0x00, sizeof(sdInfo));
360
361 if(infoFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
362 launcherGetSdCardInfoFromInfoFile((uint8_t*)infoFile.readAll().data(), infoFile.size(), &sdInfo);
363 infoFile.close();
364 }
365
366 error = emulatorInsertSdCard((uint8_t*)fileBuffer.data(), fileBuffer.size(), &sdInfo);
367 if(error != EMU_ERROR_NONE)
368 goto errorOccurred;
369 }
370 else{
371 if(!hasSaveRam){
372 error = launcherInstallFile((uint8_t*)fileBuffer.data(), fileBuffer.size());
373 if(error != EMU_ERROR_NONE)
374 goto errorOccurred;
375 }
376 error = launcherExecute(launcherGetAppId((uint8_t*)fileBuffer.data(), fileBuffer.size()));
377 if(error != EMU_ERROR_NONE)
378 goto errorOccurred;
379 }
380 }
381
382 //everything worked, set output save files
383 emuRamFilePath = mainPath + "." + emuOsName + ".ram";
384 emuSdCardFilePath = suffix != "img" ? mainPath + "." + emuOsName + ".sd.img" : "";//dont duplicate booted SD card images
385 emuSaveStatePath = mainPath + "." + emuOsName + ".states";
386
387 //make the place to store the saves
388 QDir(emuSaveStatePath).mkdir(".");
389
390 //need this goto because the emulator must be released before returning
391 errorOccurred:
392 if(error != EMU_ERROR_NONE){
393 //try and recover from error
394 emulatorEjectSdCard();
395 emulatorHardReset();
396 }
397
398 if(!wasPaused)
399 resume();
400
401 return error;
402 }
403
installApplication(const QString & path)404 uint32_t EmuWrapper::installApplication(const QString& path){
405 bool wasPaused = isPaused();
406 uint32_t error = EMU_ERROR_INVALID_PARAMETER;
407 QFile appFile(path);
408
409 if(!wasPaused)
410 pause();
411
412 if(appFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
413 error = launcherInstallFile((uint8_t*)appFile.readAll().data(), appFile.size());
414 appFile.close();
415 }
416
417 if(!wasPaused)
418 resume();
419
420 return error;
421 }
422
saveState(const QString & name)423 uint32_t EmuWrapper::saveState(const QString& name){
424 bool wasPaused = isPaused();
425 uint32_t error = EMU_ERROR_INVALID_PARAMETER;
426 QFile stateFile(emuSaveStatePath + "/" + name + ".state");
427
428 if(!wasPaused)
429 pause();
430
431 //save here
432 if(stateFile.open(QFile::WriteOnly)){
433 uint32_t stateSize = emulatorGetStateSize();
434 uint8_t* stateData = new uint8_t[stateSize];
435
436 emulatorSaveState(stateData, stateSize);//no need to check for errors since the buffer is always the right size
437 stateFile.write((const char*)stateData, stateSize);
438 stateFile.close();
439
440 error = EMU_ERROR_NONE;
441 }
442
443 if(!wasPaused)
444 resume();
445
446 return error;
447 }
448
loadState(const QString & name)449 uint32_t EmuWrapper::loadState(const QString& name){
450 bool wasPaused = isPaused();
451 uint32_t error = EMU_ERROR_INVALID_PARAMETER;
452 QFile stateFile(emuSaveStatePath + "/" + name + ".state");
453
454 if(!wasPaused)
455 pause();
456
457 if(stateFile.open(QFile::ReadOnly | QFile::ExistingOnly)){
458 if(emulatorLoadState((uint8_t*)stateFile.readAll().data(), stateFile.size()))
459 error = EMU_ERROR_NONE;
460 stateFile.close();
461
462 }
463
464 if(!wasPaused)
465 resume();
466
467 return error;
468 }
469
setPenValue(float x,float y,bool touched)470 void EmuWrapper::setPenValue(float x, float y, bool touched){
471 emuInput.touchscreenX = x;
472 emuInput.touchscreenY = y;
473 emuInput.touchscreenTouched = touched;
474 }
475
setKeyValue(uint8_t key,bool pressed)476 void EmuWrapper::setKeyValue(uint8_t key, bool pressed){
477 switch(key){
478 case BUTTON_UP:
479 emuInput.buttonUp = pressed;
480 break;
481
482 case BUTTON_DOWN:
483 emuInput.buttonDown = pressed;
484 break;
485
486 case BUTTON_LEFT:
487 emuInput.buttonLeft = pressed;
488 break;
489
490 case BUTTON_RIGHT:
491 emuInput.buttonRight = pressed;
492 break;
493
494 case BUTTON_CENTER:
495 emuInput.buttonCenter = pressed;
496 break;
497
498 case BUTTON_CALENDAR:
499 emuInput.buttonCalendar = pressed;
500 break;
501
502 case BUTTON_ADDRESS:
503 emuInput.buttonAddress = pressed;
504 break;
505
506 case BUTTON_TODO:
507 emuInput.buttonTodo = pressed;
508 break;
509
510 case BUTTON_NOTES:
511 emuInput.buttonNotes = pressed;
512 break;
513
514 case BUTTON_VOICE_MEMO:
515 emuInput.buttonVoiceMemo = pressed;
516 break;
517
518 case BUTTON_POWER:
519 emuInput.buttonPower = pressed;
520 break;
521
522 default:
523 break;
524 }
525 }
526
debugLogEntrys()527 QVector<QString>& EmuWrapper::debugLogEntrys(){
528 return debugStrings;
529 }
530
debugDuplicateLogEntryCount()531 QVector<uint64_t>& EmuWrapper::debugDuplicateLogEntryCount(){
532 return debugDuplicateCallCount;
533 }
534
debugDeletedLogEntryCount()535 uint64_t& EmuWrapper::debugDeletedLogEntryCount(){
536 return debugDeletedStrings;
537 }
538
debugGetCpuRegisterString()539 QString EmuWrapper::debugGetCpuRegisterString(){
540 QString regString = "";
541
542 if(palmEmulatingTungstenT3){
543 for(uint8_t regs = 0; regs < 16; regs++)
544 regString += QString::asprintf("R%d:0x%08X\n", regs, pxa260GetRegister(regs));
545 regString += QString::asprintf("SP:0x%08X\n", pxa260GetRegister(13));
546 regString += QString::asprintf("LR:0x%08X\n", pxa260GetRegister(14));
547 regString += QString::asprintf("PC:0x%08X\n", pxa260GetPc());
548 regString += QString::asprintf("CPSR:0x%08X\n", pxa260GetCpsr());
549 regString += QString::asprintf("SPSR:0x%08X", pxa260GetSpsr());
550 }
551 else{
552 for(uint8_t dRegs = 0; dRegs < 8; dRegs++)
553 regString += QString::asprintf("D%d:0x%08X\n", dRegs, flx68000GetRegister(dRegs));
554 for(uint8_t aRegs = 0; aRegs < 8; aRegs++)
555 regString += QString::asprintf("A%d:0x%08X\n", aRegs, flx68000GetRegister(8 + aRegs));
556 regString += QString::asprintf("SP:0x%08X\n", flx68000GetRegister(15));
557 regString += QString::asprintf("PC:0x%08X\n", flx68000GetPc());
558 regString += QString::asprintf("SR:0x%04X", flx68000GetStatusRegister());
559 }
560
561 return regString;
562 }
563
debugGetEmulatorMemory(uint32_t address,uint8_t size)564 uint64_t EmuWrapper::debugGetEmulatorMemory(uint32_t address, uint8_t size){
565 if(palmEmulatingTungstenT3)
566 return pxa260ReadArbitraryMemory(address, size);
567 return flx68000ReadArbitraryMemory(address, size);
568 }
569
debugDisassemble(uint32_t address,uint32_t opcodes)570 QString EmuWrapper::debugDisassemble(uint32_t address, uint32_t opcodes){
571 QString output = "";
572
573 if(palmEmulatingTungstenT3){
574 for(uint32_t index = 0; index < opcodes; index++){
575 address += disasm_arm_insn(address);
576 output += disasmReturnBuf;
577 output += '\n';
578 }
579 }
580 else{
581 char temp[100];
582
583 for(uint32_t index = 0; index < opcodes; index++){
584 uint8_t opcodeSize = m68k_disassemble(temp, address, M68K_CPU_TYPE_DBVZ);
585 QString opcodeHex = "";
586
587 for(uint8_t opcodeByteIndex = 0; opcodeByteIndex < opcodeSize; opcodeByteIndex++)
588 opcodeHex += QString::asprintf("%02X", flx68000ReadArbitraryMemory(address + opcodeByteIndex, 8));
589
590 output += QString::asprintf("0x%08X: 0x%s\t%s \n", address, opcodeHex.toStdString().c_str(), temp);
591
592 address += opcodeSize;
593 }
594 }
595
596 return output;
597 }
598