/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2020 thor * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // QtScriptManager.cpp // #ifdef __FCEU_QSCRIPT_ENABLE__ #include #include #include #ifdef WIN32 #include #endif #include #include #include #include #include #include #ifdef __QT_UI_TOOLS__ #include #endif #include "../../fceu.h" #include "../../movie.h" #include "../../video.h" #include "../../x6502.h" #include "../../debug.h" #include "../../state.h" #include "../../ppu.h" #include "common/os_utils.h" #include "utils/xstring.h" #include "Qt/QtScriptManager.h" #include "Qt/main.h" #include "Qt/input.h" #include "Qt/config.h" #include "Qt/keyscan.h" #include "Qt/fceuWrapper.h" #include "Qt/ConsoleUtilities.h" #include "Qt/ConsoleWindow.h" // pix format for JS graphics #define BUILD_PIXEL_ARGB8888(A,R,G,B) (((int) (A) << 24) | ((int) (R) << 16) | ((int) (G) << 8) | (int) (B)) #define DECOMPOSE_PIXEL_ARGB8888(PIX,A,R,G,B) { (A) = ((PIX) >> 24) & 0xff; (R) = ((PIX) >> 16) & 0xff; (G) = ((PIX) >> 8) & 0xff; (B) = (PIX) & 0xff; } #define JS_BUILD_PIXEL BUILD_PIXEL_ARGB8888 #define JS_DECOMPOSE_PIXEL DECOMPOSE_PIXEL_ARGB8888 #define JS_PIXEL_A(PIX) (((PIX) >> 24) & 0xff) #define JS_PIXEL_R(PIX) (((PIX) >> 16) & 0xff) #define JS_PIXEL_G(PIX) (((PIX) >> 8) & 0xff) #define JS_PIXEL_B(PIX) ((PIX) & 0xff) // File Base Name from Core extern char FileBase[]; extern uint8 joy[4]; extern uint32 GetGamepadPressedImmediate(); static thread_local FCEU::JSEngine* currentEngine = nullptr; namespace JS { //---------------------------------------------------- //---- Color Object //---------------------------------------------------- int ColorScriptObject::numInstances = 0; ColorScriptObject::ColorScriptObject(int r, int g, int b) : QObject(), color(r, g, b), _palette(0) { numInstances++; //printf("ColorScriptObject(r,g,b) %p Constructor: %i\n", this, numInstances); moveToThread(QApplication::instance()->thread()); } //---------------------------------------------------- ColorScriptObject::~ColorScriptObject() { numInstances--; //printf("ColorScriptObject %p Destructor: %i\n", this, numInstances); } //---------------------------------------------------- //---- Joypad Object //---------------------------------------------------- int JoypadScriptObject::numInstances = 0; JoypadScriptObject::JoypadScriptObject(int playerIdx, bool immediate) : QObject() { numInstances++; //printf("JoypadScriptObject %p Constructor: %i\n", this, numInstances); moveToThread(QApplication::instance()->thread()); if ( (playerIdx < 0) || (playerIdx >= MAX_JOYPAD_PLAYERS) ) { QString msg = "Error: Joypad player index (" + QString::number(playerIdx) + ") is out of bounds!\n"; auto* engine = FCEU::JSEngine::getCurrent(); if (engine != nullptr) { engine->throwError(QJSValue::RangeError, msg); } playerIdx = 0; } player = playerIdx; refreshData(immediate); } //---------------------------------------------------- JoypadScriptObject::~JoypadScriptObject() { numInstances--; //printf("JoypadScriptObject %p Destructor: %i\n", this, numInstances); } //---------------------------------------------------- void JoypadScriptObject::refreshData(bool immediate) { if (immediate) { uint32_t gpData = GetGamepadPressedImmediate(); uint8_t buttons = gpData >> (player * 8); a = (buttons & 0x01) ? true : false; b = (buttons & 0x02) ? true : false; select = (buttons & 0x04) ? true : false; start = (buttons & 0x08) ? true : false; up = (buttons & 0x10) ? true : false; down = (buttons & 0x20) ? true : false; left = (buttons & 0x40) ? true : false; right = (buttons & 0x80) ? true : false; } else { uint8_t buttons = joy[player]; a = (buttons & 0x01) ? true : false; b = (buttons & 0x02) ? true : false; select = (buttons & 0x04) ? true : false; start = (buttons & 0x08) ? true : false; up = (buttons & 0x10) ? true : false; down = (buttons & 0x20) ? true : false; left = (buttons & 0x40) ? true : false; right = (buttons & 0x80) ? true : false; } } //---------------------------------------------------- //---- EMU State Object //---------------------------------------------------- int EmuStateScriptObject::numInstances = 0; //---------------------------------------------------- EmuStateScriptObject::EmuStateScriptObject(const QJSValue& jsArg1, const QJSValue& jsArg2) { numInstances++; //printf("EmuStateScriptObject %p JS Constructor: %i\n", this, numInstances); moveToThread(QApplication::instance()->thread()); QJSValueList args = { jsArg1, jsArg2 }; for (auto& jsVal : args) { if (jsVal.isObject()) { //printf("EmuStateScriptObject %p JS Constructor(Obj): %i\n", this, numInstances); auto obj = qobject_cast(jsVal.toQObject()); if (obj != nullptr) { *this = *obj; } } else if (jsVal.isNumber()) { //printf("EmuStateScriptObject %p JS Constructor(int): %i\n", this, numInstances); setSlot(jsVal.toInt()); if ( (slot >= 0) && saveFileExists()) { loadFromFile(filename); } } } } //---------------------------------------------------- EmuStateScriptObject::~EmuStateScriptObject() { if (data != nullptr) { if (persist) { saveToFile(filename); } delete data; data = nullptr; } numInstances--; //printf("EmuStateScriptObject %p Destructor: %i\n", this, numInstances); } //---------------------------------------------------- EmuStateScriptObject& EmuStateScriptObject::operator= (const EmuStateScriptObject& obj) { setSlot( obj.slot ); persist = obj.persist; compression = obj.compression; filename = obj.filename; //printf("EmuStateScriptObject Copy Assignment: %p\n", this); if (obj.data != nullptr) { data = new EMUFILE_MEMORY(obj.data->size()); memcpy( data->buf(), obj.data->buf(), obj.data->size()); } return *this; } //---------------------------------------------------- void EmuStateScriptObject::setSlot(int value) { slot = value; if (slot >= 0) { slot = slot % 10; std::string fileString = FCEU_MakeFName(FCEUMKF_STATE, slot, 0); filename = QString::fromStdString(fileString); } } //---------------------------------------------------- bool EmuStateScriptObject::save() { if (data != nullptr) { delete data; data = nullptr; } data = new EMUFILE_MEMORY(); FCEU_WRAPPER_LOCK(); FCEUSS_SaveMS( data, compression); data->fseek(0,SEEK_SET); FCEU_WRAPPER_UNLOCK(); if (persist) { saveToFile(filename); } return true; } //---------------------------------------------------- bool EmuStateScriptObject::load() { bool loaded = false; if (data != nullptr) { FCEU_WRAPPER_LOCK(); if (FCEUSS_LoadFP( data, SSLOADPARAM_NOBACKUP)) { data->fseek(0,SEEK_SET); loaded = true; } FCEU_WRAPPER_UNLOCK(); } return loaded; } //---------------------------------------------------- bool EmuStateScriptObject::saveToFile(const QString& filepath) { if (filepath.isEmpty()) { return false; } if (data == nullptr) { return false; } FILE* outf = fopen(filepath.toLocal8Bit().data(),"wb"); if (outf == nullptr) { return false; } fwrite( data->buf(), 1, data->size(), outf); fclose(outf); return true; } //---------------------------------------------------- bool EmuStateScriptObject::loadFromFile(const QString& filepath) { if (filepath.isEmpty()) { return false; } if (data != nullptr) { delete data; data = nullptr; } FILE* inf = fopen(filepath.toLocal8Bit().data(),"rb"); if (inf == nullptr) { QString msg = "JS EmuState::loadFromFile failed to open file: " + filepath; logMessage(FCEU::JSEngine::WARNING, msg); return false; } fseek(inf,0,SEEK_END); long int len = ftell(inf); fseek(inf,0,SEEK_SET); data = new EMUFILE_MEMORY(len); if ( fread(data->buf(),1,len,inf) != static_cast(len) ) { QString msg = "JS EmuState::loadFromFile failed to load full buffer."; logMessage(FCEU::JSEngine::WARNING, msg); delete data; data = nullptr; } fclose(inf); return true; } //---------------------------------------------------- void EmuStateScriptObject::logMessage(int lvl, QString& msg) { auto* engine = FCEU::JSEngine::getCurrent(); if (engine != nullptr) { engine->logMessage(lvl, msg); } } //---------------------------------------------------- bool EmuStateScriptObject::saveFileExists() { bool exists = false; QFileInfo fileInfo(filename); if (fileInfo.exists() && fileInfo.isFile()) { exists = true; } return exists; } //---------------------------------------------------- QJSValue EmuStateScriptObject::copy() { QJSValue jsVal; auto* engine = FCEU::JSEngine::getCurrent(); if (engine != nullptr) { EmuStateScriptObject* emuStateObj = new EmuStateScriptObject(); if (emuStateObj != nullptr) { *emuStateObj = *this; jsVal = engine->newQObject(emuStateObj); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QJSEngine::setObjectOwnership( emuStateObj, QJSEngine::JavaScriptOwnership); #endif } } return jsVal; } //---------------------------------------------------- //---- EMU Script Object //---------------------------------------------------- EmuScriptObject::EmuScriptObject(QObject* parent) : QObject(parent) { script = qobject_cast(parent); } //---------------------------------------------------- EmuScriptObject::~EmuScriptObject() { } //---------------------------------------------------- void EmuScriptObject::print(const QString& msg) { if (dialog != nullptr) { dialog->logOutput(msg); } } //---------------------------------------------------- void EmuScriptObject::powerOn() { fceuWrapperHardReset(); } //---------------------------------------------------- void EmuScriptObject::softReset() { fceuWrapperSoftReset(); } //---------------------------------------------------- void EmuScriptObject::pause() { FCEUI_SetEmulationPaused( EMULATIONPAUSED_PAUSED ); } //---------------------------------------------------- void EmuScriptObject::unpause() { FCEUI_SetEmulationPaused(0); } //---------------------------------------------------- bool EmuScriptObject::paused() { return FCEUI_EmulationPaused() != 0; } //---------------------------------------------------- void EmuScriptObject::frameAdvance() { script->frameAdvance(); } //---------------------------------------------------- int EmuScriptObject::frameCount() { return FCEUMOV_GetFrame(); } //---------------------------------------------------- int EmuScriptObject::lagCount() { return FCEUI_GetLagCount(); } //---------------------------------------------------- bool EmuScriptObject::lagged() { return FCEUI_GetLagged(); } //---------------------------------------------------- void EmuScriptObject::setLagFlag(bool flag) { FCEUI_SetLagFlag(flag); } //---------------------------------------------------- bool EmuScriptObject::emulating() { return (GameInfo != nullptr); } //---------------------------------------------------- bool EmuScriptObject::isReadOnly() { return FCEUI_GetMovieToggleReadOnly(); } //---------------------------------------------------- void EmuScriptObject::setReadOnly(bool flag) { FCEUI_SetMovieToggleReadOnly(flag); } //---------------------------------------------------- void EmuScriptObject::setRenderPlanes(bool sprites, bool background) { FCEUI_SetRenderPlanes(sprites, background); } //---------------------------------------------------- void EmuScriptObject::exit() { fceuWrapperRequestAppExit(); } //---------------------------------------------------- void EmuScriptObject::message(const QString& msg) { FCEU_DispMessage("%s",0, msg.toStdString().c_str()); } //---------------------------------------------------- void EmuScriptObject::speedMode(const QString& mode) { int speed = EMUSPEED_NORMAL; bool useTurbo = false; if (mode.contains("normal", Qt::CaseInsensitive)) { speed = EMUSPEED_NORMAL; } else if (mode.contains("nothrottle", Qt::CaseInsensitive)) { useTurbo = true; } else if (mode.contains("turbo", Qt::CaseInsensitive)) { useTurbo = true; } else if (mode.contains("maximum", Qt::CaseInsensitive)) { speed = EMUSPEED_FASTEST; } else { QString msg = "Invalid mode argument \"" + mode + "\" to emu.speedmode\n"; script->throwError(QJSValue::TypeError, msg); return; } if (useTurbo) { FCEUD_TurboOn(); } else { FCEUD_TurboOff(); } FCEUD_SetEmulationSpeed(speed); } //---------------------------------------------------- bool EmuScriptObject::addGameGenie(const QString& code) { // Add a Game Genie code if it hasn't already been added int GGaddr, GGcomp, GGval; int i=0; uint32 Caddr; uint8 Cval; int Ccompare, Ctype; if (!FCEUI_DecodeGG(code.toLocal8Bit().data(), &GGaddr, &GGval, &GGcomp)) { print("Failed to decode game genie code"); return false; } while (FCEUI_GetCheat(i,NULL,&Caddr,&Cval,&Ccompare,NULL,&Ctype)) { if ((static_cast(GGaddr) == Caddr) && (GGval == static_cast(Cval)) && (GGcomp == Ccompare) && (Ctype == 1)) { // Already Added, so consider it a success return true; } i = i + 1; } if (FCEUI_AddCheat(code.toLocal8Bit().data(),GGaddr,GGval,GGcomp,1)) { // Code was added // Can't manage the display update the way I want, so I won't bother with it // UpdateCheatsAdded(); return true; } else { // Code didn't get added } return false; } //---------------------------------------------------- bool EmuScriptObject::delGameGenie(const QString& code) { // Remove a Game Genie code. Very restrictive about deleted code. int GGaddr, GGcomp, GGval; uint32 i=0; std::string Cname; uint32 Caddr; uint8 Cval; int Ccompare, Ctype; if (!FCEUI_DecodeGG(code.toLocal8Bit().data(), &GGaddr, &GGval, &GGcomp)) { print("Failed to decode game genie code"); return false; } while (FCEUI_GetCheat(i,&Cname,&Caddr,&Cval,&Ccompare,NULL,&Ctype)) { QString name = QString::fromStdString(Cname); if ((code == name) && (static_cast(GGaddr) == Caddr) && (GGval == static_cast(Cval)) && (GGcomp == Ccompare) && (Ctype == 1)) { // Delete cheat code if (FCEUI_DelCheat(i)) { return true; } else { return false; } } i = i + 1; } // Cheat didn't exist, so it's not an error return true; } //---------------------------------------------------- void EmuScriptObject::registerBeforeFrame(const QJSValue& func) { script->registerBeforeEmuFrame(func); } //---------------------------------------------------- void EmuScriptObject::registerAfterFrame(const QJSValue& func) { script->registerAfterEmuFrame(func); } //---------------------------------------------------- void EmuScriptObject::registerStop(const QJSValue& func) { script->registerStop(func); } //---------------------------------------------------- bool EmuScriptObject::loadRom(const QString& romPath) { int ret = 0; if (!romPath.isEmpty()) { ret = LoadGame(romPath.toLocal8Bit().constData()); } return ret != 0; } //---------------------------------------------------- bool EmuScriptObject::onEmulationThread() { bool isEmuThread = (consoleWindow != nullptr) && (QThread::currentThread() == consoleWindow->emulatorThread); return isEmuThread; } //---------------------------------------------------- QString EmuScriptObject::getDir() { return QString(fceuExecutablePath()); } //---------------------------------------------------- QJSValue EmuScriptObject::getScreenPixel(int x, int y, bool useBackup) { int r,g,b,p; uint32_t pixel = GetScreenPixel(x,y,useBackup); r = JS_PIXEL_R(pixel); g = JS_PIXEL_G(pixel); b = JS_PIXEL_B(pixel); p = GetScreenPixelPalette(x,y,useBackup); ColorScriptObject* pixelObj = new ColorScriptObject(r, g, b); pixelObj->setPalette(p); QJSValue jsVal = engine->newQObject(pixelObj); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QJSEngine::setObjectOwnership( pixelObj, QJSEngine::JavaScriptOwnership); #endif return jsVal; } //---------------------------------------------------- QJSValue EmuScriptObject::createState(int slot) { QJSValue jsVal; EmuStateScriptObject* emuStateObj = new EmuStateScriptObject(slot); if (emuStateObj != nullptr) { jsVal = engine->newQObject(emuStateObj); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QJSEngine::setObjectOwnership( emuStateObj, QJSEngine::JavaScriptOwnership); #endif } return jsVal; } //---------------------------------------------------- //---- ROM Script Object //---------------------------------------------------- //---------------------------------------------------- RomScriptObject::RomScriptObject(QObject* parent) : QObject(parent) { script = qobject_cast(parent); } //---------------------------------------------------- RomScriptObject::~RomScriptObject() { } //---------------------------------------------------- bool RomScriptObject::isLoaded() { return (GameInfo != nullptr); } //---------------------------------------------------- QString RomScriptObject::getFileName() { QString baseName; if (GameInfo != nullptr) { baseName = FileBase; } return baseName; } //---------------------------------------------------- QString RomScriptObject::getHash(const QString& type) { QString hash; if (GameInfo != nullptr) { MD5DATA md5hash = GameInfo->MD5; if (type.compare("md5", Qt::CaseInsensitive) == 0) { hash = md5_asciistr(md5hash); } else if (type.compare("base64", Qt::CaseInsensitive) == 0) { hash = BytesToString(md5hash.data, MD5DATA::size).c_str(); } } return hash; } //---------------------------------------------------- uint8_t RomScriptObject::readByte(int address) { return FCEU_ReadRomByte(address); } //---------------------------------------------------- uint8_t RomScriptObject::readByteUnsigned(int address) { return FCEU_ReadRomByte(address); } //---------------------------------------------------- int8_t RomScriptObject::readByteSigned(int address) { return static_cast(FCEU_ReadRomByte(address)); } //---------------------------------------------------- QJSValue RomScriptObject::readByteRange(int start, int end) { QJSValue array; int size = end - start + 1; if (size > 0) { array = engine->newArray(size); for (int i=0; iprint("rom.writebyte() can't edit the ROM header."); } else { FCEU_WriteRomByte(address, value); } } //---------------------------------------------------- //---- PPU Script Object //---------------------------------------------------- //---------------------------------------------------- PpuScriptObject::PpuScriptObject(QObject* parent) : QObject(parent) { script = qobject_cast(parent); } //---------------------------------------------------- PpuScriptObject::~PpuScriptObject() { } //---------------------------------------------------- uint8_t PpuScriptObject::readByte(int address) { uint8_t byte = 0; if (FFCEUX_PPURead != nullptr) { byte = FFCEUX_PPURead(address); } return byte; } //---------------------------------------------------- uint8_t PpuScriptObject::readByteUnsigned(int address) { uint8_t byte = 0; if (FFCEUX_PPURead != nullptr) { byte = FFCEUX_PPURead(address); } return byte; } //---------------------------------------------------- int8_t PpuScriptObject::readByteSigned(int address) { int8_t byte = 0; if (FFCEUX_PPURead != nullptr) { byte = static_cast(FFCEUX_PPURead(address)); } return byte; } //---------------------------------------------------- QJSValue PpuScriptObject::readByteRange(int start, int end) { QJSValue array; int size = end - start + 1; if (FFCEUX_PPURead == nullptr) { return array; } if (size > 0) { array = engine->newArray(size); for (int i=0; i(parent); engine = script->getEngine(); } //---------------------------------------------------- InputScriptObject::~InputScriptObject() { } //---------------------------------------------------- QJSValue InputScriptObject::readJoypad(int player, bool immediate) { JoypadScriptObject* joypadObj = new JoypadScriptObject(player, immediate); QJSValue jsObj = engine->newQObject( joypadObj ); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) QJSEngine::setObjectOwnership( joypadObj, QJSEngine::JavaScriptOwnership); #endif return jsObj; } //---------------------------------------------------- //---- Memory Script Object //---------------------------------------------------- //---------------------------------------------------- static void addressReadCallback(unsigned int address, unsigned int value, void *userData) { MemoryScriptObject* mem = static_cast(userData); if (mem != nullptr) { QJSValue* func = mem->getReadFunc(address); if (func != nullptr) { QJSValueList args = { address, value }; mem->getScript()->runFunc( *func, args); } } } //---------------------------------------------------- static void addressWriteCallback(unsigned int address, unsigned int value, void *userData) { MemoryScriptObject* mem = static_cast(userData); if (mem != nullptr) { QJSValue* func = mem->getWriteFunc(address); if (func != nullptr) { QJSValueList args = { address, value }; mem->getScript()->runFunc( *func, args); } } } //---------------------------------------------------- static void addressExecCallback(unsigned int address, unsigned int value, void *userData) { MemoryScriptObject* mem = static_cast(userData); if (mem != nullptr) { QJSValue* func = mem->getExecFunc(address); if (func != nullptr) { QJSValueList args = { address, value }; mem->getScript()->runFunc( *func, args); } } } //---------------------------------------------------- MemoryScriptObject::MemoryScriptObject(QObject* parent) : QObject(parent) { script = qobject_cast(parent); } //---------------------------------------------------- MemoryScriptObject::~MemoryScriptObject() { unregisterAll(); } //---------------------------------------------------- void MemoryScriptObject::reset() { unregisterAll(); } //---------------------------------------------------- uint8_t MemoryScriptObject::readByte(int address) { return GetMem(address); } //---------------------------------------------------- uint8_t MemoryScriptObject::readByteUnsigned(int address) { return GetMem(address); } //---------------------------------------------------- int8_t MemoryScriptObject::readByteSigned(int address) { return static_cast(GetMem(address)); } //---------------------------------------------------- uint16_t MemoryScriptObject::readWord(int addressLow, int addressHigh) { // little endian, unless the high byte address is specified as a 2nd parameter if (addressHigh < 0) { addressHigh = addressLow + 1; } uint16_t result = GetMem(addressLow) | (GetMem(addressHigh) << 8); return result; } //---------------------------------------------------- uint16_t MemoryScriptObject::readWordUnsigned(int addressLow, int addressHigh) { // little endian, unless the high byte address is specified as a 2nd parameter if (addressHigh < 0) { addressHigh = addressLow + 1; } uint16_t result = GetMem(addressLow) | (GetMem(addressHigh) << 8); return result; } //---------------------------------------------------- int16_t MemoryScriptObject::readWordSigned(int addressLow, int addressHigh) { // little endian, unless the high byte address is specified as a 2nd parameter if (addressHigh < 0) { addressHigh = addressLow + 1; } uint16_t result = GetMem(addressLow) | (GetMem(addressHigh) << 8); return static_cast(result); } //---------------------------------------------------- void MemoryScriptObject::writeByte(int address, int value) { uint32_t A = address; uint8_t V = value; if (A < 0x10000) { BWrite[A](A, V); } } //---------------------------------------------------- uint16_t MemoryScriptObject::getRegisterPC() { return X.PC; } //---------------------------------------------------- uint8_t MemoryScriptObject::getRegisterA() { return X.A; } //---------------------------------------------------- uint8_t MemoryScriptObject::getRegisterX() { return X.X; } //---------------------------------------------------- uint8_t MemoryScriptObject::getRegisterY() { return X.Y; } //---------------------------------------------------- uint8_t MemoryScriptObject::getRegisterS() { return X.S; } //---------------------------------------------------- uint8_t MemoryScriptObject::getRegisterP() { return X.P; } //---------------------------------------------------- void MemoryScriptObject::setRegisterPC(uint16_t v) { X.PC = v; } //---------------------------------------------------- void MemoryScriptObject::setRegisterA(uint8_t v) { X.A = v; } //---------------------------------------------------- void MemoryScriptObject::setRegisterX(uint8_t v) { X.X = v; } //---------------------------------------------------- void MemoryScriptObject::setRegisterY(uint8_t v) { X.Y = v; } //---------------------------------------------------- void MemoryScriptObject::setRegisterS(uint8_t v) { X.S = v; } //---------------------------------------------------- void MemoryScriptObject::setRegisterP(uint8_t v) { X.P = v; } //---------------------------------------------------- void MemoryScriptObject::registerCallback(int type, const QJSValue& func, int address, int size) { int n=0; int *numFuncsRegistered = nullptr; QJSValue** funcArray = nullptr; switch (type) { default: case X6502_MemHook::Read: funcArray = readFunc; numFuncsRegistered = &numReadFuncsRegistered; X6502_MemHook::Add(X6502_MemHook::Read, addressReadCallback, this); break; case X6502_MemHook::Write: funcArray = writeFunc; numFuncsRegistered = &numWriteFuncsRegistered; X6502_MemHook::Add(X6502_MemHook::Write, addressWriteCallback, this); break; case X6502_MemHook::Exec: funcArray = execFunc; numFuncsRegistered = &numExecFuncsRegistered; X6502_MemHook::Add(X6502_MemHook::Exec, addressExecCallback, this); break; } n = *numFuncsRegistered; if (func.isCallable()) { for (int i=0; i= 0) && (addr < AddressRange) ) { if (funcArray[addr] != nullptr) { n--; delete funcArray[addr]; } funcArray[addr] = new QJSValue(func); n++; } } } else { for (int i=0; i= 0) && (addr < AddressRange) ) { if (funcArray[addr] != nullptr) { n--; delete funcArray[addr]; } funcArray[addr] = nullptr; } } } *numFuncsRegistered = n; } //---------------------------------------------------- void MemoryScriptObject::registerRead(const QJSValue& func, int address, int size) { registerCallback(X6502_MemHook::Read, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::registerWrite(const QJSValue& func, int address, int size) { registerCallback(X6502_MemHook::Write, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::registerExec(const QJSValue& func, int address, int size) { registerCallback(X6502_MemHook::Exec, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::unregisterCallback(int type, const QJSValue& func, int address, int size) { int n=0; int *numFuncsRegistered = nullptr; QJSValue** funcArray = nullptr; switch (type) { default: case X6502_MemHook::Read: funcArray = readFunc; numFuncsRegistered = &numReadFuncsRegistered; break; case X6502_MemHook::Write: funcArray = writeFunc; numFuncsRegistered = &numWriteFuncsRegistered; break; case X6502_MemHook::Exec: funcArray = execFunc; numFuncsRegistered = &numExecFuncsRegistered; break; } n = *numFuncsRegistered; if (func.isCallable()) { for (int i=0; i= 0) && (addr < AddressRange) ) { if (funcArray[addr] != nullptr) { n--; delete funcArray[addr]; } funcArray[addr] = nullptr; } } } else { for (int i=0; i= 0) && (addr < AddressRange) ) { if (funcArray[addr] != nullptr) { n--; delete funcArray[addr]; } funcArray[addr] = nullptr; } } } *numFuncsRegistered = n; if (0 <= numReadFuncsRegistered) { X6502_MemHook::Remove(X6502_MemHook::Read, addressReadCallback, this); } if (0 <= numWriteFuncsRegistered) { X6502_MemHook::Remove(X6502_MemHook::Write, addressWriteCallback, this); } if (0 <= numExecFuncsRegistered) { X6502_MemHook::Remove(X6502_MemHook::Exec, addressExecCallback, this); } } //---------------------------------------------------- void MemoryScriptObject::unregisterRead(const QJSValue& func, int address, int size) { unregisterCallback(X6502_MemHook::Read, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::unregisterWrite(const QJSValue& func, int address, int size) { unregisterCallback(X6502_MemHook::Write, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::unregisterExec(const QJSValue& func, int address, int size) { unregisterCallback(X6502_MemHook::Exec, func, address, size); } //---------------------------------------------------- void MemoryScriptObject::unregisterAll() { X6502_MemHook::Remove(X6502_MemHook::Read, addressReadCallback, this); X6502_MemHook::Remove(X6502_MemHook::Write, addressWriteCallback, this); X6502_MemHook::Remove(X6502_MemHook::Exec, addressExecCallback, this); for (int i=0; ilogOutput(fullMsg); } } } void JSEngine::acquireThreadContext() { prevContext = currentEngine; currentEngine = this; } void JSEngine::releaseThreadContext() { currentEngine = prevContext; prevContext = nullptr; } JSEngine* JSEngine::getCurrent() { return currentEngine; } } //---------------------------------------------------- //---- Qt Script Instance //---------------------------------------------------- QtScriptInstance::QtScriptInstance(QObject* parent) : QObject(parent) { QScriptDialog_t* win = qobject_cast(parent); if (win != nullptr) { dialog = win; } initEngine(); QtScriptManager::getInstance()->addScriptInstance(this); } //---------------------------------------------------- QtScriptInstance::~QtScriptInstance() { QtScriptManager::getInstance()->removeScriptInstance(this); shutdownEngine(); //printf("QtScriptInstance Destroyed\n"); } //---------------------------------------------------- void QtScriptInstance::shutdownEngine() { running = false; if (onFrameBeginCallback != nullptr) { delete onFrameBeginCallback; onFrameBeginCallback = nullptr; } if (onFrameFinishCallback != nullptr) { delete onFrameFinishCallback; onFrameFinishCallback = nullptr; } if (onScriptStopCallback != nullptr) { delete onScriptStopCallback; onScriptStopCallback = nullptr; } if (onGuiUpdateCallback != nullptr) { delete onGuiUpdateCallback; onGuiUpdateCallback = nullptr; } if (engine != nullptr) { engine->collectGarbage(); //engine->deleteLater(); delete engine; engine = nullptr; } if (emu != nullptr) { delete emu; emu = nullptr; } if (rom != nullptr) { delete rom; rom = nullptr; } if (ppu != nullptr) { delete ppu; ppu = nullptr; } if (mem != nullptr) { mem->reset(); delete mem; mem = nullptr; } if (input != nullptr) { delete input; input = nullptr; } if (ui_rootWidget != nullptr) { ui_rootWidget->hide(); ui_rootWidget->deleteLater(); ui_rootWidget = nullptr; } } //---------------------------------------------------- void QtScriptInstance::resetEngine() { shutdownEngine(); initEngine(); } //---------------------------------------------------- int QtScriptInstance::initEngine() { engine = new FCEU::JSEngine(this); engine->setScript(this); engine->setDialog(dialog); emu = new JS::EmuScriptObject(this); rom = new JS::RomScriptObject(this); ppu = new JS::PpuScriptObject(this); mem = new JS::MemoryScriptObject(this); input = new JS::InputScriptObject(this); emu->setDialog(dialog); rom->setDialog(dialog); mem->setDialog(dialog); engine->installExtensions(QJSEngine::ConsoleExtension); emu->setEngine(engine); rom->setEngine(engine); mem->setEngine(engine); // emu QJSValue emuObject = engine->newQObject(emu); engine->globalObject().setProperty("emu", emuObject); // rom QJSValue romObject = engine->newQObject(rom); engine->globalObject().setProperty("rom", romObject); // ppu QJSValue ppuObject = engine->newQObject(ppu); engine->globalObject().setProperty("ppu", ppuObject); // memory QJSValue memObject = engine->newQObject(mem); engine->globalObject().setProperty("memory", memObject); // input QJSValue inputObject = engine->newQObject(input); engine->globalObject().setProperty("input", inputObject); // gui QJSValue guiObject = engine->newQObject(this); engine->globalObject().setProperty("gui", guiObject); // Class Type Definitions for Script Use QJSValue jsColorMetaObject = engine->newQMetaObject(&JS::ColorScriptObject::staticMetaObject); engine->globalObject().setProperty("Color", jsColorMetaObject); QJSValue jsJoypadMetaObject = engine->newQMetaObject(&JS::JoypadScriptObject::staticMetaObject); engine->globalObject().setProperty("Joypad", jsJoypadMetaObject); QJSValue jsEmuStateMetaObject = engine->newQMetaObject(&JS::EmuStateScriptObject::staticMetaObject); engine->globalObject().setProperty("EmuState", jsEmuStateMetaObject); return 0; } //---------------------------------------------------- int QtScriptInstance::loadScriptFile( QString filepath ) { QFile scriptFile(filepath); running = false; if (!scriptFile.open(QIODevice::ReadOnly)) { return -1; } QTextStream stream(&scriptFile); QString fileText = stream.readAll(); scriptFile.close(); FCEU_WRAPPER_LOCK(); engine->acquireThreadContext(); QJSValue evalResult = engine->evaluate(fileText, filepath); engine->releaseThreadContext(); FCEU_WRAPPER_UNLOCK(); if (evalResult.isError()) { QString msg = "Uncaught exception at: " + evalResult.property("fileName").toString() + ":" + evalResult.property("lineNumber").toString() + " : " + evalResult.toString() + "\nStack:\n" + evalResult.property("stack").toString() + "\n"; print(msg); return -1; } else { running = true; //printf("Script Evaluation Success!\n"); } return 0; } //---------------------------------------------------- void QtScriptInstance::loadObjectChildren(QJSValue& jsObject, QObject* obj) { const QObjectList& childList = obj->children(); for (auto& child : childList) { QString name = child->objectName(); if (!name.isEmpty()) { //printf("Object: %s.%s\n", obj->objectName().toStdString().c_str(), child->objectName().toStdString().c_str()); QJSValue newJsObj = engine->newQObject(child); jsObject.setProperty(child->objectName(), newJsObj); loadObjectChildren( newJsObj, child); } } } //---------------------------------------------------- void QtScriptInstance::loadUI(const QString& uiFilePath) { #ifdef __QT_UI_TOOLS__ QFile uiFile(uiFilePath); QUiLoader uiLoader; ui_rootWidget = uiLoader.load(&uiFile, dialog); if (ui_rootWidget == nullptr) { return; } QJSValue uiObject = engine->newQObject(ui_rootWidget); engine->globalObject().setProperty("ui", uiObject); loadObjectChildren( uiObject, ui_rootWidget); ui_rootWidget->show(); #else throwError(QJSValue::GenericError, "Error: Application was not linked against Qt UI Tools"); #endif } //---------------------------------------------------- void QtScriptInstance::frameAdvance() { frameAdvanceCount++; } //---------------------------------------------------- void QtScriptInstance::registerBeforeEmuFrame(const QJSValue& func) { if (onFrameBeginCallback != nullptr) { delete onFrameBeginCallback; } onFrameBeginCallback = new QJSValue(func); } //---------------------------------------------------- void QtScriptInstance::registerAfterEmuFrame(const QJSValue& func) { if (onFrameFinishCallback != nullptr) { delete onFrameFinishCallback; } onFrameFinishCallback = new QJSValue(func); } //---------------------------------------------------- void QtScriptInstance::registerStop(const QJSValue& func) { if (onScriptStopCallback != nullptr) { delete onScriptStopCallback; } onScriptStopCallback = new QJSValue(func); } //---------------------------------------------------- void QtScriptInstance::registerGuiUpdate(const QJSValue& func) { if (onGuiUpdateCallback != nullptr) { delete onGuiUpdateCallback; } onGuiUpdateCallback = new QJSValue(func); } //---------------------------------------------------- void QtScriptInstance::print(const QString& msg) { if (dialog) { dialog->logOutput(msg); } } //---------------------------------------------------- bool QtScriptInstance::onEmulationThread() { bool isEmuThread = (consoleWindow != nullptr) && (QThread::currentThread() == consoleWindow->emulatorThread); return isEmuThread; } //---------------------------------------------------- bool QtScriptInstance::onGuiThread() { bool isGuiThread = (QThread::currentThread() == QApplication::instance()->thread()); return isGuiThread; } //---------------------------------------------------- int QtScriptInstance::throwError(QJSValue::ErrorType errorType, const QString &message) { running = false; print(message); engine->setInterrupted(true); return 0; } //---------------------------------------------------- void QtScriptInstance::printSymbols(QJSValue& val, int iter) { int i=0; if (iter > 10) { return; } QJSValueIterator it(val); while (it.hasNext()) { it.next(); QJSValue child = it.value(); qDebug() << iter << ":" << i << " " << it.name() << ": " << child.toString(); bool isPrototype = it.name() == "prototype"; if (!isPrototype) { printSymbols(child, iter + 1); } i++; } } //---------------------------------------------------- int QtScriptInstance::runFunc(QJSValue &func, const QJSValueList& args) { int retval = 0; auto state = getExecutionState(); state->start(); engine->acquireThreadContext(); QJSValue callResult = func.call(args); engine->releaseThreadContext(); state->stop(); if (callResult.isError()) { retval = -1; running = false; QString msg = "Uncaught exception at: " + callResult.property("fileName").toString() + ":" + callResult.property("lineNumber").toString() + " : " + callResult.toString() + "\nStack:\n" + callResult.property("stack").toString() + "\n"; print(msg); } return retval; } //---------------------------------------------------- int QtScriptInstance::call(const QString& funcName, const QJSValueList& args) { if (engine == nullptr) { return -1; } if (!engine->globalObject().hasProperty(funcName)) { print(QString("No function exists: ") + funcName); return -1; } QJSValue func = engine->globalObject().property(funcName); FCEU_WRAPPER_LOCK(); int retval = runFunc(func, args); FCEU_WRAPPER_UNLOCK(); return retval; } //---------------------------------------------------- void QtScriptInstance::stopRunning() { FCEU_WRAPPER_LOCK(); if (running) { if (onScriptStopCallback != nullptr && onScriptStopCallback->isCallable()) { runFunc( *onScriptStopCallback ); } running = false; mem->reset(); } FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QtScriptInstance::onFrameBegin() { if (running) { if (onFrameBeginCallback != nullptr && onFrameBeginCallback->isCallable()) { runFunc( *onFrameBeginCallback ); } if (frameAdvanceCount > 0) { if (frameAdvanceState == 0) { FCEUI_FrameAdvance(); frameAdvanceState = 1; frameAdvanceCount--; } } } } //---------------------------------------------------- void QtScriptInstance::onFrameFinish() { if (running) { if (onFrameFinishCallback != nullptr && onFrameFinishCallback->isCallable()) { runFunc( *onFrameFinishCallback ); } if (frameAdvanceState == 1) { FCEUI_FrameAdvanceEnd(); frameAdvanceState = 0; } } } //---------------------------------------------------- void QtScriptInstance::onGuiUpdate() { if (running && onGuiUpdateCallback != nullptr && onGuiUpdateCallback->isCallable()) { runFunc( *onGuiUpdateCallback ); } } //---------------------------------------------------- ScriptExecutionState* QtScriptInstance::getExecutionState() { ScriptExecutionState* state; if (onEmulationThread()) { state = &emuFuncState; } else { state = &guiFuncState; } return state; } //---------------------------------------------------- void QtScriptInstance::checkForHang() { static constexpr unsigned int funcTimeoutMs = 1000; if ( guiFuncState.isRunning() ) { unsigned int timeRunningMs = guiFuncState.timeCheck(); if (timeRunningMs > funcTimeoutMs) { printf("Interrupted GUI Thread Script Function\n"); engine->setInterrupted(true); } } if ( emuFuncState.isRunning() ) { unsigned int timeRunningMs = emuFuncState.timeCheck(); if (timeRunningMs > funcTimeoutMs) { printf("Interrupted Emulation Thread Script Function\n"); engine->setInterrupted(true); } } } //---------------------------------------------------- QString QtScriptInstance::openFileBrowser(const QString& initialPath) { QString selectedFile; QFileDialog dialog(this->dialog, tr("Open File") ); QList urls; bool useNativeFileDialogVal = false; g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal); const QStringList filters({ "Any files (*)" }); urls << QUrl::fromLocalFile( QDir::rootPath() ); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); urls << QUrl::fromLocalFile( QDir( FCEUI_GetBaseDirectory() ).absolutePath() ); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters( filters ); dialog.setViewMode(QFileDialog::List); dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden ); dialog.setLabelText( QFileDialog::Accept, tr("Open") ); if (!initialPath.isEmpty() ) { dialog.setDirectory( initialPath ); } dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); dialog.setSidebarUrls(urls); int ret = dialog.exec(); if ( ret ) { QStringList fileList; fileList = dialog.selectedFiles(); if ( fileList.size() > 0 ) { selectedFile = fileList[0]; } } return selectedFile; } //---------------------------------------------------- //---- Qt Script Manager //---------------------------------------------------- QtScriptManager* QtScriptManager::_instance = nullptr; QtScriptManager::QtScriptManager(QObject* parent) : QObject(parent) { _instance = this; monitorThread = new ScriptMonitorThread_t(this); monitorThread->start(); periodicUpdateTimer = new QTimer(this); connect( periodicUpdateTimer, &QTimer::timeout, this, &QtScriptManager::guiUpdate ); periodicUpdateTimer->start(50); // ~20hz } //---------------------------------------------------- QtScriptManager::~QtScriptManager() { monitorThread->requestInterruption(); monitorThread->wait(); _instance = nullptr; //printf("QtScriptManager destroyed\n"); } //---------------------------------------------------- QtScriptManager* QtScriptManager::create(QObject* parent) { QtScriptManager* mgr = new QtScriptManager(parent); //printf("QtScriptManager created\n"); return mgr; } //---------------------------------------------------- void QtScriptManager::destroy(void) { if (_instance != nullptr) { delete _instance; } } //---------------------------------------------------- void QtScriptManager::logMessageQt(QtMsgType type, const QString &msg) { auto* engine = FCEU::JSEngine::getCurrent(); if (engine != nullptr) { int logLevel = FCEU::JSEngine::WARNING; switch (type) { case QtDebugMsg: logLevel = FCEU::JSEngine::DEBUG; break; case QtInfoMsg: logLevel = FCEU::JSEngine::INFO; break; case QtWarningMsg: logLevel = FCEU::JSEngine::WARNING; break; case QtCriticalMsg: logLevel = FCEU::JSEngine::CRITICAL; break; case QtFatalMsg: logLevel = FCEU::JSEngine::FATAL; break; } engine->logMessage( logLevel, msg ); } } //---------------------------------------------------- void QtScriptManager::addScriptInstance(QtScriptInstance* script) { FCEU::autoScopedLock autoLock(scriptListMutex); scriptList.push_back(script); } //---------------------------------------------------- void QtScriptManager::removeScriptInstance(QtScriptInstance* script) { FCEU::autoScopedLock autoLock(scriptListMutex); auto it = scriptList.begin(); while (it != scriptList.end()) { if (*it == script) { it = scriptList.erase(it); } else { it++; } } } //---------------------------------------------------- void QtScriptManager::frameBeginUpdate() { FCEU_WRAPPER_LOCK(); for (auto script : scriptList) { script->onFrameBegin(); } FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QtScriptManager::frameFinishedUpdate() { FCEU_WRAPPER_LOCK(); for (auto script : scriptList) { script->onFrameFinish(); } FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QtScriptManager::guiUpdate() { FCEU_WRAPPER_LOCK(); for (auto script : scriptList) { script->onGuiUpdate(); } FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- //---- Qt Script Monitor Thread //---------------------------------------------------- ScriptMonitorThread_t::ScriptMonitorThread_t(QObject *parent) : QThread(parent) { } //---------------------------------------------------- void ScriptMonitorThread_t::run() { //printf("Script Monitor Thread is Running...\n"); QtScriptManager* manager = QtScriptManager::getInstance(); while (!isInterruptionRequested()) { manager->scriptListMutex.lock(); for (auto script : manager->scriptList) { script->checkForHang(); } manager->scriptListMutex.unlock(); msleep(ScriptExecutionState::checkPeriod); } //printf("Script Monitor Thread is Stopping...\n"); } //---------------------------------------------------- //---- Qt Script Dialog Window //---------------------------------------------------- QScriptDialog_t::QScriptDialog_t(QWidget *parent) : QDialog(parent, Qt::Window) { QVBoxLayout *mainLayout; QHBoxLayout *hbox; QPushButton *closeButton; QLabel *lbl; std::string filename; QSettings settings; resize(512, 512); setWindowTitle(tr("Qt Java Script Control")); mainLayout = new QVBoxLayout(); lbl = new QLabel(tr("Script File:")); scriptPath = new QLineEdit(); scriptArgs = new QLineEdit(); g_config->getOption("SDL.LastLoadJs", &filename); scriptPath->setText( tr(filename.c_str()) ); scriptPath->setClearButtonEnabled(true); scriptArgs->setClearButtonEnabled(true); jsOutput = new QTextEdit(); jsOutput->setReadOnly(true); hbox = new QHBoxLayout(); browseButton = new QPushButton(tr("Browse")); stopButton = new QPushButton(tr("Stop")); scriptInstance = new QtScriptInstance(this); if (scriptInstance->isRunning()) { startButton = new QPushButton(tr("Restart")); } else { startButton = new QPushButton(tr("Start")); } stopButton->setEnabled(scriptInstance->isRunning()); connect(browseButton, SIGNAL(clicked()), this, SLOT(openScriptFile(void))); connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void))); connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(void))); hbox->addWidget(browseButton); hbox->addWidget(stopButton); hbox->addWidget(startButton); mainLayout->addWidget(lbl); mainLayout->addWidget(scriptPath); mainLayout->addLayout(hbox); hbox = new QHBoxLayout(); lbl = new QLabel(tr("Arguments:")); hbox->addWidget(lbl); hbox->addWidget(scriptArgs); mainLayout->addLayout(hbox); tabWidget = new QTabWidget(); mainLayout->addWidget(tabWidget); tabWidget->addTab(jsOutput, tr("Output Console")); propTree = new JsPropertyTree(); propTree->setColumnCount(3); propTree->setSelectionMode( QAbstractItemView::SingleSelection ); propTree->setAlternatingRowColors(true); auto* item = new QTreeWidgetItem(); item->setText(0, QString::fromStdString("Name")); item->setText(1, QString::fromStdString("Type")); item->setText(2, QString::fromStdString("Value")); item->setTextAlignment(0, Qt::AlignLeft); item->setTextAlignment(1, Qt::AlignLeft); item->setTextAlignment(2, Qt::AlignLeft); propTree->setHeaderItem(item); propTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents); tabWidget->addTab(propTree, tr("Global Properties")); closeButton = new QPushButton( tr("Close") ); closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); hbox = new QHBoxLayout(); hbox->addStretch(5); hbox->addWidget( closeButton, 1 ); mainLayout->addLayout( hbox ); setLayout(mainLayout); emuThreadText.reserve(4096); //winList.push_back(this); periodicTimer = new QTimer(this); connect(periodicTimer, &QTimer::timeout, this, &QScriptDialog_t::updatePeriodic); periodicTimer->start(200); // 5hz restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray()); } //---------------------------------------------------- QScriptDialog_t::~QScriptDialog_t(void) { QSettings settings; //printf("Destroy JS Control Window\n"); periodicTimer->stop(); clearPropertyTree(); scriptInstance->stopRunning(); delete scriptInstance; settings.setValue("QScriptWindow/geometry", saveGeometry()); } //---------------------------------------------------- void QScriptDialog_t::closeEvent(QCloseEvent *event) { scriptInstance->stopRunning(); //printf("JS Control Close Window Event\n"); done(0); deleteLater(); event->accept(); } //---------------------------------------------------- void QScriptDialog_t::closeWindow(void) { scriptInstance->stopRunning(); //printf("JS Control Close Window\n"); done(0); deleteLater(); } //---------------------------------------------------- void QScriptDialog_t::clearPropertyTree() { propTree->childMap.clear(); propTree->clear(); } //---------------------------------------------------- void QScriptDialog_t::loadPropertyTree(QJSValue& object, JsPropertyItem* parentItem) { QJSValueIterator it(object); while (it.hasNext()) { it.next(); QJSValue child = it.value(); bool isPrototype = it.name() == "prototype"; if (!isPrototype) { JsPropertyItem* item = nullptr; QString name = it.name(); QString value; const char *type = "unknown"; bool itemIsNew = false; if (parentItem == nullptr) { auto it = propTree->childMap.find(name); if (it != propTree->childMap.end()) { item = it.value(); } } else { auto it = parentItem->childMap.find(name); if (it != parentItem->childMap.end()) { item = it.value(); } } if (item == nullptr) { item = new JsPropertyItem(); itemIsNew = true; } if (child.isArray()) { type = "array"; } else if (child.isBool()) { type = "bool"; value = QVariant(child.toBool()).toString(); } else if (child.isCallable()) { type = "function"; value = ""; } else if (child.isDate()) { type = "date"; value = child.toDateTime().toString(); } else if (child.isError()) { type = "error"; value = child.toString(); } else if (child.isNull()) { type = "null"; value = "null"; } else if (child.isNumber()) { type = "number"; value = QVariant(child.toNumber()).toString(); } else if (child.isRegExp()) { type = "RegExp"; value.clear(); } else if (child.isQMetaObject()) { type = "meta"; value.clear(); auto* meta = child.toQMetaObject(); if (meta != nullptr) { value = meta->className(); } } else if (child.isObject()) { type = "object"; value.clear(); auto* obj = child.toQObject(); if (obj != nullptr) { auto* meta = obj->metaObject(); if (meta != nullptr) { value = meta->className(); } } } else if (child.isString()) { type = "string"; value = child.toString(); } if (itemIsNew) { item->setText(0, name); item->setText(1, type); item->setText(2, value); } else { bool itemHasChanged = !item->jsValue.strictlyEquals(child); if (itemHasChanged) { item->setText(2, value); } } item->jsValue = child; if (itemIsNew) { if (parentItem == nullptr) { propTree->addTopLevelItem(item); propTree->childMap[name] = item; } else { parentItem->addChild(item); parentItem->childMap[name] = item; } } if (child.isObject()) { loadPropertyTree(child, item); } } } } //---------------------------------------------------- void QScriptDialog_t::updatePeriodic(void) { //printf("Update JS\n"); FCEU_WRAPPER_LOCK(); if (!emuThreadText.isEmpty()) { auto* vbar = jsOutput->verticalScrollBar(); int vbarValue = vbar->value(); bool vbarAtMax = vbarValue >= vbar->maximum(); jsOutput->insertPlainText(emuThreadText); if (vbarAtMax) { vbar->setValue( vbar->maximum() ); } emuThreadText.clear(); } if (scriptInstance != nullptr) { auto* engine = scriptInstance->getEngine(); if (engine) { QJSValue globals = engine->globalObject(); loadPropertyTree(globals); } } refreshState(); FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QScriptDialog_t::openJSKillMessageBox(void) { int ret; QMessageBox msgBox(this); msgBox.setIcon(QMessageBox::Warning); msgBox.setText(tr("The JS script running has been running a long time.\nIt may have gone crazy. Kill it? (I won't ask again if you say No)\n")); msgBox.setStandardButtons(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); ret = msgBox.exec(); if (ret == QMessageBox::Yes) { } } //---------------------------------------------------- void QScriptDialog_t::openScriptFile(void) { int ret, useNativeFileDialogVal; QString filename; std::string last; std::string dir; const char *exePath = nullptr; const char *jsPath = nullptr; QFileDialog dialog(this, tr("Open JS Script")); QList urls; QDir d; exePath = fceuExecutablePath(); //urls = dialog.sidebarUrls(); urls << QUrl::fromLocalFile(QDir::rootPath()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); urls << QUrl::fromLocalFile(QDir(FCEUI_GetBaseDirectory()).absolutePath()); if (exePath[0] != 0) { d.setPath(QString(exePath) + "/../jsScripts"); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } #ifndef WIN32 d.setPath("/usr/share/fceux/jsScripts"); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } #endif jsPath = getenv("FCEU_QSCRIPT_PATH"); // Parse LUA_PATH and add to urls if (jsPath) { int i, j; char stmp[2048]; i = j = 0; while (jsPath[i] != 0) { if (jsPath[i] == ';') { stmp[j] = 0; if (j > 0) { d.setPath(stmp); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } j = 0; } else { stmp[j] = jsPath[i]; j++; } i++; } stmp[j] = 0; if (j > 0) { d.setPath(stmp); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } } dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilter(tr("JS Scripts (*.js *.JS) ;; All files (*)")); dialog.setViewMode(QFileDialog::List); dialog.setFilter(QDir::AllEntries | QDir::AllDirs | QDir::Hidden); dialog.setLabelText(QFileDialog::Accept, tr("Load")); g_config->getOption("SDL.LastLoadJs", &last); if (last.size() == 0) { #ifdef WIN32 last.assign(FCEUI_GetBaseDirectory()); #else last.assign("/usr/share/fceux/jsScripts"); #endif } getDirFromFile(last.c_str(), dir); dialog.setDirectory(tr(dir.c_str())); // Check config option to use native file dialog or not g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal); dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); dialog.setSidebarUrls(urls); ret = dialog.exec(); if (ret) { QStringList fileList; fileList = dialog.selectedFiles(); if (fileList.size() > 0) { filename = fileList[0]; } } if (filename.isNull()) { return; } //qDebug() << "selected file path : " << filename.toUtf8(); g_config->setOption("SDL.LastLoadJs", filename.toStdString().c_str()); scriptPath->setText(filename); } //---------------------------------------------------- void QScriptDialog_t::startScript(void) { FCEU_WRAPPER_LOCK(); jsOutput->clear(); clearPropertyTree(); scriptInstance->resetEngine(); if (scriptInstance->loadScriptFile(scriptPath->text())) { // Script parsing error FCEU_WRAPPER_UNLOCK(); return; } // Pass command line arguments to script main. QStringList argStringList = scriptArgs->text().split(" ", Qt::SkipEmptyParts); QJSValue argArray = scriptInstance->getEngine()->newArray(argStringList.size()); for (int i=0; icall("main", argList); refreshState(); QJSValue globals = scriptInstance->getEngine()->globalObject(); loadPropertyTree(globals); FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QScriptDialog_t::stopScript(void) { FCEU_WRAPPER_LOCK(); scriptInstance->stopRunning(); refreshState(); FCEU_WRAPPER_UNLOCK(); } //---------------------------------------------------- void QScriptDialog_t::refreshState(void) { if (scriptInstance->isRunning()) { stopButton->setEnabled(true); startButton->setText(tr("Restart")); } else { stopButton->setEnabled(false); startButton->setText(tr("Start")); } } //---------------------------------------------------- void QScriptDialog_t::logOutput(const QString& text) { if (QThread::currentThread() == consoleWindow->emulatorThread) { emuThreadText.append(text); } else { auto* vbar = jsOutput->verticalScrollBar(); int vbarValue = vbar->value(); bool vbarAtMax = vbarValue >= vbar->maximum(); jsOutput->insertPlainText(text); if (vbarAtMax) { vbar->setValue( vbar->maximum() ); } } } //---------------------------------------------------- #endif // __FCEU_QSCRIPT_ENABLE__