Added emulator save state JS interface.

This commit is contained in:
harry 2024-02-06 22:44:08 -05:00
parent 2f2482e950
commit 8ebb560d1c
2 changed files with 233 additions and 4 deletions

View File

@ -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<EmuStateScriptObject*>(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<size_t>(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;
}

View File

@ -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);
};