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