648 lines
16 KiB
C++
648 lines
16 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "Debugger.h"
|
|
#include "Project64\UserInterface\DiscordRPC.h"
|
|
#include "ScriptAPI/ScriptAPI.h"
|
|
#include "ScriptInstance.h"
|
|
#include "ScriptSystem.h"
|
|
#include "ScriptTypes.h"
|
|
#include <sstream>
|
|
#include <sys/stat.h>
|
|
|
|
CScriptSystem::CScriptSystem(CDebuggerUI * debugger) :
|
|
m_Debugger(debugger),
|
|
m_NextAppCallbackId(0),
|
|
m_AppCallbackCount(0),
|
|
m_CpuExecCbInfo({}),
|
|
m_CpuReadCbInfo({}),
|
|
m_CpuWriteCbInfo({})
|
|
{
|
|
InitDirectories();
|
|
|
|
m_hCmdEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
|
m_hThread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
|
|
}
|
|
|
|
CScriptSystem::~CScriptSystem()
|
|
{
|
|
PostCommand(JS_CMD_SHUTDOWN);
|
|
WaitForSingleObject(m_hThread, INFINITE);
|
|
CloseHandle(m_hThread);
|
|
CloseHandle(m_hCmdEvent);
|
|
}
|
|
|
|
JSInstanceStatus CScriptSystem::GetStatus(const char * name)
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
if (m_UIInstanceStatus.count(name) == 0)
|
|
{
|
|
return JS_STATUS_STOPPED;
|
|
}
|
|
else
|
|
{
|
|
return m_UIInstanceStatus[name];
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::NotifyStatus(const char * name, JSInstanceStatus status)
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
if (status == JS_STATUS_STOPPED)
|
|
{
|
|
m_UIInstanceStatus.erase(name);
|
|
}
|
|
else
|
|
{
|
|
m_UIInstanceStatus[name] = status;
|
|
}
|
|
m_Debugger->Debug_RefreshScriptsWindow();
|
|
}
|
|
|
|
void CScriptSystem::ConsoleLog(const char * format, ...)
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
int size = vsnprintf(nullptr, 0, format, args) + 1;
|
|
char * str = new char[size];
|
|
vsnprintf(str, size, format, args);
|
|
|
|
stdstr formattedMsg = FixStringReturns(str) + "\r\n";
|
|
|
|
m_Debugger->Debug_LogScriptsWindow(formattedMsg.c_str());
|
|
m_UILog += formattedMsg;
|
|
|
|
delete[] str;
|
|
va_end(args);
|
|
}
|
|
|
|
void CScriptSystem::ConsolePrint(const char * format, ...)
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
int size = vsnprintf(nullptr, 0, format, args) + 1;
|
|
char * str = new char[size];
|
|
vsnprintf(str, size, format, args);
|
|
|
|
stdstr formattedMsg = FixStringReturns(str);
|
|
|
|
m_Debugger->Debug_LogScriptsWindow(formattedMsg.c_str());
|
|
m_UILog += formattedMsg;
|
|
|
|
delete[] str;
|
|
va_end(args);
|
|
}
|
|
|
|
void CScriptSystem::ConsoleClear()
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
m_UILog.clear();
|
|
m_Debugger->Debug_ClearScriptsWindow();
|
|
}
|
|
|
|
stdstr CScriptSystem::GetConsoleBuffer()
|
|
{
|
|
CGuard guard(m_UIStateCS);
|
|
return stdstr(m_UILog);
|
|
}
|
|
|
|
stdstr CScriptSystem::InstallDirPath()
|
|
{
|
|
return m_InstallDirFullPath;
|
|
}
|
|
|
|
stdstr CScriptSystem::ScriptsDirPath()
|
|
{
|
|
return m_ScriptsDirFullPath;
|
|
}
|
|
|
|
stdstr CScriptSystem::ModulesDirPath()
|
|
{
|
|
return m_ModulesDirFullPath;
|
|
}
|
|
|
|
void CScriptSystem::InitDirectories()
|
|
{
|
|
m_InstallDirFullPath = (std::string)CPath(CPath::MODULE_DIRECTORY);
|
|
m_ScriptsDirFullPath = m_InstallDirFullPath + SCRIPTSYS_SCRIPTS_DIR;
|
|
m_ModulesDirFullPath = m_InstallDirFullPath + SCRIPTSYS_MODULES_DIR;
|
|
|
|
if (!PathFileExistsA(m_ScriptsDirFullPath.c_str()))
|
|
{
|
|
CreateDirectoryA(m_ScriptsDirFullPath.c_str(), nullptr);
|
|
}
|
|
|
|
if (!PathFileExistsA(m_ModulesDirFullPath.c_str()))
|
|
{
|
|
CreateDirectoryA(m_ModulesDirFullPath.c_str(), nullptr);
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::StartScript(const char * name, const char * path)
|
|
{
|
|
PostCommand(JS_CMD_START_SCRIPT, name, path);
|
|
}
|
|
|
|
void CScriptSystem::StopScript(const char * name)
|
|
{
|
|
PostCommand(JS_CMD_STOP_SCRIPT, name);
|
|
}
|
|
|
|
void CScriptSystem::Input(const char * name, const char * code)
|
|
{
|
|
PostCommand(JS_CMD_INPUT, name, code);
|
|
}
|
|
|
|
bool CScriptSystem::HaveAppCallbacks(JSAppHookID hookId)
|
|
{
|
|
CGuard guard(m_InstancesCS);
|
|
|
|
return (hookId < JS_NUM_APP_HOOKS && m_AppCallbackHooks[hookId].size() > 0);
|
|
}
|
|
|
|
void CScriptSystem::InvokeAppCallbacks(JSAppHookID hookId, void * env)
|
|
{
|
|
CGuard guard(m_InstancesCS);
|
|
|
|
JSAppCallbackList & callbacks = m_AppCallbackHooks[hookId];
|
|
size_t numCallbacks = callbacks.size();
|
|
|
|
bool bNeedSweep = false;
|
|
|
|
for (size_t i = 0; i < numCallbacks; i++)
|
|
{
|
|
JSAppCallback & callback = callbacks[i];
|
|
CScriptInstance * instance = callback.m_Instance;
|
|
|
|
if (callback.m_bDisabled || instance->IsStopping() || m_StopsIssued.count(instance->Name()) != 0 || !callback.m_ConditionFunc(&callback, env))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
instance->RawInvokeAppCallback(callback, env);
|
|
|
|
if (instance->GetRefCount() == 0)
|
|
{
|
|
bNeedSweep = true;
|
|
}
|
|
}
|
|
|
|
if (bNeedSweep)
|
|
{
|
|
PostCommand(JS_CMD_SWEEP);
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::UpdateCpuCbListInfo(volatile JSCpuCbListInfo & info, JSAppCallbackList & callbacks)
|
|
{
|
|
uint32_t minAddrStart = 0;
|
|
uint32_t maxAddrEnd = 0;
|
|
int numCacheEntries = 0;
|
|
bool bCacheExceeded = false;
|
|
|
|
for (JSAppCallback & callback : callbacks)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < numCacheEntries; i++)
|
|
{
|
|
// combine adjacent/overlapping ranges
|
|
if ((callback.m_Params.addrStart >= info.rangeCache[i].addrStart && callback.m_Params.addrStart <= info.rangeCache[i].addrEnd + 1) || (callback.m_Params.addrEnd >= info.rangeCache[i].addrStart - 1 && callback.m_Params.addrEnd <= info.rangeCache[i].addrEnd))
|
|
{
|
|
info.rangeCache[i].addrStart = min(info.rangeCache[i].addrStart, callback.m_Params.addrStart);
|
|
info.rangeCache[i].addrEnd = max(info.rangeCache[i].addrEnd, callback.m_Params.addrEnd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == numCacheEntries)
|
|
{
|
|
if (i == JS_CPU_CB_RANGE_CACHE_SIZE)
|
|
{
|
|
bCacheExceeded = true;
|
|
}
|
|
else
|
|
{
|
|
info.rangeCache[i].addrStart = callback.m_Params.addrStart;
|
|
info.rangeCache[i].addrEnd = callback.m_Params.addrEnd;
|
|
numCacheEntries++;
|
|
}
|
|
}
|
|
|
|
if (callback.m_Params.addrStart < minAddrStart)
|
|
{
|
|
minAddrStart = callback.m_Params.addrStart;
|
|
}
|
|
|
|
if (callback.m_Params.addrEnd > maxAddrEnd)
|
|
{
|
|
maxAddrEnd = callback.m_Params.addrEnd;
|
|
}
|
|
}
|
|
|
|
info.numRangeCacheEntries = numCacheEntries;
|
|
info.bRangeCacheExceeded = bCacheExceeded;
|
|
info.numCallbacks = callbacks.size();
|
|
info.minAddrStart = minAddrStart;
|
|
info.maxAddrEnd = maxAddrEnd;
|
|
}
|
|
|
|
void CScriptSystem::RefreshCallbackMaps()
|
|
{
|
|
for (JSAppCallbackList & callbacks : m_AppCallbackHooks)
|
|
{
|
|
JSAppCallbackList::iterator it = callbacks.begin();
|
|
|
|
while (it != callbacks.end())
|
|
{
|
|
if (it->m_bDisabled)
|
|
{
|
|
it = callbacks.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateCpuCbListInfo(m_CpuExecCbInfo, m_AppCallbackHooks[JS_HOOK_CPU_EXEC]);
|
|
UpdateCpuCbListInfo(m_CpuReadCbInfo, m_AppCallbackHooks[JS_HOOK_CPU_READ]);
|
|
UpdateCpuCbListInfo(m_CpuWriteCbInfo, m_AppCallbackHooks[JS_HOOK_CPU_WRITE]);
|
|
}
|
|
|
|
void CScriptSystem::DoMouseEvent(JSAppHookID hookId, int x, int y, DWORD uMsg)
|
|
{
|
|
int button = -1;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
button = 0;
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
button = 1;
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
button = 2;
|
|
break;
|
|
}
|
|
|
|
JSHookMouseEnv env = {x, y, button};
|
|
InvokeAppCallbacks(hookId, (void *)&env);
|
|
}
|
|
|
|
void CScriptSystem::PostCMethodCall(const char * name, void * dukThisHeapPtr, duk_c_function func,
|
|
JSDukArgSetupFunc argSetupFunc, void * argSetupParam, size_t argSetupParamSize)
|
|
{
|
|
CGuard guard(m_CmdQueueCS);
|
|
// Will be deleted by command handler
|
|
JSSysCMethodCall * methodCall = new JSSysCMethodCall(dukThisHeapPtr, func, argSetupFunc, argSetupParam, argSetupParamSize);
|
|
PostCommand(JS_CMD_C_METHOD_CALL, name, "", (void *)methodCall);
|
|
}
|
|
|
|
void CScriptSystem::PostCommand(JSSysCommandID id, stdstr paramA, stdstr paramB, void * paramC)
|
|
{
|
|
CGuard guard(m_CmdQueueCS);
|
|
JSSysCommand cmd = {id, paramA, paramB, paramC};
|
|
|
|
if (id == JS_CMD_STOP_SCRIPT)
|
|
{
|
|
JSInstanceName instName = paramA;
|
|
if (m_StopsIssued.count(instName) != 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_StopsIssued.insert(instName);
|
|
}
|
|
}
|
|
|
|
m_CmdQueue.push_back(cmd);
|
|
SetEvent(m_hCmdEvent);
|
|
}
|
|
|
|
DWORD CScriptSystem::ThreadProc(void * _this)
|
|
{
|
|
((CScriptSystem *)_this)->ThreadProc();
|
|
return 0;
|
|
}
|
|
|
|
void CScriptSystem::ThreadProc()
|
|
{
|
|
std::vector<JSSysCommand> queue;
|
|
|
|
while (true)
|
|
{
|
|
WaitForSingleObject(m_hCmdEvent, INFINITE);
|
|
|
|
{
|
|
CGuard guard(m_CmdQueueCS);
|
|
queue = m_CmdQueue;
|
|
m_CmdQueue.clear();
|
|
}
|
|
|
|
if (!ProcessCommandQueue(queue))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::OnStartScript(const char * name, const char * path)
|
|
{
|
|
if (m_Instances.count(name) != 0)
|
|
{
|
|
ConsoleLog("[SCRIPTSYS]: error: START_SCRIPT aborted; '%s' is already instanced", name);
|
|
}
|
|
|
|
CScriptInstance * inst = new CScriptInstance(this, name);
|
|
|
|
NotifyStatus(name, JS_STATUS_STARTING);
|
|
|
|
if (inst->Run(path) && inst->GetRefCount() > 0)
|
|
{
|
|
m_Instances[name] = inst;
|
|
NotifyStatus(name, JS_STATUS_STARTED);
|
|
}
|
|
else
|
|
{
|
|
NotifyStatus(name, JS_STATUS_STOPPED);
|
|
delete inst;
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::OnStopScript(const char * name)
|
|
{
|
|
if (m_Instances.count(name) == 0)
|
|
{
|
|
ConsoleLog("[SCRIPTSYS]: error: STOP_SCRIPT aborted; instance '%s' does not exist", name);
|
|
return;
|
|
}
|
|
|
|
m_StopsIssued.erase(name);
|
|
RawRemoveInstance(name);
|
|
NotifyStatus(name, JS_STATUS_STOPPED);
|
|
}
|
|
|
|
void CScriptSystem::OnInput(const char * name, const char * code)
|
|
{
|
|
if (m_Instances.count(name) == 0)
|
|
{
|
|
ConsoleLog("[SCRIPTSYS]: error: INPUT aborted; instance '%s' does not exist", name);
|
|
return;
|
|
}
|
|
|
|
CScriptInstance * inst = m_Instances[name];
|
|
|
|
inst->RawConsoleInput(code);
|
|
|
|
if (!inst->IsStopping() && inst->GetRefCount() == 0)
|
|
{
|
|
NotifyStatus(name, JS_STATUS_STOPPED);
|
|
RawRemoveInstance(name);
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::OnCMethodCall(const char * name, JSSysCMethodCall * methodCall)
|
|
{
|
|
if (m_Instances.count(name) == 0)
|
|
{
|
|
ConsoleLog("[SCRIPTSYS]: error: method call aborted; instance '%s' doesn't exist", name);
|
|
}
|
|
|
|
if (m_Instances.count(name) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CScriptInstance * inst = m_Instances[name];
|
|
|
|
inst->RawCMethodCall(methodCall->m_DukThisHeapPtr, methodCall->m_Func, methodCall->m_ArgSetupFunc, methodCall->m_ArgSetupParam);
|
|
|
|
if (!inst->IsStopping() && inst->GetRefCount() == 0)
|
|
{
|
|
NotifyStatus(name, JS_STATUS_STOPPED);
|
|
RawRemoveInstance(name);
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::OnSweep(bool bIfDone)
|
|
{
|
|
JSInstanceMap::iterator it = m_Instances.begin();
|
|
while (it != m_Instances.end())
|
|
{
|
|
CScriptInstance *& inst = it->second;
|
|
if (!bIfDone || inst->GetRefCount() == 0)
|
|
{
|
|
NotifyStatus(inst->Name().c_str(), JS_STATUS_STOPPED);
|
|
delete inst;
|
|
it = m_Instances.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CScriptSystem::RawRemoveInstance(const char * name)
|
|
{
|
|
if (m_Instances.count(name) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CScriptInstance *& inst = m_Instances[name];
|
|
|
|
if (inst->IsStopping())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inst->SetStopping(true);
|
|
inst->StopRegisteredWorkers();
|
|
|
|
std::vector<JSSysCommand> pendingCalls;
|
|
PullCommands(JS_CMD_C_METHOD_CALL, pendingCalls);
|
|
ProcessCommandQueue(pendingCalls);
|
|
|
|
delete inst;
|
|
m_Instances.erase(name);
|
|
return true;
|
|
}
|
|
|
|
JSAppCallbackID CScriptSystem::RawAddAppCallback(JSAppHookID hookId, JSAppCallback & callback)
|
|
{
|
|
if (hookId >= JS_NUM_APP_HOOKS)
|
|
{
|
|
return JS_INVALID_CALLBACK;
|
|
}
|
|
|
|
callback.m_Instance->IncRefCount();
|
|
callback.m_CallbackId = m_NextAppCallbackId++;
|
|
m_AppCallbackHooks[hookId].push_back(callback);
|
|
m_AppCallbackCount++;
|
|
RefreshCallbackMaps();
|
|
return callback.m_CallbackId;
|
|
}
|
|
|
|
void CScriptSystem::RawRemoveAppCallback(JSAppHookID hookId, JSAppCallbackID callbackId)
|
|
{
|
|
JSAppCallbackList::iterator it = m_AppCallbackHooks[hookId].begin();
|
|
while (it != m_AppCallbackHooks[hookId].end())
|
|
{
|
|
if (it->m_CallbackId == callbackId)
|
|
{
|
|
m_AppCallbackCount--;
|
|
it->m_Instance->DecRefCount();
|
|
it->m_bDisabled = true;
|
|
return;
|
|
}
|
|
|
|
it++;
|
|
}
|
|
RefreshCallbackMaps();
|
|
}
|
|
|
|
void CScriptSystem::ExecAutorunList()
|
|
{
|
|
LoadAutorunList();
|
|
|
|
std::istringstream joinedNames(g_Settings->LoadStringVal(Debugger_AutorunScripts));
|
|
std::string scriptName;
|
|
while (std::getline(joinedNames, scriptName, '|'))
|
|
{
|
|
std::string scriptPath = m_ScriptsDirFullPath + scriptName;
|
|
ConsoleLog("[SCRIPTSYS]: autorun '%s'", scriptName.c_str());
|
|
StartScript(scriptName.c_str(), scriptPath.c_str());
|
|
}
|
|
}
|
|
|
|
std::set<std::string> & CScriptSystem::AutorunList()
|
|
{
|
|
return m_AutorunList;
|
|
}
|
|
|
|
void CScriptSystem::LoadAutorunList()
|
|
{
|
|
m_AutorunList.clear();
|
|
|
|
std::istringstream joinedNames(g_Settings->LoadStringVal(Debugger_AutorunScripts));
|
|
std::string scriptName;
|
|
|
|
while (std::getline(joinedNames, scriptName, '|'))
|
|
{
|
|
m_AutorunList.insert(scriptName);
|
|
}
|
|
}
|
|
|
|
void CScriptSystem::SaveAutorunList()
|
|
{
|
|
std::string joinedNames = "";
|
|
|
|
std::set<std::string>::iterator it;
|
|
for (it = m_AutorunList.begin(); it != m_AutorunList.end(); it++)
|
|
{
|
|
if (it != m_AutorunList.begin())
|
|
{
|
|
joinedNames += "|";
|
|
}
|
|
joinedNames += *it;
|
|
}
|
|
|
|
g_Settings->SaveString(Debugger_AutorunScripts, joinedNames.c_str());
|
|
}
|
|
|
|
CDebuggerUI * CScriptSystem::Debugger()
|
|
{
|
|
return m_Debugger;
|
|
}
|
|
|
|
stdstr CScriptSystem::FixStringReturns(const char * str)
|
|
{
|
|
stdstr fstr = str;
|
|
size_t pos = 0;
|
|
while ((pos = fstr.find("\n", pos)) != stdstr::npos)
|
|
{
|
|
fstr.replace(pos, 1, "\r\n");
|
|
pos += 2;
|
|
}
|
|
return fstr;
|
|
}
|
|
|
|
bool CScriptSystem::ProcessCommandQueue(std::vector<JSSysCommand> & queue)
|
|
{
|
|
std::vector<JSSysCommand>::iterator it;
|
|
for (it = queue.begin(); it != queue.end(); it++)
|
|
{
|
|
if (!ProcessCommand(*it))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CScriptSystem::ProcessCommand(JSSysCommand & cmd)
|
|
{
|
|
CGuard guard(m_InstancesCS);
|
|
|
|
switch (cmd.id)
|
|
{
|
|
case JS_CMD_START_SCRIPT:
|
|
OnStartScript(cmd.paramA.c_str(), cmd.paramB.c_str());
|
|
break;
|
|
case JS_CMD_STOP_SCRIPT:
|
|
OnStopScript(cmd.paramA.c_str());
|
|
break;
|
|
case JS_CMD_INPUT:
|
|
OnInput(cmd.paramA.c_str(), cmd.paramB.c_str());
|
|
break;
|
|
case JS_CMD_C_METHOD_CALL:
|
|
{
|
|
JSSysCMethodCall * methodCall = (JSSysCMethodCall *)cmd.paramC;
|
|
OnCMethodCall(cmd.paramA.c_str(), methodCall);
|
|
delete methodCall;
|
|
}
|
|
break;
|
|
case JS_CMD_SWEEP:
|
|
OnSweep(true);
|
|
break;
|
|
case JS_CMD_SHUTDOWN:
|
|
OnSweep(false);
|
|
return false;
|
|
}
|
|
|
|
RefreshCallbackMaps();
|
|
return true;
|
|
}
|
|
|
|
void CScriptSystem::PullCommands(JSSysCommandID id, std::vector<JSSysCommand> & out)
|
|
{
|
|
CGuard guard(m_CmdQueueCS);
|
|
|
|
std::vector<JSSysCommand>::iterator it;
|
|
for (it = m_CmdQueue.begin(); it != m_CmdQueue.end();)
|
|
{
|
|
if ((*it).id == id)
|
|
{
|
|
out.push_back(*it);
|
|
it = m_CmdQueue.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|