From 8ebb560d1c0c402e30eb7dcc58efd2db56f774a3 Mon Sep 17 00:00:00 2001 From: harry Date: Tue, 6 Feb 2024 22:44:08 -0500 Subject: [PATCH] Added emulator save state JS interface. --- src/drivers/Qt/QtScriptManager.cpp | 199 ++++++++++++++++++++++++++++- src/drivers/Qt/QtScriptManager.h | 38 ++++++ 2 files changed, 233 insertions(+), 4 deletions(-) diff --git a/src/drivers/Qt/QtScriptManager.cpp b/src/drivers/Qt/QtScriptManager.cpp index bb097a92..f2bb376c 100644 --- a/src/drivers/Qt/QtScriptManager.cpp +++ b/src/drivers/Qt/QtScriptManager.cpp @@ -44,6 +44,7 @@ #include "../../video.h" #include "../../x6502.h" #include "../../debug.h" +#include "../../state.h" #include "../../ppu.h" #include "common/os_utils.h" @@ -83,6 +84,8 @@ ColorScriptObject::ColorScriptObject(int r, int g, int b) { numInstances++; //printf("ColorScriptObject(r,g,b) %p Constructor: %i\n", this, numInstances); + + moveToThread(QApplication::instance()->thread()); } //---------------------------------------------------- ColorScriptObject::~ColorScriptObject() @@ -91,6 +94,177 @@ ColorScriptObject::~ColorScriptObject() //printf("ColorScriptObject %p Destructor: %i\n", this, numInstances); } //---------------------------------------------------- +//---- 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) + { + 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) + { + 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) ) + { + FCEU_printf("Warning: JS EmuState::loadFromFile failed to load full buffer.\n"); + delete data; + data = nullptr; + } + fclose(inf); + return true; +} +//---------------------------------------------------- //---- EMU Script Object //---------------------------------------------------- EmuScriptObject::EmuScriptObject(QObject* parent) @@ -361,8 +535,6 @@ QJSValue EmuScriptObject::getScreenPixel(int x, int y, bool useBackup) pixelObj->setPalette(p); - pixelObj->moveToThread(QApplication::instance()->thread()); - QJSValue jsVal = engine->newQObject(pixelObj); #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) @@ -372,6 +544,22 @@ QJSValue EmuScriptObject::getScreenPixel(int x, int y, bool useBackup) 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 //---------------------------------------------------- //---------------------------------------------------- @@ -1077,8 +1265,11 @@ int QtScriptInstance::initEngine() engine->globalObject().setProperty("gui", guiObject); // Class Type Definitions for Script Use - QJSValue jsMetaObject = engine->newQMetaObject(&JS::ColorScriptObject::staticMetaObject); - engine->globalObject().setProperty("Color", jsMetaObject); + QJSValue jsColorMetaObject = engine->newQMetaObject(&JS::ColorScriptObject::staticMetaObject); + engine->globalObject().setProperty("Color", jsColorMetaObject); + + QJSValue jsEmuStateMetaObject = engine->newQMetaObject(&JS::EmuStateScriptObject::staticMetaObject); + engine->globalObject().setProperty("EmuState", jsEmuStateMetaObject); return 0; } diff --git a/src/drivers/Qt/QtScriptManager.h b/src/drivers/Qt/QtScriptManager.h index e9cd0817..0bed1b82 100644 --- a/src/drivers/Qt/QtScriptManager.h +++ b/src/drivers/Qt/QtScriptManager.h @@ -65,6 +65,43 @@ public slots: Q_INVOKABLE void setPalette(int p){ _palette = p; } }; +class EmuStateScriptObject: public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE EmuStateScriptObject(const QJSValue& jsArg1 = QJSValue(), const QJSValue& jsArg2 = QJSValue()); + ~EmuStateScriptObject(); + + Q_PROPERTY(bool persist READ isPersistent WRITE setPersistent) + Q_PROPERTY(int slot READ getSlot WRITE setSlot) + Q_PROPERTY(int compressionLevel READ getCompressionLevel WRITE setCompressionLevel) + + EmuStateScriptObject& operator= (const EmuStateScriptObject& obj); + +private: + QString filename; + EMUFILE_MEMORY *data = nullptr; + int compression = 0; + int slot = -1; + bool persist = false; + + static int numInstances; + +public slots: + Q_INVOKABLE bool save(); + Q_INVOKABLE bool load(); + Q_INVOKABLE bool isValid(){ return (data != nullptr); } + Q_INVOKABLE bool isPersistent(){ return persist; } + Q_INVOKABLE void setPersistent(bool value){ persist = value; } + Q_INVOKABLE int getSlot(){ return slot; } + Q_INVOKABLE void setSlot(int value); + Q_INVOKABLE int getCompressionLevel(){ return compression; } + Q_INVOKABLE void setCompressionLevel(int value){ compression = value; } + Q_INVOKABLE void setFilename(const QString& name){ filename = name; } + Q_INVOKABLE bool saveToFile(const QString& filepath); + Q_INVOKABLE bool loadFromFile(const QString& filepath); +}; + class EmuScriptObject: public QObject { Q_OBJECT @@ -108,6 +145,7 @@ public slots: Q_INVOKABLE void exit(); Q_INVOKABLE QString getDir(); Q_INVOKABLE QJSValue getScreenPixel(int x, int y, bool useBackup = false); + Q_INVOKABLE QJSValue createState(int slot = -1); };