fceux/src/drivers/Qt/QtScriptManager.cpp

2614 lines
61 KiB
C++

/* 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 <stdio.h>
#include <string.h>
#include <list>
#ifdef WIN32
#include <Windows.h>
#endif
#include <QTextEdit>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
#include <QHeaderView>
#include <QJSValueIterator>
#ifdef __QT_UI_TOOLS__
#include <QUiLoader>
#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<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) && 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<size_t>(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<QtScriptInstance*>(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<uint32>(GGaddr) == Caddr) && (GGval == static_cast<int>(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<uint32>(GGaddr) == Caddr) && (GGval == static_cast<int>(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<QtScriptInstance*>(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<int8_t>(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; i<size; i++)
{
int byte = FCEU_ReadRomByte(start + i);
QJSValue element = byte;
array.setProperty(i, element);
}
}
return array;
}
//----------------------------------------------------
void RomScriptObject::writeByte(int address, int value)
{
if (address < 16)
{
script->print("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<QtScriptInstance*>(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<int8_t>(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<size; i++)
{
int byte = FFCEUX_PPURead(start + i);
QJSValue element = byte;
array.setProperty(i, element);
}
}
return array;
}
//----------------------------------------------------
void PpuScriptObject::writeByte(int address, int value)
{
if (FFCEUX_PPUWrite != nullptr)
{
FFCEUX_PPUWrite(address, value);
}
}
//----------------------------------------------------
//---- Input Script Object
//----------------------------------------------------
//----------------------------------------------------
InputScriptObject::InputScriptObject(QObject* parent)
: QObject(parent)
{
script = qobject_cast<QtScriptInstance*>(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<MemoryScriptObject*>(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<MemoryScriptObject*>(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<MemoryScriptObject*>(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<QtScriptInstance*>(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<int8_t>(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<int16_t>(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<size; i++)
{
int addr = address + i;
if ( (addr >= 0) && (addr < AddressRange) )
{
if (funcArray[addr] != nullptr)
{
n--;
delete funcArray[addr];
}
funcArray[addr] = new QJSValue(func);
n++;
}
}
}
else
{
for (int i=0; i<size; i++)
{
int addr = address + i;
if ( (addr >= 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<size; i++)
{
int addr = address + i;
if ( (addr >= 0) && (addr < AddressRange) )
{
if (funcArray[addr] != nullptr)
{
n--;
delete funcArray[addr];
}
funcArray[addr] = nullptr;
}
}
}
else
{
for (int i=0; i<size; i++)
{
int addr = address + i;
if ( (addr >= 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; i<AddressRange; i++)
{
if (execFunc[i] != nullptr)
{
numExecFuncsRegistered--;
delete execFunc[i];
}
if (readFunc[i] != nullptr)
{
numReadFuncsRegistered--;
delete readFunc[i];
}
if (writeFunc[i] != nullptr)
{
numWriteFuncsRegistered--;
delete writeFunc[i];
}
execFunc[i] = nullptr;
readFunc[i] = nullptr;
writeFunc[i] = nullptr;
}
numReadFuncsRegistered = 0;
numWriteFuncsRegistered = 0;
numExecFuncsRegistered = 0;
}
} // JS
//----------------------------------------------------
//---- FCEU JSEngine
//----------------------------------------------------
namespace FCEU
{
JSEngine::JSEngine(QObject* parent)
: QJSEngine(parent)
{
}
JSEngine::~JSEngine()
{
}
void JSEngine::logMessage(int lvl, const QString& msg)
{
if (dialog != nullptr)
{
if (lvl <= _logLevel)
{
const char *prefix = "Warning: ";
switch (lvl)
{
case FCEU::JSEngine::DEBUG:
prefix = "Debug: ";
break;
case FCEU::JSEngine::INFO:
prefix = "Info: ";
break;
case FCEU::JSEngine::WARNING:
prefix = "Warning: ";
break;
case FCEU::JSEngine::CRITICAL:
prefix = "Critical: ";
break;
case FCEU::JSEngine::FATAL:
prefix = "Fatal: ";
break;
}
QString fullMsg = prefix + msg.trimmed() + "\n";
dialog->logOutput(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<QScriptDialog_t*>(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<QUrl> 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<QUrl> 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; i<argStringList.size(); i++)
{
argArray.setProperty(i, argStringList[i]);
}
QJSValueList argList = { argArray };
scriptInstance->call("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__